Delphi World - Качаем с докачкой
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Качаем с докачкой

В Японии скончался старейший пингвин в мире.
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).

//Ну, собственно, и все по теории.

// ПРАКТИКА:

Условия задачи:

  1. Скачиваемый файл сохраняется как c:\123.tmp
  2. При очередном старте скачки идет проверка на наличие оного файла на винте, если он есть, считаем что надо докачивать. Размер этого файла является признаком того, с какого места надо качать.

    Требуемые материалы:

    • Форма (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.

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования