В Японии скончался старейший пингвин в мире.
Linux объявил 3-х дневный траур...
// ПРЕДИСЛОВИЕ:{
Копаясь как-то в исходниках модулей третьей Delphi, я наткнулся на файл,
который назывался WinInet.pas. Имея врожденное любопытство, я заглянул
в него и нашел там очень много интересных вещей. О некоторых из них я
попытаюсь рассказать в данной статье, в частности, как, используя этот
модуль, организовать докачку файлов при обрыве связи. В модуле WinInet.pas
содержатся описания прототипов функций и некоторых типов входящих в т.н.
Microsoft Windows Internet Extensions, описания которых я не нашел в
справочной системе (хотя может плохо искал) :-(. Поэтому пришлось идти
почти вслепую.
}// ТЕОРИЯ:{
Для начала рассмотрим все функции, константы и типы, которые мы будем
использовать:
}// 1) HINTERNET, вот как он описан:type
HINTERNET = Pointer;
PHINTERNET = ^HINTERNET;
// При детальном рассмотрении, это обычный указатель.// 2) функции InternetOpen и InternetCloseHandle:function InternetOpen(lpszAgent: PChar; dwAccessType: DWORD;
lpszProxy, lpszProxyBypass: PChar; dwFlags: DWORD): HINTERNET; stdcall;
{
где:
lpszAgent <-|Имя программы, с помощью которой мы соединяемся,
|может принимать любые значения
dwAccessType <-|Каким макаром соединяться с и-нетом
|принимаемые значения:
| PRE_CONFIG_INTERNET_ACCESS -как в системном реестре
| LOCAL_INTERNET_ACCESS -напрямую
| GATEWAY_INTERNET_ACCESS -через GateWay
| CERN_PROXY_INTERNET_ACCESS -через проксю
lpszProxy <-|Имя прокси сервера (ставим в nil)
lpszProxyBypass<-|Не уверен, но смахивает на имена хостов, для которых не
|использовать проксю (ставим в nil)
dwFlags <-|Принимаеемые значения:
| INTERNET_FLAG_ASYNC -этот запрос асинхронный (если есть
| поддержка), но мы поставим 0
}// возвращает пресловутый HINTERNET, который будет требоваться при вызове// всех остальных функций. С вызова этой функции начинается вся наша работа// с интернетом, а с вызова второй заканчивается.function InternetCloseHandle(hInet: HINTERNET): BOOL; stdcall;
// где: nInet ранее созданый указатель.// 3) функция InternetOpenUrl:function InternetOpenUrl(hInet: HINTERNET; lpszUrl: PChar;
lpszHeaders: PChar; dwHeadersLength: DWORD; dwFlags: DWORD;
dwContext: DWORD): HINTERNET; stdcall;
{
где:
hInet <-|Ранее созданый указатель
lpszUrl <-|Сам УРЛ
lpszHeaders <-|Дополнительные строки в НТТР запрос
dwHeadersLength<-|Длинна предыдущего
dwFlags <-|Принимаемые значения:
| INTERNET_FLAG_RAW_DATA -принимать как RAW данные
| INTERNET_FLAG_EXISTING_CONNECT -не создавать для
| объекта нового соединения
| (поставим в 0)
dwContext <-|пока не знаю, ставим в 0
}// Функция возвращает HINTERNET, указывающий на конкретный файл (далее он в// параметрах функций будет называться hFile).// 4) функция InternetReadFile:function InternetReadFile(hFile: HINTERNET; lpBuffer: Pointer;
dwNumberOfBytesToRead: DWORD; var lpdwNumberOfBytesRead: DWORD): BOOL; stdcall;
{
где:
hFile <-|Указатель, созданый предыдущей функцией
lpBuffer <-|Указатель на буфер куда читать
dwNumberOfBytesToRead<-|Сколько максимум читать (можно сказать размер
| буфера, хотя не факт)
lpdwNumberOfBytesRead<-|Сколько реально прочитано байт
}// Этой функой мы будем читать файл из и-нета.// 5) функция InternetSetFilePointer:function InternetSetFilePointer(hFile: HINTERNET;
lDistanceToMove: Longint; pReserved: Pointer;
dwMoveMethod, dwContext: DWORD): DWORD; stdcall;
{
где:
hFile <-|Указатель созданый функцией InternetOpenUrl
lDistanceToMove<-|На сколько байт смещать указатель
pReserved <-|??
dwMoveMethod <-|Как смещать (=0)
dwContext <-|??
}// Собственно, эта функция и поможет нам организовать докачку. Она смещает// указатель в файле, после чего передача файла начнется с этого места.// В принципе этих данных уже достаточно для наших целей, но есть еще одна// полезная функция, которая пригодится нам:function InternetQueryDataAvailable(hFile: HINTERNET; var lpdwNumberOfBytesAvailable: DWORD;
dwFlags, dwContext: DWORD): BOOL; stdcall;
{
где:
hFile <-|Указатель, созданный функцией InternetOpenUrl
lpdwNumberOfBytesAvailable<-|Сколько осталось байт
dwFlags <-|??
dwContext <-|??
}// Как вы уже догадались, с помощью этой функции можно узнать сколько// осталось байт скачать (или размер файла, если вызвать ее сразу после// InternetOpenUrl).//Ну, собственно, и все по теории.// ПРАКТИКА:
Условия задачи:
Скачиваемый файл сохраняется как c:\123.tmp
При очередном старте скачки идет проверка на наличие оного файла на винте, если он есть, считаем что надо докачивать. Размер этого файла является признаком того, с какого места надо качать.
Требуемые материалы:
Форма (TForm)-1 шт.
Кнопки (TButton)-2 шт.
Строка ввода (TEdit)-1 шт.
Progress bar для красоты (TProgressBar)-1 шт.
Метки (TLabel)-по необходимости.
Далее идет полный листинг модуля:
unit Unit1;
interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
wininet,
StdCtrls, ComCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit; //<-строка для УРЛа
Label1: TLabel;
Button1: TButton; //<-кнопка Start
Button2: TButton; //<-кнопка Stop
ProgressBar1: TProgressBar; //<-декорацияprocedure Button1Click(Sender: TObject); //<-|процедура начала скачкиprocedure Button2Click(Sender: TObject); //<-|принудительный обрывprocedure FormCreate(Sender: TObject);
private{ Private declarations }public{ Public declarations }end;
var
Form1: TForm1;
stop: boolean; //<-|вспомогательная переменная отв. за// |остановку скачкиimplementation{$R *.DFM}procedure TForm1.Button1Click(Sender: TObject);
var
hInet, //<-переменная сод. указатель на сессию
hURL: HINTERNET; //<-указатель на URL
fSize, //<-размер файла
ReadLen, //<-количество реально прочитанных байт
RestartPos: DWORD; //<-|позиция с которой начинается// |докачка
fBuf: array[1..1024] of byte; //<-буфер куда качаем
f: file; //<-файл куда качаем
Header: string; //<-|дополнительная переменная в HTTP// |заголовокbegin
RestartPos := 0; //<- |инициализация
fSize := 0; //<- |переменных
Button1.Enabled := false;
Button2.Enabled := true;
//Если на винте есть файл то считаем, что нужно докачиватьif FileExists('c:\123.tmp') thenbegin
AssignFile(f, 'c:\123.tmp');
Reset(f, 1);
RestartPos := FileSize(F);
Seek(F, FileSize(F));
endelsebegin//иначе с начала
AssignFile(f, 'c:\123.tmp');
ReWrite(f, 1);
end;
//открываем сессию
hInet := InternetOpen('Mozilla',
PRE_CONFIG_INTERNET_ACCESS,
nil,
nil,
0);
//Пишем дополнительную строку для заголовка
Header := 'Accept: */*';
//открываем URL
hURL := InternetOpenURL(hInet,
PChar(Edit1.Text),
pchar(Header),
StrLen(pchar(Header)),
0,
0);
//устанавливаем позицию в файле для докачкиif RestartPos > 0 then
InternetSetFilePointer(hURL,
RestartPos,
nil,
0,
0);
//смотрим ск-ко надо скачать
InternetQueryDataAvailable(hURL, fSize, 0, 0);
if RestartPos > 0 thenbegin
ProgressBar1.Min := 0;
ProgressBar1.Max := fSize + RestartPos;
ProgressBar1.Position := RestartPos;
endelsebegin
ProgressBar1.Min := 0;
ProgressBar1.Max := fSize + RestartPos;
end;
//качаем до тех пор пока реально прочитаное число байт не//будет равно нулю или не сторwhile (ReadLen <> 0) and (stop = false) dobegin//читаем в буфер
InternetReadFile(hURL, @fBuf, SizeOf(fBuf), ReadLen);
//смотрим ск-ко осталось докачать
InternetQueryDataAvailable(hURL, fSize, 0, 0);
ProgressBar1.Position := ProgressBar1.Max - fSize;
BlockWrite(f, fBuf, ReadLen); //<-пишем в файл
Application.ProcessMessages;
end;
stop := false;
Button1.Enabled := true;
Button2.Enabled := false;
InternetCloseHandle(hURL); //<-|закрываем
InternetCloseHandle(hInet); //<-|сесcии
CloseFile(f); //<-|и файлend;
procedure TForm1.FormCreate(Sender: TObject);
begin
stop := false; //<-прервать скачку
Button2.Enabled := false; //<-кнопка останова скачкиend;
procedure TForm1.Button2Click(Sender: TObject);
begin
stop := true; //<-сообщаем о необходимости прерывания скачкиend;
end.
Перевод на русский язык:
Похоже, вы пытаетесь реализовать программу, которая может продолжать загрузку файла с места, где она была прервана или остановлена. Вы выбрали использовать библиотеку WinInet, которая является частью Windows API.
Код, который вы предоставили, кажется почти корректным, но есть несколько вопросов и предложений, которые я хотел бы поделиться:
В процедуре Button1Click вы используете функцию FileExists для проверки существования файла на локальном компьютере. Однако, эта функция возвращает булевое значение, указывающее, существует ли файл или нет. Вы присваиваете это значение переменной RestartPos, которая используется позднее в коде. Вместо этого вы должны сравнить результат FileExists для определения существования файла или не.
В той же процедуре вы используете функцию InternetSetFilePointer для установки позиции указателя файла на правильное местоположение для продолжения загрузки. Однако, эта функция возвращает DWORD-значение, указывающее результат операции. Вы не проверяете возвращаемое значение в вашем коде, что может привести к проблемам, если функция fails.
В процедуре FormCreate вы присваиваете переменной stop значение false, но не ясно, почему вам нужно это поле. Если вы пытаетесь отслеживать, должна ли загрузка быть остановлена или нет, то вы можете рассмотреть другой подход.
В процедуре Button2Click вы присваиваете переменной stop значение true. Однако, неясно, как эта переменная используется в вашем коде. Вы можете рассмотреть более явный механизм для остановки загрузки, например, вызов InternetCloseHandle для закрытия файла-указателя.
Вот обновленный вариант вашего кода, который решает эти вопросы и предложения:
Обновленный код должен помочь вам достичь вашей цели продолжать загрузки файлов с места, где они были прерваны.
В статье описывается как использовать модуль WinInet.pas для организации докачки файлов при обрыве связи в программе на языке Delphi, создавая сессию интернета, открывая URL и управляя процессом скачивания файла.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.