Nano Hash - криптовалюты, майнинг, программирование

Delphi System.net.HTTPClient прервал файловый поток после простоя диска

Это класс Delphi, основанный на System.net.HTTPClient с функцией для загрузки файла с URL-адреса и сохранения в месте назначения имени файла:

function Download(const ASrcUrl : string; const ADestFileName : string): Boolean;

Основная особенность - возможность приостанавливать и возобновлять частичную загрузку.

unit AcHTTPClient;

interface

uses
  System.Net.URLClient, System.net.HTTPClient;

type
  TAcHTTPProgress = procedure(const Sender: TObject; AStartPosition : Int64; AEndPosition: Int64; AContentLength: Int64; AReadCount: Int64; ATimeStart : Int64; ATime : Int64; var Abort: Boolean) of object;
  TAcHTTPClient = class
    private
      FOnProgress:     TAcHTTPProgress;
      FHTTPClient:     THTTPClient;
      FTimeStart:      cardinal;
      FCancelDownload: boolean;
      FStartPosition:  Int64;
      FEndPosition:    Int64;
      FContentLength:  Int64;
    private
      procedure   SetProxySettings(AProxySettings: TProxySettings);
      function    GetProxySettings : TProxySettings;
      procedure   OnReceiveDataEvent(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
    public
      constructor Create;
      destructor  Destroy; override;
      property    ProxySettings : TProxySettings read FProxySettings write SetProxySettings;
      property    OnProgress : TAcHTTPProgress read FOnProgress write FOnProgress;
      property    CancelDownload : boolean read FCancelDownload write FCancelDownload;
      function    Download(const ASrcUrl : string; const ADestFileName : string): Boolean;
  end;

implementation

uses
  System.Classes, System.SysUtils, Winapi.Windows;

constructor TAcHTTPClient.Create;
// -----------------------------------------------------------------------------
// Constructor
begin
  inherited Create;

  // create an THTTPClient
  FHTTPClient := THTTPClient.Create;
  FHTTPClient.OnReceiveData := OnReceiveDataEvent;

  // setting the timeouts
  FHTTPClient.ConnectionTimeout :=  5000;
  FHTTPClient.ResponseTimeout   := 15000;

  // initialize the class variables
  FCancelDownload := false;
  FOnProgress     := nil;
  FEndPosition    := -1;
  FStartPosition  := -1;
  FContentLength  := -1;
end;


destructor TAcHTTPClient.Destroy;
// -----------------------------------------------------------------------------
// Destructor
begin
  FHTTPClient.free;

  inherited Destroy;
end;


procedure TAcHTTPClient.SetProxySettings(AProxySettings: TProxySettings);
// -----------------------------------------------------------------------------
// Set FHTTPClient.ProxySettings with AProxySettings
begin
  FHTTPClient.ProxySettings := AProxySettings;
end;


function TAcHTTPClient.GetProxySettings : TProxySettings;
// -----------------------------------------------------------------------------
// Get FHTTPClient.ProxySettings
begin
  Result := FHTTPClient.ProxySettings;
end;


procedure TAcHTTPClient.OnReceiveDataEvent(const Sender: TObject; AContentLength: Int64; AReadCount: Int64; var Abort: Boolean);
// -----------------------------------------------------------------------------
// HTTPClient.OnReceiveDataEvent become OnProgress
begin
  Abort := CancelDownload;

  if Assigned(OnProgress) then
    OnProgress(Sender, FStartPosition, FEndPosition, AContentLength, AReadCount, FTimeStart, GetTickCount,  Abort);
end;


function TAcHTTPClient.Download(const ASrcUrl : string; const ADestFileName : string): Boolean;
// -----------------------------------------------------------------------------
// Download a file from ASrcUrl and store to ADestFileName
var
  aResponse:           IHTTPResponse;
  aFileStream:         TFileStream;
  aTempFilename:       string;
  aAcceptRanges:       boolean;
  aTempFilenameExists: boolean;
begin
  Result         := false;
  FEndPosition   := -1;
  FStartPosition := -1;
  FContentLength := -1;

  aResponse   := nil;
  aFileStream := nil;
  try
    // raise an exception if the file already exists on ADestFileName 
    if FileExists(ADestFileName) then
      raise Exception.Create(Format('the file %s alredy exists', [ADestFileName]));

    // reset the CancelDownload property
    CancelDownload := false;

    // set the time start of the download
    FTimeStart := GetTickCount;

    // until the download is incomplete the ADestFileName has *.parts extension 
    aTempFilename := ADestFileName + '.parts';

    // get the header from the server for aSrcUrl
    aResponse := FHTTPClient.Head(aSrcUrl);

    // checks if the response StatusCode is 2XX (aka OK) 
    if (aResponse.StatusCode < 200) or (aResponse.StatusCode > 299) then
      raise Exception.Create(Format('Server error %d: %s', [aResponse.StatusCode, aResponse.StatusText]));

    // checks if the server accept bytes ranges 
    aAcceptRanges := SameText(aResponse.HeaderValue['Accept-Ranges'], 'bytes');

    // get the content length (aka FileSize)
    FContentLength := aResponse.ContentLength;

    // checks if a "partial" download already exists
    aTempFilenameExists := FileExists(aTempFilename);

    // if a "partial" download already exists
    if aTempFilenameExists then
    begin
      // re-utilize the same file stream, with position on the end of the stream
      aFileStream := TFileStream.Create(aTempFilename, fmOpenWrite or fmShareDenyNone);
      aFileStream.Seek(0, TSeekOrigin.soEnd);
    end else begin
      // create a new file stream, with the position on the beginning of the stream
      aFileStream := TFileStream.Create(aTempFilename, fmCreate);
      aFileStream.Seek(0, TSeekOrigin.soBeginning);
    end;

    // if the server doesn't accept bytes ranges, always start to write at beginning of the stream
    if not(aAcceptRanges) then
      aFileStream.Seek(0, TSeekOrigin.soBeginning);

    // set the range of the request (from the stream position to server content length)
    FStartPosition := aFileStream.Position;
    FEndPosition   := FContentLength;

    // if the range is incomplete (the FStartPosition is less than FEndPosition)
    if (FEndPosition > 0) and (FStartPosition < FEndPosition) then
    begin
      // ... and if a starting point is present
      if FStartPosition > 0 then
      begin
        // makes a bytes range request from FStartPosition to FEndPosition
        aResponse := FHTTPClient.GetRange(aSrcUrl, FStartPosition, FEndPosition, aFileStream);
      end else begin
        // makes a canonical GET request
        aResponse := FHTTPClient.Get(aSrcUrl, aFileStream);
      end;

      // check if the response StatusCode is 2XX (aka OK) 
      if (aResponse.StatusCode < 200) or (aResponse.StatusCode > 299) then
        raise Exception.Create(Format('Server error %d: %s', [aResponse.StatusCode, aResponse.StatusText]));
    end;

    // if the FileStream.Size is equal to server ContentLength, the download is completed!
    if (aFileStream.Size > 0) and (aFileStream.Size = FContentLength) then begin

      // free the FileStream otherwise doesn't renames the "partial file" into the DestFileName
      FreeAndNil(aFileStream);

      // renames the aTempFilename file into the ADestFileName 
      Result := RenameFile(aTempFilename, ADestFileName);

      // What?
      if not(Result) then
        raise Exception.Create(Format('RenameFile from %s to %s: %s', [aTempFilename, ADestFileName, SysErrorMessage(GetLastError)]));
    end;
  finally
    if aFileStream <> nil then aFileStream.Free;
    aResponse := nil;
  end;
end;

end.

Это, например, форма (только для тестирования класса):

введите здесь описание изображения

unit WMain;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.StdCtrls,
  Vcl.ComCtrls,
  System.Math,
  AcHTTPClient,
  System.Net.URLClient;

type
  TWinMain = class(TForm)
    BtnDownload: TButton;
    EdSrcUrl: TEdit;
    EdDestFilename: TEdit;
    ProgressBar: TProgressBar;
    BtnSospendi: TButton;
    LblInfo: TLabel;
    procedure BtnDownloadClick(Sender: TObject);
    procedure BtnCancelClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private declarations }
    FAcHTTPClient: TAcHTTPClient;
    FLastProcess:  cardinal;
    procedure AcHTTPProgressEvent(const Sender: TObject; AStartPosition : Int64; AEndPosition: Int64; AContentLength: Int64; AReadCount: Int64; ATimeStart : Int64; ATime : Int64; var Abort: Boolean);
  public
    { Public declarations }
  end;

var
  WinMain: TWinMain;

implementation

{$R *.dfm}

procedure TWinMain.FormCreate(Sender: TObject);
begin
  FLastProcess  := GetTickCount;
  FAcHTTPClient := TAcHTTPClient.Create;

  FAcHTTPClient.OnProgress := AcHTTPProgressEvent;

  LblInfo.Caption      := '';
  ProgressBar.Max      := 0;
  ProgressBar.Position := 0;
end;

procedure TWinMain.FormDestroy(Sender: TObject);
begin
  FAcHTTPClient.Free;
end;

procedure TWinMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  FAcHTTPClient.CancelDownload := true;
end;

procedure TWinMain.BtnCancelClick(Sender: TObject);
begin
  FAcHTTPClient.CancelDownload := true;
end;


procedure TWinMain.AcHTTPProgressEvent(const Sender: TObject; AStartPosition : Int64; AEndPosition: Int64; AContentLength: Int64; AReadCount: Int64; ATimeStart : Int64; ATime : Int64; var Abort: Boolean);

  function ConvertBytes(Bytes: Int64): string;
  const
    Description: Array [0 .. 8] of string = ('Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
  var
    i: Integer;
  begin
    i := 0;

    while Bytes > Power(1024, i + 1) do
      Inc(i);

    Result := FormatFloat('###0.##', Bytes / Power(1024, i)) + #32 + Description[i];
  end;
var
  aSpeedBytesSec: Int64;
  aBytesToDwn:    Int64;
  aSecsDwn:       Int64;
  aSecsDwnLeft:   Int64;
  aCaption:       string;
begin
  aSpeedBytesSec := 0;
  aSecsDwnLeft   := 0;
  aCaption       := '';

  if (AReadCount > 0) and (ATime > 0) then
  begin
    aBytesToDwn := AContentLength - AReadCount;

    aSecsDwn := (ATime - ATimeStart) div 1000;

    if aSecsDwn > 0 then
      aSpeedBytesSec := AReadCount div aSecsDwn;

    if aSpeedBytesSec > 0 then
      aSecsDwnLeft := aBytesToDwn div aSpeedBytesSec;

    // size to download
    if AReadCount > 1024 then
      aCaption := aCaption + Format('%s/%s ', [ConvertBytes(AReadCount), ConvertBytes(AContentLength)]);

    if AEndPosition > AContentLength then
      aCaption := aCaption + Format('(final size on disk %s) ', [ConvertBytes(AEndPosition)]);

    // download speed
    if aSpeedBytesSec > 0 then
      aCaption := aCaption + Format('(%s/s) ', [ConvertBytes(aSpeedBytesSec)]);

    if aSecsDwn > 0 then
      aCaption := aCaption + Format('time passed %.2d:%.2d ', [aSecsDwn div 60, aSecsDwn mod 60]);

    if aSecsDwnLeft > 0 then
      aCaption := aCaption + Format('time left %.2d:%.2d ', [aSecsDwnLeft div 60, aSecsDwnLeft mod 60]);

    LblInfo.Caption := aCaption;

    ProgressBar.Max      := AEndPosition;
    ProgressBar.Position := AStartPosition + AReadCount;

    Application.ProcessMessages;
  end;
end;

procedure TWinMain.BtnDownloadClick(Sender: TObject);
begin
  // Enable away mode and prevent the sleep idle time-out
  SetThreadExecutionState(ES_CONTINUOUS or ES_SYSTEM_REQUIRED);
  try
    try
      if FAcHTTPClient.Download(EdSrcUrl.Text, EdDestFilename.Text) then
          ShowMessage('File downloaded!');
    except on E : Exception do
      ShowMessage(E.Message);
    end;
  finally
    // Clear EXECUTION_STATE flags to disable away mode 
    // and allow the system to idle to sleep normally.
    SetThreadExecutionState(ES_CONTINUOUS);
  end;
end;

end.

Я обнаружил первую проблему, когда Windows переходила в состояние ожидания / гибернации в середине загрузки, это нарушало поток файлов ... вероятно, потому, что Windows также простаивает диск.

Я частично решил проблему, заставив систему оставаться активной с помощью SetThreadExecutionState следующим образом:

procedure TWinMain.BtnDownloadClick(Sender: TObject);
begin
  // Enable away mode and prevent the sleep idle time-out
  SetThreadExecutionState(ES_CONTINUOUS or ES_SYSTEM_REQUIRED);
  try
    try
      if FAcHTTPClient.Download(EdSrcUrl.Text, EdDestFilename.Text) then
        ShowMessage('File downloaded!');
    except on E : Exception do
      ShowMessage(E.Message);
    end;
  finally
    // Clear EXECUTION_STATE flags to disable away mode 
    // and allow the system to idle to sleep normally.
    SetThreadExecutionState(ES_CONTINUOUS);
  end;
end;

однако иногда загруженный файл кажется поврежденным, и проблема все еще связана с возобновлением частичной загрузки после простоя диска.

предложения?

примечание стороны, я нахожусь на Delphi Berlin Update 2

19.05.2017

  • если вы можете возобновить работу и контролировать загрузку части файла, то вы можете повторно загрузить сломанную часть до того, как система перейдет в режим ожидания, а затем исправить ее исходным файлом с помощью потоков. 23.05.2017
  • Не помогло это stackoverflow.com/q/8733457/8041231? В вашем коде отсутствует один флаг ... 26.05.2017
  • Я не уверен, что понимаю, почему бы просто не перезапустить загрузку с последней записанной позиции файлового потока? все последние HTTP-серверы поддерживают возобновление загрузки 27.05.2017
  • Пользователь @loki уже использует GetRange. 28.05.2017

Новые материалы

Кластеризация: более глубокий взгляд
Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

Как написать эффективное резюме
Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

Частный метод Python: улучшение инкапсуляции и безопасности
Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

Как я автоматизирую тестирование с помощью Jest
Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..