Вы здесь

Создать свой сервис обновления: как загрузить файлы с сайта

Погрузка
 
 
В первых двух частях разговора о создании в КаВо своего сервиса обновления для программ мы уже немного поговорили о чудесах архивации и контроля версий файлов. Самое время начать «качать» данные!
 
 
 
С этой целью, CA-Visual Objects (КаВо) предоставляет нам чудесную библиотеку «Internet». Для работы по протоколу ftp – класс «cFTP», а с http – «cHTTP». Допустим, у меня уже есть сайт, и я не хочу использовать протокол FTP, значит, мне нужен «cHTTP». Документация о работе с интернетом в КаВо столь «подробна» и примеров так много, что прям «нажимай да дуй». Или, как говорят у нас – «без пол литра – не разобраться». :)
 
Geoff SchallerПосмотрел в интернете на форумах, и понял, что здесь явно что-то не так. Ведь, не будет же народ упрямо пытаться что-то где-то дорабатывать. Начались эти попытки с австралийского программиста Geoff Schaller-а и продолжились в «IC2» (см. CA-Visual Objects: Third-Party Products, пункт 20).
 
 
     На фото: Geoff Schaller
 
 
И самое неприятное для начинающих – что до сих пор никто не удосужился «разжевать» эту тему, рассказать «что с чем едят». Короче, «копать от забора и до обеда». И тут нам поможет SDK КаВо.
 
«Последователей» предупрежу сразу – код в SDK, скорее всего, не совсем соответствует действительности. И это сильно затрудняет понимание вопроса. Т.е., некоторые ошибки, которые там нашёл, абсолютно не проявляли себя, когда вместо SDK-библиотеки подключал стандартную (не SDK) библиотеку. Например, в help-е следующий синтаксис:
 
CHttp{<cCaption>, <n>}  => SELF
Init(<cCaption>, <n>)  => SELF
 
А на самом деле, там три параметра (см. SDK):
 
METHOD Init( cCaption, n, lStat ) CLASS CHttp
 
Третий параметр не описан. А он – весьма «подленький». «lStat» фактически отвечает за использование критических секций. TRUE – использовать, FALSE – нет. Автор SDK применил свой механизм работы с секциями и считает, что вы тоже их используете (по умолчанию, lStat = TRUE). Следовательно, при работе с SDK, если не планируете использовать критические секции, надо писать: cHTTP{ cCaption, n, FALSE }. Или переписать SDK. Самое смешное, в рабочей (не SDK) библиотеке такой проблемы – нет. Ей всё равно, используете вы критические секции или нет.
 
Если сильно не хочется в SDK работать с критическими секциями, предлагаю субклассирование:
 
CLASS msHTTP INHERIT cHTTP // Pencil
 
METHOD Init( cCaption, n, lStat ) CLASS msHTTP
      // Description: Этот метод предназначен только для работы с SDK
      // Parameters :
      // Returns    :
 
      // Если критическая секция не инициализирована, использовать её нельзя!!!
      IF csWSA.DebugInfo = NULL_PTR
            lStat := FALSE
      ENDIF
     
      SUPER:Init( cCaption, n, lStat )
 
      RETURN SELF
 
Где csWSA – глобальная переменная моей критической секции. А у вас может быть другая. Как-то так. Ещё раз напоминаю, в нормальном коде (не для работы с SDK) – это не нужно, более того – противопоказано. Только для работы с SDK!!!
 
Вернёмся чуть назад. Почему я отказался от идеи использования ftp? Во-первых, лёгких путей мы не ищем, во-вторых, зачем лишний раз где-то «светить» данные подключений, в-третьих, есть стандартные средства для сканирования ftp на предмет наличия файлов, а для http – нет. Хочу, чтобы пользователи получали имена и адрес файлов на основе обработки указанных данных на сайте. Плюс простота проверки настроек в Windows: если в IE открываются интернет-странички, то и обмен файлами тоже будет.
 
Да уж, как-то я опрометчиво отказался от идеи использования ftp… Дальнейшее исследование показало, что в стандартной библиотеке нет годных средств для работы с файлами по протоколу http. «Орешек знаний твёрд. Но всё же, нам расколоть его поможет…». И хорошая музыка. Что вы сейчас слушаете? А я (прям сейчас) – «RPWL».
 
План такой:
  1. Открываем интернет-сессию (передаём данные о нашем прокси и пр.). Здесь мы используем API-функцию InternetOpen().
  2. Соединяемся с хостом (ftp, http). Это – InternetConnect().
  3. Выполняем нужные запросы (работаем в сессии с хостом: документы, файлы и пр.). См. InternetOpenUrl().
     Ну, а закрывать будем в обратном порядке.
 
 
Смотрим, что и как сделано в библиотеке «Internet SDK». И видим, что там вообще не предусмотрен контроль действий. Т.е., очень желательно дописать проверку открытия интернет-сессии и соединения с хостом (сайтом). Ибо, то, что есть – абсолютно нерабочее. Плюс, анализ кода SDK показывает слабую проработку закрытия процессов. А значит, «от греха» стоит эти кусочки тоже переписать (нет гарантий, что в стандартной библиотеке эти вопросы решены лучше).
 
Здесь надо принять ещё одно волевое решение: как мы будем контролировать, и сообщать причины неполадок. Я пошёл стандартным путём – если Windows считает, что интернет-сессия не открыта – просто сообщать об этом, не уточняя причины. Это дело администратора. Мы, ведь, не пишем диагностическую программу? Там большое поле для деятельности: подножки могут ставить как сам Windows (Firewall), устройства (плата, модем, роутер, кабель), так и провайдер.
 
CLASS msHTTP INHERIT cHTTP // Pencil
      // Description:
 
      DECLARE ACCESS IsOpen // Статус интернета: открыт или нет
 
ACCESS IsOpen AS LOGIC PASCAL CLASS msHTTP
      // Description: Проверка открытия интернета (активное или пассивное подключение)
            // Примечание: если пассивное - оно реально может и не работать...
 
      // Parameters : No
      // Returns    : LOGIC
 
      LOCAL lSuccess AS LOGIC
      LOCAL dwConnectionType AS DWORD
 
 
      IF SELF:hSession == NULL_PTR
            lSuccess := FALSE
      ELSE
            dwConnectionType := INTERNET_CONNECTION_MODEM + INTERNET_CONNECTION_LAN + INTERNET_CONNECTION_PROXY 
 
                  // INTERNET_CONNECTION_MODEM      = 1; // Через модем 
                  // INTERNET_CONNECTION_LAN        = 2; 
                  // INTERNET_CONNECTION_PROXY      = 4; 
/*
; Return :
; 0x40 INTERNET_CONNECTION_CONFIGURED : Local system has a valid connection to the Internet, but it might or might not be currently connected.
; 0x02 INTERNET_CONNECTION_LAN : Local system uses a local area network to connect to the Internet.
; 0x01 INTERNET_CONNECTION_MODEM : Local system uses a modem to connect to the Internet.
; 0x08 INTERNET_CONNECTION_MODEM_BUSY : No longer used.
; 0x20 INTERNET_CONNECTION_OFFLINE : Local system is in offline mode.
; 0x04 INTERNET_CONNECTION_PROXY : Local system uses a proxy server to connect to the Internet.
; 0x10 INTERNET_RAS_INSTALLED : Local system has RAS installed
; or 0 if there is no Internet connection
*/
            lSuccess := InternetGetConnectedState( @dwConnectionType, 0 )
      ENDIF
 
      RETURN lSuccess
 
METHOD IsConnected( cHost ) CLASS msHTTP
      // Description: Проверка соединения с хостом
      // Parameters :
      // Returns    : LOGIC
 
      LOCAL lSuccess AS LOGIC
 
 
      IF SELF:hConnect == NULL_PTR
            lSuccess := FALSE
      ELSE
 
            IF     IsNil( cHost )
 
                  cHost := SELF:cCurrentUrl
                  lSuccess := InternetCheckConnection( String2Psz( cHost ), FLAG_ICC_FORCE_CONNECTION, 0 )
 
            ELSEIF IsString( cHost )
 
                  cHost := AllTrim( cHost )
                  IF cHost == NULL_STRING
                        cHost := SELF:cCurrentUrl
                  ELSE
 
                  IF At2( "//", cHost ) == 0
 
                     IF SELF:lFtpRequest
                        cHost := "ftp://" + cHost
                     ELSE
                        cHost := "http://" + cHost
                     ENDIF
                  ENDIF
                  ENDIF
 
                  lSuccess := InternetCheckConnection( String2Psz( cHost ), FLAG_ICC_FORCE_CONNECTION, 0 )
            ELSE
                  lSuccess := FALSE
            ENDIF
      ENDIF
 
      RETURN lSuccess
 
METHOD CloseRequest( ) CLASS msHTTP
      // Description: Закрытие запроса(ов)
      // Parameters : No
      // Returns    : NIL
 
      LOCAL lRet AS LOGIC
 
 
lRet := TRUE
 
      IF SELF:hRequest == NULL_PTR
            RETURN lRet
      ENDIF
 
// Удаляем запрос в крит. секции
      IF SELF:lStatus
            lRet := SELF:__DelStatus( SELF:hRequest )
      ENDIF
 
      IF lRet .AND. InternetCloseHandle( SELF:hRequest )
 
            SELF:hRequest := NULL_PTR
            RETURN TRUE
      ENDIF
 
      RETURN FALSE
 
METHOD CloseRemote( ) CLASS msHTTP // Pencil
      // Description: Закрытие соединения с сайтом
      // Parameters : No
      // Returns    : NIL
 
      LOCAL lRet AS LOGIC
 
 
// Закрываем запрос
      lRet := SELF:CloseRequest( )
 
      IF SELF:hConnect == NULL_PTR
            RETURN lRet
      ENDIF
 
// Убираем соединение в крит. секции
      IF SELF:lStatus
            lRet := SELF:__DelStatus( SELF:hConnect )
      ENDIF
 
      IF lRet .AND. InternetCloseHandle( SELF:hConnect )
 
            SELF:hConnect := NULL_PTR
            RETURN TRUE
      ENDIF
 
      RETURN FALSE
 
METHOD Close( ) CLASS msHTTP
      // Description: Закрытие интернет-сеанса (сессии)
      // Parameters : No
      // Returns    : NIL
 
 
      // Закрываем соединение
      IF .NOT. SELF:CloseRemote()
 
// !!! Если не закрылось - закрываем, как можем
 
            // Закрываем запрос
            IF .NOT. SELF:hRequest == NULL_PTR
 
                  IF SELF:lStatus
                        SELF:__DelStatus( SELF:hRequest )
                  ENDIF
                  InternetCloseHandle( SELF:hRequest )
                  SELF:hRequest := NULL_PTR
            ENDIF
 
            // Закрываем соединение
            IF .NOT. SELF:hConnect == NULL_PTR
 
                  IF SELF:lStatus
                        SELF:__DelStatus( SELF:hConnect )
                  ENDIF
                  InternetCloseHandle( SELF:hConnect )
                  SELF:hConnect := NULL_PTR
            ENDIF
      ENDIF
 
// Закрываем интернет
      IF .NOT. SELF:hSession == NULL_PTR
 
            IF SELF:lStatus
                  SELF:__DelStatus( SELF:hSession )
            ENDIF
            InternetCloseHandle( SELF:hSession )
            SELF:hSession := NULL_PTR
      ENDIF
 
// Чистим переменные
      SELF:cResponse               := NULL_STRING
      SELF:cResponseHeader    := NULL_STRING
      SELF:cCurDir                 := NULL_STRING
      SELF:cCurrentUrl        := NULL_STRING
 
      SELF:cHostAddress       := NULL_STRING
      SELF:cUserName               := NULL_STRING
      SELF:cPassWord               := NULL_STRING
      SELF:cError                  := NULL_STRING
      SELF:cAgent                  := NULL_STRING
      SELF:cProxy                  := NULL_STRING
      SELF:cProxyBypass       := NULL_STRING
 
      RETURN NIL
 
METHOD Axit( ) CLASS msHTTP
      // Description:
      // Parameters : No
      // Returns    : NIL
 
      SELF:Close()
 
      RETURN ( SUPER:Axit() )
 
 
Как открывать – мы знаем, как закрывать – написали. Осталось научиться «закачивать» файлы. Сколько уже можно издеваться над читателями? Придётся ещё немного попыхтеть, и переписать стандартный метод GetFile(). Подберём ему «костюм» и «туфли»:
 
METHOD GetFile( cRemoteFile, cNewFile, lFailIfExists ) CLASS msHTTP
      // Description: Получение (загрузка) указанного файла
      // Parameters :
      // Returns    : LOGIC
 
      LOCAL lRet AS LOGIC
 
      LOCAL cHead AS STRING
      LOCAL nFlags AS DWORD
      LOCAL cUrl AS STRING
 
      LOCAL hNewFile AS PTR
 
      LOCAL DIM abTemp[ MAX_SOCKBUFF ] AS BYTE
      LOCAL nSize AS DWORD
 
      LOCAL lFile AS LOGIC
 
 
      Default( @lFailIfExists,     FALSE )
      Default( @cRemoteFile,       NULL_STRING )
      Default( @cNewFile,                NULL_STRING )
 
      cRemoteFile := AllTrim( cRemoteFile )
      cNewFile    := AllTrim( cNewFile )
 
lRet := FALSE
 
      IF cRemoteFile == NULL_STRING
            RETURN lRet
      ENDIF
 
      IF cNewFile == NULL_STRING
            cNewFile := cRemoteFile
      ENDIF
 
      lFile := File( cNewFile )
 
// Если файл есть, а дописывать в него - нельзя
      IF lFile .AND. lFailIfExists
            RETURN lRet
      ENDIF
 
// Подготовка данных по открытию
      IF SELF:hSession == NULL_PTR
 
            IF .NOT. SELF:Open( NIL, SELF:cProxy, SELF:cProxyBypass )
                  RETURN lRet
            ENDIF
      ENDIF
 
      cUrl := SELF:__GetUrl( cRemoteFile )
      IF cUrl == NULL_STRING
            RETURN lRet
      ENDIF
 
      cHead       := HEADER_ACCEPT
      nFlags      := INTERNET_FLAG_DONT_CACHE + INTERNET_FLAG_EXISTING_CONNECT
 
// !!!
SELF:__SetStatusObject()
 
      SELF:hRequest := InternetOpenUrl( SELF:hSession, String2Psz( cUrl ), String2Psz( cHead ), SLen( cHead ), nFlags, SELF:__GetStatusContext() )
      IF .NOT. SELF:hRequest == NULL_PTR
 
SELF:__SetStatus( SELF:hRequest )
 
            // Создаём файл
            IF lFile
                  hNewFile := FxOpen( cNewFile, OF_READWRITE + OF_SHARE_EXCLUSIVE, NULL_STRING )
            ELSE
                  hNewFile := FCreate2( cNewFile, FC_ARCHIVED )
            ENDIF
 
            IF .NOT. hNewFile == INVALID_HANDLE_VALUE // F_ERROR
 
                  // Читаем и пишем в файл
                  DO WHILE ( lRet := InternetReadFile( SELF:hRequest, @abTemp[ 1 ], MAX_SOCKBUFF, @nSize ) )
 
                        IF nSize = 0
                              EXIT
                        ENDIF
 
                        FWrite3( hNewFile, @abTemp[ 1 ], nSize )
                  END DO
 
                  // Закрываем файл
                  FClose( hNewFile )
                  hNewFile := NULL_PTR
 
                  // Если файл плохой - удаляем
                  IF !lRet
                        FErase( cNewFile )
                  ENDIF
            ENDIF
 
// Освобождаем интернет-запрос
SELF:__DelStatus( SELF:hRequest )
 
            IF InternetCloseHandle( SELF:hRequest )
                  SELF:hRequest := NULL_PTR
            ENDIF
      ENDIF
 
SELF:__DelStatusObject()
 
      RETURN lRet
 
 
Скажу нетерпеливым испытателям: будьте внимательны. Мой код не идеален и не учитывает многих нюансов. Например, метод получения файлов не учитывает наличия такой возможности, как URL redirection. Т.е., если пользователь получит некую ссылку, которая на самом деле перенаправит его в другое место, где он получит следующую (возможно, правильную или очередную) – вышеуказанный метод GetFile() не сработает.
 
Говорят, такую проблему можно решить, просмотрев заголовок (см. метод GetResponseHeader). Ещё раз, будьте внимательны, и всё в ваших руках!
 
Пример «на закуску»:
 
      oHTTP := msHTTP{ }
 
      IF oHTTP:Open( ) // Открыли интернет-сессию
 
            IF oHTTP:ConnectRemote( "Test.Com" ) // Имя или IP-адрес сайта
 
                  cRemFile := "sites/default/files/Test.rar" // Здесь полный путь от "корня" сайта
                  cNewFile := "D:\Demo.rar"
                  IF oHTTP:GetFile( cRemFile, cNewFile )
 
                        ErrorBox{, "Получилось !"}:Show()
                  ELSE
                        ErrorBox{, "Ошибка !"}:Show()
                  ENDIF
            ELSE
                  ErrorBox{, "Ошибка !"}:Show()
            ENDIF
      ELSE
            ErrorBox{, "Ошибка !"}:Show()
      ENDIF
 
      oHTTP:Close()
 
 
 
   Удачи!
 
09.11.2014 г.      Карандаш. 
 

Комментарии

This is a very interesting subject that I would like to know more about. I would greatly appreciate an English translation of this page! I have tried the Google translation but it is very bad.

Forgive me for absence of the English version of the text! While I wrote this note, I didn't think that this subject will interest English-speaking readers. When I write for Russian-speaking readers, I use many words and the phrases clear only to Russians. Therefore, Google not always gives a good translation. I will try to translate this text in the next few days.
 
         Thanks!

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer