•  

ГлавнаяIndyЧастые вопросы по Indy → Автоматизация скачивания с FTP

Создано: 17.05.2014 1:17:41 · Исправлено: 17.05.2014 3:10:01 · Прочтений: 1799

Добрый день, добрый вечер! Тема вопроса у меня связана с FTP клиентом. Немного отойду от темы! Я работаю в ИТ отделе банка М, города В. У нас есть программа которую регулярно обновляют (бухгалтерского характера). Все пользователи ей пользуются! Когда приходит время обновления то самым простым способом является замена одной папки на главном компьютере офиса. По городу В у нас пока 3 офиса, по области еще 2. Представляете эту картину когда приходит обновление. Скорость обмена не ахти и занимает очень много времени. Поэтому меня немного осинило, на данном сайте пол года тому назад я задавал вопрос по поводу SQL таблиц, а именно складская структура , мол оборудование получил отдал! и т.д., так вот в одних из справочников в моей базе есть список ДО (дополнительных офисов) в которой и хранится сетевая информация (ИП,маска,шлюз,ДНС, а также ИП сервака в каждом офисе)! Задумка довольно таки проста, в каждом офисе сделать ФТП сервер, который каждые ну там 30 минут будет проверять обновления! А пользователи перед запуском программного комплекса будут запускать мою минипрограмму, которая делает: 1) соединяется с SQl сервером 2) определяет свой ИП-к 3) посылает запрос мол какой сервак ближайший (получает его ИП) 4) связывается с ФТП и с указанной папки качает к себе 5) запускает обновленную программу Немного муторно! Дело в том что авто обновление есть в самом комплексе, только недавнешнее обновление с Москвы меня огорчило, программеры видимо версию поменяли, а файл инициализации нет. И поэтому версия программы осталась прежней! с 1-4 у меня все выходит. Только через Wininet немного коряво получается. Меня всетаки интересует
function FtpCommand(hConnect: HINTERNET; fExpectResponse: BOOL;
  dwFlags: DWORD; lpszCommand: PChar; dwContext: DWORD): BOOL; stdcall;
Как им пользоваться я точно не понял, а именно
function FtpCommand(
hConnect: HINTERNET;  // session
fExpectResponse: BOOL; //Ждать ответа от сервера
dwFlags: DWORD;        // Способ передачи информации
lpszCommand: PChar;  // собственно сам запрос
dwContext: DWORD      // !! Вот это слабо понимаю что это такое
                  ): BOOL; stdcall;

Дело в том что запрос я сделал только как считать результат запроса!? Вот код моего экземпляра! Он радотает конечно. Но хотелось бы сделать не при помощи команды FtpGetFile и этим перещелкиванием InternetFindNextFile. Я конечно понимаю что альтернатива будет не легче но хотябы это можно как то контролировать. Если еть у когото соображения пишите. Мне будет интересно почитать! Ну а если кому поможет моя статья тоже буду рад! Скинул бы файл, но походу на данном форуме кидаться такими вещами не льзя! И так код моего исходника используется MEmo, только модуль нужно будет передрать на форму!
unit FTP_move;

interface
uses
winsock,wininet,Classes,Variants,SysUtils,Controls,ExtCtrls,
windows,ComCtrls,Messages,Dialogs;


type
  ftpstat=^tftpstat;
  tftpstat=record
  ftp_open,
  ftp_con: HINTERNET;
end;




//// Функция статуса подключения
function ftpstatus(nameAgent:string;Port:integer;host,user,passwd:string):tftpstat;

//// Просмотр результат, чтение снутренности заданной папки
function ftpshow(session:tftpstat;dir_net,dir_local,mask:string;down:boolean =false):boolean;

//Второй и последний способ скачивания файла
function Getftpfile (session:HINTERNET;file_in,file_out:string):boolean;


// Самый первый способ содранный с интернета
function ftpDown (session:HINTERNET;dir_net,dir_local,name:string):boolean;



  var
  Filestat:ftpstat;


implementation
uses down_ftp;


///////////////////////////////////////////////////////////////////////////////////////
//// Функция статуса подключения
function ftpstatus(nameAgent:string;Port:integer;host,user,passwd:string):tftpstat;
begin
  Result.ftp_open:=InternetOpen(pchar(nameAgent),
            INTERNET_OPEN_TYPE_PRECONFIG, // AccessType
            nil,  // ProxyName
            nil, // ProxyBypass
            0); // or I

  result.ftp_con:=InternetConnect(Result.ftp_open, // Handle from InternetOpen
                          PChar(host), // FTP server
                          Port, // порт
                          PChar(user), // username
                          PChar(passwd),  // password
                          INTERNET_SERVICE_FTP, // FTP, HTTP, or Gopher?
                          0, // flag: 0 or INTERNET_FLAG_PASSIVE
                          0);// User defined number for callback

end;

/////////////////////////////////////////////////////////////////////////////////
//// Просмотр результат, чтение снутренности заданной папки .........................................
function ftpshow(session:tftpstat;dir_net,dir_local,mask:string;down:boolean =false):boolean;
var
ftp_dir:HINTERNET;
find_data: TWin32FindData;
begin
if (session.ftp_open<>nil) and (session.ftp_con<>nil) then
begin
if FtpSetCurrentDirectory(session.ftp_con, PChar(dir_net))= false then
FtpSetCurrentDirectory(session.ftp_con, PChar(/));

// Заходим в заданную директорию, в дальнейшем при втором способе оказалось ////не нужнойц
  ftp_dir:=FtpFindFirstFile(session.ftp_con,pchar(mask),find_data,0,0);

  //// пока не дойдем до последнего файла
    while GetLastError<>ERROR_NO_MORE_FILES do
    begin
      Form1.Memo1.Lines.Add(dir_net+--->+find_data.cFileName+ -and- +dir_local+--->+find_data.cFileName+ error +IntToStr(GetLastError));
    if IOResult <> 0 then
    begin
    InternetCloseHandle(session.ftp_con);
    end;
      // отмечаем только файлы и закачивать ли их
// 1604 - файлы,1877 - папки
      if (find_data.dwReserved0<>1877)and (down=true) then
      begin

        Getftpfile
        (
        session.ftp_con,
        dir_net+/+find_data.cFileName,
        dir_local+/+find_data.cFileName,

        );

      end;

    InternetFindNextFile(ftp_dir,@find_data);

    end;
InternetCloseHandle(session.ftp_con);
end
else exit;
end;



/////////////////////////////////////////////////////////////////////////////
//Второй и последний способ скачивания файла
function Getftpfile (session:HINTERNET;file_in,file_out:string):boolean;
begin
FtpGetFile(
            session,
            pchar(file_in),
            pchar(file_out),
            FALSE,

            FILE_ATTRIBUTE_NORMAL,
            (FTP_TRANSFER_TYPE_BINARY or INTERNET_FLAG_RELOAD),
            0

          );

end;


/////////////////////////////////////////////////////////////////////////////
// Самый первый способ содранный с интернета
function ftpDown (session:HINTERNET;dir_net,dir_local,name:string):boolean;
const
  READ_BUFFERSIZE = 256;  // or 256, 512, ...

var
  hNet, hFTP, hFile: HINTERNET;
  buffer: array[0..READ_BUFFERSIZE - 1] of Char;
  bufsize, dwBytesRead, fileSize: DWORD;
  strStatus: string;
  LocalFile: file;
  bSuccess: Boolean;
begin

  hFile := FtpOpenFile(session, // Handle to the ftp session
                      PChar(name), // filename
                      GENERIC_READ, // dwAccess
                      FTP_TRANSFER_TYPE_BINARY , // dwFlags
                      0); // This is the context used for callbacks.

  if hFile = nil then
  begin
    InternetCloseHandle(hFTP);
    Exit;
  end;
//  Create a new local file
  AssignFile(LocalFile, dir_local+\+name);
  {$i-}
  Rewrite(LocalFile, 1);
  {$i+}

  if IOResult <> 0 then
  begin
    InternetCloseHandle(hFile);
    InternetCloseHandle(hFTP);
    Exit;
  end;

  dwBytesRead := 0;
  bufsize := READ_BUFFERSIZE;

  while (bufsize > 0) do
  begin
//    Application.ProcessMessages;

    if not InternetReadFile(hFile,
                            @buffer, // address of a buffer that receives the data
                            READ_BUFFERSIZE, // number of bytes to read from the file
                            bufsize) then Break; // receives the actual number of bytes read

    if (bufsize > 0) and (bufsize <= READ_BUFFERSIZE) then
      BlockWrite(LocalFile, buffer, bufsize);
    dwBytesRead := dwBytesRead + bufsize;

  end;

  CloseFile(LocalFile); // закрываем файл

//  InternetCloseHandle(session);
  Result := True;

end;
с 1-4 у меня все выходит. Только через Wininet немного коряво получается. Я делал подобную задачу, только использовал indy компоненты (idFTP). Получилось достаточно просто и красиво. У меня программа действует немного по другому. 1) Загружаем конфигурацию программы и соединяемся с ftp сервером 2) Если соединение не удалось установить, то запускаем основную программу 3) Загружаем конфигурацию обновления (в ней говорится что запускать, что загружать, в какие папки, какие компоненты надо регистрировать и тд) 4) Проверяем все файлы на ftp сервере, сравнивая дату файла и размер. 5) Если что-то не совпадает - забираем файл 6) Регистрируем компоненты (в соответствии с конфигурацией) 7) Запускаенм основной исполняемый файл. В принципе можно завязать это хозяйство на СУБД. Обратите внимание на то, что программа сначала проверяет атрибуты файлов, а потом производит скачивание файла, при этом проверка производится каждый раз при запуске программы. Если делать проверку каждые 30 минут, например, то надо тогда каким-то образом решать проблему закрытия всех ранее запущенных приложений. При этом программа обновления должна не удалять обновляемые файлы, а перемещать их, что позволяет проводить обновление когда основное приложение запущено. В случае сбоя приложения (отрубилось питание, например) программа обновления должна поидее довести копирование до конца, что в общем-то также решается при применении моего алгоритма.