Inno Setup torrent download implementation

2019-01-26 01:22发布

I am currently using Inno Download Plugin to download files for my installer, the biggest problem with this is that it faila to download the file correctly. Because of many reasons like bad connection. I would like to add an alternative method to download files, so the user may chose if he want regular way, or a torrent way. I know that I can use aria2c.exe app (https://aria2.github.io/), can someone help me with implementing it to the code of inno setup?

What I need is to download a 7z file using torrent (aria2.exe) and then unpack the contents to defined folder in {{app}} location.

Good code example is probably all I need.

1条回答
一夜七次
2楼-- · 2019-01-26 01:50

Just run the aria2c, redirect its output to a file and poll the file contents for progress of the download.

It's actually very similar to my solution for this answer:
Inno Setup - Make Inno Setup Installer report its installation progress status to master installer


#define TorrentMagnet "magnet:..."

[Files]
Source: aria2c.exe; Flags: dontcopy
Source: InnoCallback.dll; Flags: dontcopy

[Code]

function BufferToAnsi(const Buffer: string): AnsiString;
var
  W: Word;
  I: Integer;
begin
  SetLength(Result, Length(Buffer) * 2);
  for I := 1 to Length(Buffer) do
  begin
    W := Ord(Buffer[I]);
    Result[(I * 2)] := Chr(W shr 8); { high byte }
    Result[(I * 2) - 1] := Chr(Byte(W)); { low byte }
  end;
end;

type
  TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);

function SetTimer(Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
  external 'SetTimer@user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
  external 'KillTimer@user32.dll stdcall';

function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
  external 'wrapcallback@files:innocallback.dll stdcall';

var
  ProgressPage: TOutputProgressWizardPage;
  ProgressFileName: string;

procedure UpdateProgressProc(H: LongWord; Msg: LongWord; Event: LongWord; Time: LongWord);
var
  S: AnsiString;
  I: Integer;
  L: Integer;
  P: Integer;
  Max: Integer;
  Progress: string;
  Buffer: string;
  Stream: TFileStream;
  Transferred: string;
  Percent: Integer;
  Found: Boolean;
begin
  Found := False;
  try
    { Need shared read as the output file is locked for writting, }
    { so we cannot use LoadStringFromFile }
    Stream := TFileStream.Create(ProgressFileName, fmOpenRead or fmShareDenyNone);
    try
      L := Stream.Size;
      Max := 100*2014;
      if L > Max then
      begin
        Stream.Position := L - Max;
        L := Max;
      end;
      SetLength(Buffer, (L div 2) + (L mod 2));
      Stream.ReadBuffer(Buffer, L);
      S := BufferToAnsi(Buffer);
    finally
      Stream.Free;
    end;

    if S = '' then
    begin
      Log(Format('Progress file %s is empty', [ProgressFileName]));
    end;
  except
    Log(Format('Failed to read progress from file %s', [ProgressFileName]));
  end;

  if S <> '' then
  begin
    P := Pos('[#', S);
    if P = 0 then
    begin
      Log('Not found any progress line');
    end
      else
    begin
      repeat
        Delete(S, 1, P - 1);
        P := Pos(']', S);
        Progress := Copy(S, 2, P - 2);
        Delete(S, 1, P);
        P := Pos('[#', S);
      until (P = 0);

      Log(Format('Found progress line: %s', [Progress]));
      P := Pos(' ', Progress);
      if P > 0 then
      begin
        Log('A');
        Delete(Progress, 1, P);
        P := Pos('(', Progress);
        if P > 0 then
        begin
          Log('b');
          Transferred := Copy(Progress, 1, P - 1);
          Delete(Progress, 1, P);
          P := Pos('%)', Progress);
          if P > 0 then
          begin
            Log('c');
            Percent := StrToIntDef(Copy(Progress, 1, P - 1), -1);
            if Percent >= 0 then
            begin
              Log(Format('Transferred: %s, Percent: %d', [Transferred, Percent]));
              ProgressPage.SetProgress(Percent, 100);
              ProgressPage.SetText(Format('Transferred: %s', [Transferred]), '');
              Found := True;
            end;
          end;      
        end;
      end;
    end;
  end;

  if not Found then
  begin
    Log('No new data found');
    { no new progress data, at least pump the message queue }
    ProgressPage.SetProgress(ProgressPage.ProgressBar.Position, 100);
  end;
end;

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
  TorrentDownloaderPath: string;
  TempPath: string;
  CommandLine: string;
  Timer: LongWord;
  InstallError: string;
  ResultCode: Integer;
  S: AnsiString;
begin
  ExtractTemporaryFile('aria2c.exe');

  ProgressPage := CreateOutputProgressPage('Torrent download', 'Downloading torrent...');
  ProgressPage.SetProgress(0, 100);
  ProgressPage.Show;
  try
    Timer := SetTimer(0, 0, 250, WrapTimerProc(@UpdateProgressProc, 4));

    TempPath := ExpandConstant('{tmp}');
    TorrentDownloaderPath := TempPath + '\aria2c.exe';
    ProgressFileName := ExpandConstant('{tmp}\progress.txt');
    Log(Format('Expecting progress in %s', [ProgressFileName]));
    CommandLine :=
      Format('"%s" "%s" > "%s"', [
        TorrentDownloaderPath, '{#TorrentMagnet}', ProgressFileName]);
    Log(Format('Executing: %s', [CommandLine]));
    CommandLine := Format('/C "%s"', [CommandLine]);
    if not Exec(ExpandConstant('{cmd}'), CommandLine, TempPath, SW_HIDE,
                ewWaitUntilTerminated, ResultCode) then
    begin
      Result := 'Cannot start torrent download';
    end
      else
    if ResultCode <> 0 then
    begin
      LoadStringFromFile(ProgressFileName, S);
      Result := Format('Torrent download failed with code %d', [ResultCode]);
      Log(Result);
      Log('Output: ' + S);
    end;
  finally
    { Clean up }
    KillTimer(0, Timer);
    ProgressPage.Hide;
    DeleteFile(ProgressFileName);
  end;
end;

The BufferToAnsi and its use is based on:
Inno Setup LoadStringFromFile fails when file is open in another process


Torrent download progress

查看更多
登录 后发表回答