[РЕШЕНО] Загрузка файла с помощью Indy

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

[РЕШЕНО] Загрузка файла с помощью Indy

Сообщение ronin » 24.06.2011 21:54:16

Такой вопрос уже поднимал раньше тут, вот опять пришлось вернуться к нему. Проблема в следующем, идёт закачка файла с помощью idHTTP.Get в файловый поток TFileStream. Для поддержки докачки использую idHTTP.Request.Range. Проблема в том что если сразу писать в файловый поток, то файл куда качаем становится равен размеру файла источника (который скачиваем), причём сразу при старте закачки. При прерывании закачки файловый поток закрывается и файл получатель становится равен фактически скаченному объёму. При повторном запуске закачка продолжается с предыдущего места всё ОК, файл получатель опять становится равен файлу источнику, пока не будет закачан полностью.

Так вот, если происходит аварийное завершение программы, например перезагрузка, поток закачки не отрабатывает до конца и получаем на выходе файл равный размеру файла источника, но не докачанный фактически, так как закачка файла оборвана.

Программа под линукс, поэтому перехватить удаётся только сигнал SIGTERM, сигнал SIGKILL перехватить не получается, поэтому в некоторых случаях корректно отработать остановку потока закачки невозможно. Соответственно стоит задача: либо каким то образом корректно уничтожать объект файлового потока и тогда файл будет равняться фактически скаченному объёму, либо подумал сделать закачку в потоке порциями, чтобы файл получатель не был равен объёму файла источника, видимо это особенности поведения Indy и TFileStream.

Кто подскажет какие возможны варианты? Гуглить уже устал, вариантов нету пока никаких.

Вот отрывок кода что бы было понятно:

Код: Выделить всё

var
  rcvrdata: TFileStream; //файловый поток приёмник   
  url:UTF8String;
...
//задаём диапазон
HTTP.Request.Range:=IntToStr(StartSize)+'-'+IntToStr(FSize);
...
try
   HTTP.Get(url, rcvrdata);
except
...


Всё это дело происходит в отдельном потоке

Добавлено спустя 20 часов 50 минут 33 секунды:
попытался скачивать в TMemoryStream, а потом в idHTTP.OnWork скидывать порциями в TFileStream, но потом понял что бывает приходится скачивать файлы более 1 Гб, тут проблема с расходованием памяти, поэтому отказался от этого варианта

поковырялся в idHTTP.pas, отследил последовательность используемых процедур но так и не смог понять, хотя увидел что там тоже используется TMemoryStream, правда для каждой скачиваемой порции

для меня так и остаётся нерешённый вопрос - почему при скачивании в файловый поток idHTTP приравнивает размер файла получателя файлу источнику? а не качает в поток порциями... может кто знает где рыть?
Последний раз редактировалось ronin 26.06.2011 00:26:03, всего редактировалось 1 раз.
ronin
постоялец
 
Сообщения: 174
Зарегистрирован: 27.01.2010 00:14:46

Re: Загрузка файла с помощью Indy

Сообщение Brainenjii » 25.06.2011 18:58:44

Только если костыль - каждые n секунд сохранять последнее положения для скачиваемого файла и, в случае рестарта, - обрезать сохранённый файл с того положения ^_^
Аватара пользователя
Brainenjii
энтузиаст
 
Сообщения: 1351
Зарегистрирован: 10.05.2007 00:04:46

Re: Загрузка файла с помощью Indy

Сообщение ronin » 26.06.2011 00:23:42

Разобрался наконец :) Нашёл в модуле idIOHandler.pas следующую процедуру (отследил по цепочке)

Код: Выделить всё
procedure TIdIOHandler.ReadStream(AStream: TStream; AByteCount: Int64;
  AReadUntilDisconnect: Boolean);
var
  i: Integer;
  LBuf: TIdBytes;
  LWorkCount: Int64;
const
  cSizeUnknown=-1;
begin
  Assert(AStream<>nil);

  if (AByteCount = cSizeUnknown) and (not AReadUntilDisconnect) then begin
    // Read size from connection
    if LargeStream then begin
      AByteCount := ReadInt64;
    end else begin
    AByteCount := ReadLongInt;
    end;
  end;

  // Presize stream if we know the size - this reduces memory/disk allocations to one time
  // Have an option for this? user might not want to presize, eg for int64 files
  if AByteCount > -1 then begin
    AdjustStreamSize(AStream, AStream.Position + AByteCount);
  end;

  if AReadUntilDisconnect then begin
    LWorkCount := High(LWorkCount);
    BeginWork(wmRead);
  end else begin
    LWorkCount := AByteCount;
    BeginWork(wmRead, LWorkCount);
  end;

  try
    // If data already exists in the buffer, write it out first.
    // should this loop for all data in buffer up to workcount? not just one block?
    if FInputBuffer.Size > 0 then begin
      i := IndyMin(FInputBuffer.Size, LWorkCount);
      FInputBuffer.ExtractToStream(AStream, i);
      Dec(LWorkCount, i);
    end;

    // RLebeau - don't call Connected() here!  ReadBytes() already
    // does that internally. Calling Connected() here can cause an
    // EIdConnClosedGracefully exception that breaks the loop
    // prematurely and thus leave unread bytes in the InputBuffer.
    // Let the loop catch the exception before exiting...
    while {Connected and} (LWorkCount > 0) do begin
      i := IndyMin(LWorkCount, RecvBufferSize);
      //TODO: Improve this - dont like the use of the exception handler
      //DONE -oAPR: Dont use a string, use a memory buffer or better yet the buffer itself.
      try
        try
          SetLength(LBuf, 0); // clear the buffer
          ReadBytes(LBuf, i, False);
          TIdAntiFreezeBase.DoProcess;
        except
          on E: Exception do begin
            // RLebeau - ReadFromSource() inside of ReadBytes()
            // could have filled the InputBuffer with more bytes
            // than actually requested, so don't extract too
            // many bytes here...
            i := IndyMin(i, FInputBuffer.Size);
            FInputBuffer.ExtractToBytes(LBuf, i);
            if (E is EIdConnClosedGracefully) and AReadUntilDisconnect then begin
              Break;
            end else begin
              raise;
            end;
          end;
        end;
      finally
        if i > 0 then begin
          TIdStreamHelper.Write(AStream, LBuf, i);
          Dec(LWorkCount, i);
        end;
      end;
    end;
  finally
    EndWork(wmRead);
    if AStream.Size > AStream.Position then begin
      AStream.Size := AStream.Position;
    end;
    LBuf := nil;
  end;
end;


в следующем куске читаем комментарий :)

Код: Выделить всё
  // Presize stream if we know the size - this reduces memory/disk allocations to one time
  // Have an option for this? user might not want to presize, eg for int64 files
  if AByteCount > -1 then begin
    AdjustStreamSize(AStream, AStream.Position + AByteCount);
  end;


надеюсь далее понятно, комментируем блок

Код: Выделить всё
if AByteCount > -1 then begin
    AdjustStreamSize(AStream, AStream.Position + AByteCount);
  end;


и всё, теперь не происходит резервирования всего объёма под поток, а запись идёт кусками по мере скачивания
ronin
постоялец
 
Сообщения: 174
Зарегистрирован: 27.01.2010 00:14:46


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: Yandex [Bot] и гости: 6

Рейтинг@Mail.ru