#include <iostream> ¦include <windows.h> ¦include <wininet.h> ¦include <fstream> ¦include <cstdio> using namespace std; const int MAX_EREMSG_SIZE = 80; const int MAX_FILENAME_SIZE = 512; Const int BUF_SIZE = 1024; 11 Класс исключений для ошибок загрузки, class DLExc { Char err [ MAX_ERRMSG_S IZE] ; Public: DLExc(char *exc) { if(strlen(exc) < MAX_ERRMSG_SIZE) strcpy(err, exc); } // Возвращает указатель на сообщение об ошибке, const char * geterr() return err; } }; // Класс для загрузки файлов из Интернета, class Download { static bool ishttp(char *url); static bool- httpverOK(HINTERNET hlurl); static bool getfname(char *url, char *fname); static unsigned long openfile(char *url, bool reload, ofstream &fout); public: static bool download(char *url, bool restart=false, void ("update)(unsigned long, unsigned long)=NULL); }; // Загружает файл. // // Передает URL файла параметру url. // // Для дозагрузки файла передает true параметру reload. // // Для определения функции update, которая вызывается после того, // как очередной раз считан буфер, переда'ет указатель //на эту.функцию как третий параметр. Если функция update //не нужна, оставляет текущее значение // третьего параметра, равное null. bool Download::download(char *url, bool reload, void (*update)(unsigned long, unsigned long)) { ofstream fout; // выходной поток unsigned char buf[BUF_SIZE]; // входной буфер unsigned long numrcved; // количество считанных байтов unsigned long filelen; // длина файла на диске HINTERNET hlurl, hlnet; // интернет-дескрипторы unsigned long contentlen;// длина контента unsigned long len; // длина contentlen unsigned long total =0; // накапливаемый итог полученных байтов char header[80]; //'содержит заголовок Range try { if(lishttp(url)) throw DLExcC'Must be HTTP url."); // Открывает файл, заданный в url. // Открытый поток будет возвращаться //в fout. Если reload равна true, данные //в любом загружавшемся прежде файле будут удалены. // Возвращается длина любого загружавшегося ранее файла // (после возможного удаления данных). filelen = openfile(url, reload, fout); // Проверяет, доступно ли интернет-соединение, if(InternetAttemptConnect(O) != ERROR_SUCCESS) throw DLExc("Can11 connect."); // Открывает интернет-соединение. hlnet = InternetOpen("downloader", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); if(hlnet == NULL) throw DLExc("Can't open connection."); // Конструирует заголовок запрашиваемого интервала данных, sprintf(header, "Range:bytes=%d-", filelen); // Открывает URL и запрашивает диапазон, hlurl = InternetOpenUrl(hlnet, url, header, -1, INTERNET_FLAG_NO_CACHE_WRITE, 0); if(hlurl == NULL) throw DLExc("Can't open url."); // Подтверждает, что поддерживается протокол HTTP/1.1 // или более поздние его версии, if(JhttpverOK(hlurl)) throw DLExc("HTTP/1.1 not supported."); // Получает длину контента, len = sizeof contentlen; if(!HttpQuerylnfо(hlurl, HTTP_QUERY_CONTENT_JJENGTH I HTTP_QUERY_FLAG_NUMBER, &contentlen, &len, NULL)) throw DLExc("File or content length not found."); // Если существующий файл (при условии, что он есть) не завершен, // завершает его загрузку. if(filelen != contentlen && contentlen) do { // Читает буфер of info. if(!InternetReadFile(hlurl, &buf, BUF_SIZE, &numrcved)) throw DLExc("Error occurred during download."); // Записывает буфер на диск. fout.write((const char *) buf, numrcved); if(!fout.good()) throw DLExc("Error writing file."); total += numrcved; // обновляет накапливаемый итог // Вызывает функцию update, если она задана, if(update && numrcved > 0) update(contentlen+filelen, total+filelen); } while(numrcved > 0); else ' if(update) update(filelen, filelen); catch(DLExc) { } fout.close(); InternetCloseHandle(hlurl); internetCloseHandle(hlnet); throw; // повторно генерирует исключение для использования // вызывающей программой (caller) } fout.close(); InternetCloseHandle(hlurl); InternetCloseHandle(hlnet); return true; } // Возвращает true, если у протокола HTTP версия 1.1 или более поздняя, bool Download::httpverOK(HINTERNET hlurl) { char str[80]; unsigned long len = 79; // Получает версию протокола HTTP. if (IHttpQueryInfo(hlurl, HTTP_QUERY_VERSION, &str, &len, NULL)) return false; // Сначала проверяет главный номер версии HTTP. char *р = strchr(str, '/*); Р++; if(*p == '0') return false; // can use HTTP 0.x / / Теперь находит начало младшего номера версии HTTP. Р = strchr(str, '.'); Р++; // Преобразует в тип int. int minorVerNum = atoi (p) ; if (minorVerNum > 0) return true; return false; // Выделяет имя файла из URL. Возвращает false, если // имя файла не может быть найдено. bool Download::getfname(char *url, char *fname) { // Находит последний слэш (/). char *p = strrchr(url, '/'); // Копирует имя файла после последнего знака /. if(р && (strlen(p) < MAX_FILENAME_SIZE)) { р++; strcpy(fname, р); return true,- } else return false; } // Открывает файл вывода, инициализирует поток // вывода и возвращает длину файла. Если // reload равна true, сначала укорачивает до нуля любой // уже существующий файл. unsigned long Download::openfile(char *url, bool reload, ofstream &fout) { char fname[MAX_FILENAME_SIZE]; i f(!getfname (url, fname)) throw DLExc("File name error."); if(!reload) fout.open(fname, ios::binary | ios::out | ios::app | ios::ate); else fout.open(fname, ios:: binary | ios::out | ios::trunc); r if(!fout) throw DLExc("Can't open output file."); // Получает текущую длину файла. return fout.tellp(); ) // Подтверждает, что в URL задан HTTP, tool Download::ishttp(char *url) { char str[5] = ""; // Получает первые 4 символа из URL. strncpy(str, url, 4); // Преобразует их в нижний регистр. for(char *p=str; *р,- р++) *р = tolower(*p); return !strcmp("http", str); } В листинге 5.1 определены два класса. Первый, DLEXC, — класс, в котором инкапсулированы все исключения, генерируемые загрузчиком. Конструктор класса DLEXC получает указатель на строку, описывающую исключение, и сохраняет ее. Указатель на сообщение об ошибке получается с помощью вызова функции-члена класса geterr (). Второй класс — Download — обеспечивает загрузку файлов из Интернета. Обратите внимание на то, что этот класс состоит только из статических (static) функций. Следовательно, создание этого класса — скорее организационный прием, чем средство для инкапсуляции. Действительно, в данном случае можно было обойтись пространством имен вместо описания класса. Однако применение класса позволяет объявить некоторые функции со спецификатором доступа private, тем самым препятствуя их использованию в другом коде, и облегчает включение новых функциональных возможностей в дальнейшем. В следующих разделах подробно обсуждается класс Download.
|