Подтвердить что ты не робот

Загрузка файла с использованием TIdHttp

Я хочу реализовать простой HTTP-загрузчик, используя TIdHttp (Indy10). Я нашел два примера кода из Интернета. К сожалению, ни один из них не удовлетворяет мне 100%. Вот код, и я хочу посоветовать.


Вариант 1

var
  Buffer: TFileStream;
  HttpClient: TIdHttp;
begin
  Buffer := TFileStream.Create('somefile.exe', fmCreate or fmShareDenyWrite);
  try
    HttpClient := TIdHttp.Create(nil);
    try
      HttpClient.Get('http://somewhere.com/somefile.exe', Buffer); // wait until it is done
    finally
      HttpClient.Free;
    end;
  finally
    Buffer.Free;
  end;
end;

Код компактный и очень простой для понимания. Проблема в том, что при загрузке он выделяет дисковое пространство. Другая проблема заключается в том, что мы не можем показывать ход загрузки в графическом интерфейсе напрямую, если только код не выполняется в фоновом потоке (в качестве альтернативы мы можем связать событие HttpClient.OnWork).


Вариант 2:

const
  RECV_BUFFER_SIZE = 32768;
var
  HttpClient: TIdHttp;
  FileSize: Int64;
  Buffer: TMemoryStream;
begin
  HttpClient := TIdHttp.Create(nil);
  try
    HttpClient.Head('http://somewhere.com/somefile.exe');
    FileSize := HttpClient.Response.ContentLength;

    Buffer := TMemoryStream.Create;
    try
      while Buffer.Size < FileSize do
      begin
        HttpClient.Request.ContentRangeStart := Buffer.Size;
        if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
          HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1
        else
          HttpClient.Request.ContentRangeEnd := FileSize;

        HttpClient.Get(HttpClient.URL.URI, Buffer); // wait until it is done
        Buffer.SaveToFile('somefile.exe');
      end;
    finally
      Buffer.Free;
    end;
  finally
    HttpClient.Free;
  end;
end;

Сначала мы запрашиваем размер файла с сервера, а затем загружаем содержимое файла в куски. Сохраняемое содержимое файла будет сохранено на диске, когда они будут получены полностью. Потенциальная проблема заключается в том, что нам нужно отправить несколько запросов GET на сервер. Я не уверен, что некоторые серверы (например, megaupload) могут ограничить количество запросов в течение определенного периода времени.


Мои ожидания

  • Загружающий должен отправить на сервер только один запрос GET.
  • Дисковое пространство не должно выделяться при загрузке.

Любые намеки приветствуются.

4b9b3361

Ответ 1

Вариант № 1 является самым простым, и как предполагается использовать Indy.

Что касается проблемы выделения диска, вы можете получить новый класс из TFileStream и переопределить его метод SetSize(), чтобы ничего не делать. TIdHTTP будет по-прежнему пытаться предварительно выделить файл, если это необходимо, но на самом деле он не будет выделять какое-либо дисковое пространство. Запись в TFileStream будет увеличивать файл по мере необходимости.

Что касается отчетов о состоянии, TIdHTTP имеет OnWork... события для этой цели. Параметр AWorkCountMax OnWorkBegin будет фактическим размером файла, если он известен (ответ не помечен), или 0, если неизвестно. Параметр AWorkCount события OnWork будет суммарным количеством байтов, которые были переданы до сих пор. Если размер файла известен, вы можете отобразить общий процент, просто разделив AWorkCount на AWorkCountMax и умножив на 100, иначе просто отобразите значение AWorkCount самостоятельно. Если вы хотите отобразить скорость передачи, вы можете вычислить это из разницы значений AWorkCount и временных интервалов между несколькими событиями OnWork.

Попробуйте следующее:

type
  TNoPresizeFileStream = class(TFileStream)
  procedure
    procedure SetSize(const NewSize: Int64); override;
  end;

procedure TNoPresizeFileStream.SetSize(const NewSize: Int64);
begin
end;

.

type
  TSomeClass = class(TSomething)
  ...
    TotalBytes: In64;
    LastWorkCount: Int64;
    LastTicks: LongWord;
    procedure Download;
    procedure HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
    procedure HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
    procedure HttpWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
  ...
  end;

procedure TSomeClass.Download;
var
  Buffer: TNoPresizeFileStream;
  HttpClient: TIdHttp;
begin
  Buffer := TNoPresizeFileStream.Create('somefile.exe', fmCreate or fmShareDenyWrite);
  try
    HttpClient := TIdHttp.Create(nil);
    try
      HttpClient.OnWorkBegin := HttpWorkBegin;
      HttpClient.OnWork := HttpWork;
      HttpClient.OnWorkEnd := HttpWorkEnd;

      HttpClient.Get('http://somewhere.com/somefile.exe', Buffer); // wait until it is done
    finally
      HttpClient.Free;
    end;
  finally
    Buffer.Free;
  end;
end;

procedure TSomeClass.HttpWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
  if AWorkMode <> wmRead then Exit;

  // initialize the status UI as needed...
  //
  // If TIdHTTP is running in the main thread, update your UI
  // components directly as needed and then call the Form's
  // Update() method to perform a repaint, or Application.ProcessMessages()
  // to process other UI operations, like button presses (for
  // cancelling the download, for instance).
  //
  // If TIdHTTP is running in a worker thread, use the TIdNotify
  // or TIdSync class to update the UI components as needed, and
  // let the OS dispatch repaints and other messages normally...

  TotalBytes := AWorkCountMax;
  LastWorkCount := 0;
  LastTicks := Ticks;
end;

procedure TSomeClass.HttpWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
var
  PercentDone: Integer;
  ElapsedMS: LongWord;
  BytesTransferred: Int64;
  BytesPerSec: Int64;
begin
  if AWorkMode <> wmRead then Exit;

  ElapsedMS := GetTickDiff(LastTicks, Ticks);
  if ElapsedMS = 0 then ElapsedMS := 1; // avoid EDivByZero error

  if TotalBytes > 0 then
    PercentDone := (Double(AWorkCount) / TotalBytes) * 100.0;
  else
    PercentDone := 0.0;

  BytesTransferred := AWorkCount - LastWorkCount;

  // using just BytesTransferred and ElapsedMS, you can calculate
  // all kinds of speed stats - b/kb/mb/gm per sec/min/hr/day ...
  BytesPerSec := (Double(BytesTransferred) * 1000) / ElapsedMS;

  // update the status UI as needed...

  LastWorkCount := AWorkCount;
  LastTicks := Ticks;
end;

procedure TSomeClass.HttpWorkEnd(ASender: TObject; AWorkMode: TWorkMode);
begin
  if AWorkMode <> wmRead then Exit;

  // finalize the status UI as needed...
end;

Ответ 2

Вот пример, показывающий, как использовать компоненты OnWork для отображения индикатора выполнения:

Загрузите файл из Интернета программно с событием Progress с использованием Delphi и Indy

Вам не стоит беспокоиться о распределении диска. Место на диске, которое выделено, на самом деле не записано, поэтому оно не повредит ваши диски. Будьте счастливы, что он выделен так, что невозможно, чтобы другой процесс требовал дискового пространства и позволял вам бежать из космоса!

Ответ 3

Не забудьте добавить это для варианта 2

 : Else HttpClient.Request.ContentRangeEnd := FileSize;

Заменить

   if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
  HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1;

Через

   if Buffer.Size + RECV_BUFFER_SIZE < FileSize then
  HttpClient.Request.ContentRangeEnd := Buffer.Size + RECV_BUFFER_SIZE - 1;
   Else HttpClient.Request.ContentRangeEnd := FileSize;