inno-setupcompression

How to decompress archive file with progress and cancel button in Inno Setup using unarc


I am using this code: How to add .arc decompression to Inno Setup? (answer of Martin Prikryl). I want to add a cancel button at decompressing page and active this page for others functions (when the decompression is active, this page is inactive and, for example, i can not press on/off button of my music implementation).

How to add a cancel button to decompression page? and how to active this page for others functions?


Solution

  • I have reimplemented the solution from How to add .arc decompression to Inno Setup? using unarc.dll (from FreeArc+InnoSetup package ISFreeArcExtract v.4.0.rar).

    It greatly simplifies the code and also makes it easier to add the ability to cancel the decompression.


    #define ArcArchive "test.arc"
    
    [Files]
    Source: unarc.dll; Flags: dontcopy
    
    [Code]
    
    const
      ArcCancelCode = -10;
    
    function FreeArcExtract(
      Callback: LongWord;
      Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: PAnsiChar
      ): Integer;
      external 'FreeArcExtract@files:unarc.dll cdecl';
      
    const
      CP_UTF8 = 65001;
    
    function WideCharToMultiByte(CodePage: UINT; dwFlags: DWORD;
      lpWideCharStr: string; cchWideChar: Integer; lpMultiByteStr: AnsiString;
      cchMultiByte: Integer; lpDefaultCharFake: Integer;
      lpUsedDefaultCharFake: Integer): Integer;
      external 'WideCharToMultiByte@kernel32.dll stdcall';
    
    function GetStringAsUtf8(S: string): AnsiString;
    var
      Len: Integer;
    begin
      Len := WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, 0, 0, 0);
      SetLength(Result, Len);
      WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, Len, 0, 0);
    end;
    
    var
      ArcTotalSize: Integer;
      ArcExtracted: Integer;
      ArcCancel: Boolean;
      ArcProgressPage: TOutputProgressWizardPage;
    
    function FreeArcCallback(
      AWhat: PAnsiChar; Int1, Int2: Integer; Str: PAnsiChar): Integer;
    var
      What: string;
    begin
      What := AWhat;
      if What = 'origsize' then
      begin
        ArcTotalSize := Int1;
        Log(Format('Total size of files to be extracted is %d MB', [ArcTotalSize]));
      end
        else
      if What = 'write' then
      begin
        if ArcTotalSize > 0 then
        begin
          ArcProgressPage.SetProgress(Int1, ArcTotalSize);
        end;
        ArcExtracted := Int1;
      end
        else
      begin
        // Just to pump message queue more often (particularly for 'read' callbacks),
        // to get more smooth progress bar
        if (ArcExtracted > 0) and (ArcTotalSize > 0) then
        begin
          ArcProgressPage.SetProgress(ArcExtracted, ArcTotalSize);
        end;
      end;
    
      if ArcCancel then Result := ArcCancelCode
        else Result := 0;
    end;
    
    function FreeArcCmd(
      Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: string): Integer;
    begin
      ArcCancel := False;
      try
        Result :=
          FreeArcExtract(
            CreateCallback(@FreeArcCallback),
            GetStringAsUtf8(Cmd1), GetStringAsUtf8(Cmd2), GetStringAsUtf8(Cmd3),
            GetStringAsUtf8(Cmd4), GetStringAsUtf8(Cmd5), GetStringAsUtf8(Cmd6),
            GetStringAsUtf8(Cmd7), GetStringAsUtf8(Cmd8), GetStringAsUtf8(Cmd9),
            GetStringAsUtf8(Cmd10));
    
        Log(Format('Arc command "%s" result %d', [Cmd1, Result]));
      except
        Result := -63;
      end;
    end;
    
    function UnPackArchive(ArchivePath: string; DestPath: string): Integer;
    begin
      { Find out length of files to be extracted - origsize }
      Result := FreeArcCmd('l', '--', ArchivePath, '', '', '', '', '', '', '');
    
      if Result = 0 then
      begin
        // Actually extract
        Result :=
          FreeArcCmd('x', '-o+', '-dp' + DestPath, '-w' + DestPath, '--', ArchivePath,
                     '', '', '', '');
      end;
    end;
    
    procedure UnpackCancelButtonClick(Sender: TObject);
    begin
      ArcCancel := True;
    end;
    
    procedure ExtractArc;
    var
      ArcArchivePath: string;
      UnpackResult: Integer;
      PrevCancelButtonClick: TNotifyEvent;
      Error: string;
    begin
      ArcProgressPage :=
        CreateOutputProgressPage('Decompression', 'Decompressing archive...');
      ArcProgressPage.SetProgress(0, 100);
      ArcProgressPage.Show;
      try
        WizardForm.CancelButton.Visible := True;
        WizardForm.CancelButton.Enabled := True;
        PrevCancelButtonClick := WizardForm.CancelButton.OnClick;
        WizardForm.CancelButton.OnClick := @UnpackCancelButtonClick;
    
        ArcArchivePath := ExpandConstant('{src}\{#ArcArchive}');
        Log(Format('Arc extraction starting - %s', [ArcArchivePath]));
    
        ArcExtracted := 0;
        UnpackResult := UnPackArchive(ArcArchivePath, ExpandConstant('{app}'));
    
        if UnpackResult <> 0 then
        begin
          if ArcCancel then
          begin
            Error := 'Extraction cancelled';
          end
            else
          begin
            Error := Format('Extraction failed with code %d', [UnpackResult]);
          end;
    
          MsgBox(Error, mbError, MB_OK);
        end;
      finally
        Log('Arc extraction cleanup');
        ArcProgressPage.Hide;
        WizardForm.CancelButton.OnClick := PrevCancelButtonClick;
      end;
    end;
    
    procedure CurStepChanged(CurStep: TSetupStep);
    begin
      if CurStep = ssPostInstall then
      begin
        ExtractArc;
      end;
    end;
    

    For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.


    Extraction cancelled


    The code extracts a separate .arc file. If you want to embed the archive to the installer, you can use

    [Files]
    Source: {#ArcArchive}; DestDir: "{tmp}"; Flags: nocompression deleteafterinstall
    

    And extract the archive from the {tmp}:

    ArcArchivePath := ExpandConstant('{tmp}\{#ArcArchive}');
    

    Note that the unarc.dll from ISFreeArcExtract v.4.0.rar does not seem to support password protected archives. The version from ISFreeArcExtract v.4.2.rar does, but I'm not aware of trustworthy download link.


    If you want to extract multiple archives, see Inno Setup - How to add multiple arc files to decompress?