Качаем с докачкой

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

В Японии скончался старейший пингвин в мире.

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;

interface

uses

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’) then

begin

AssignFile(f, ‘c:\123.tmp’);

Reset(f, 1);

RestartPos := FileSize(F);

Seek(F, FileSize(F));

end

else

begin

//иначе с начала

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 then

begin

ProgressBar1.Min := 0;

ProgressBar1.Max := fSize RestartPos;

ProgressBar1.Position := RestartPos;

end

else

begin

ProgressBar1.Min := 0;

ProgressBar1.Max := fSize RestartPos;

end;

//качаем до тех пор пока реально прочитаное число байт не

//будет равно нулю или не стор

while (ReadLen 0) and (stop = false) do

begin

//читаем в буфер

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.

{/codecitation}

Добавить комментарий