{
   Double Commander
   -------------------------------------------------------------------------
   File packing window

   Copyright (C) 2007-2011  Koblov Alexander (Alexx2000@mail.ru)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

}

unit fPackDlg;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Forms, Controls, Dialogs, StdCtrls, EditBtn, ExtCtrls, Buttons,
  Menus, DividerBevel, uWcxArchiveFileSource, uArchiveFileSource, uFile,
  uFileSource, Classes, fButtonForm;

type

  { TfrmPackDlg }

  TfrmPackDlg = class(TfrmButtonForm)
    btnConfig: TButton;
    btnHelp: TButton;
    cbCreateSeparateArchives: TCheckBox;
    cbCreateSFX: TCheckBox;
    cbEncrypt: TCheckBox;
    cbMoveToArchive: TCheckBox;
    cbMultivolume: TCheckBox;
    cbPackerList: TComboBox;
    cbOtherPlugins: TCheckBox;
    cbPutInTarFirst: TCheckBox;
    DividerBevel: TDividerBevel;
    edtPackCmd: TDirectoryEdit;
    lblPrompt: TLabel;
    cbStoreDir: TCheckBox;
    rgPacker: TRadioGroup;
    pnlOptions: TPanel;
    procedure btnConfigClick(Sender: TObject);
    procedure cbCreateSeparateArchivesChange(Sender: TObject);
    procedure cbCreateSFXClick(Sender: TObject);
    procedure cbOtherPluginsChange(Sender: TObject);
    procedure cbPutInTarFirstChange(Sender: TObject);
    procedure edtPackCmdAcceptDirectory(Sender: TObject; var Value: String);
    procedure FormShow(Sender: TObject);
    procedure arbChange(Sender: TObject);

  private
    FTarExt,
    FArchiveExt,
    FArchiveName,
    FArchiveType: String;
    FArchiveTypeCount: Integer;
    FHasFolder,
    FExistsArchive : Boolean;
    FSourceFileSource: IFileSource;
    FCustomParams: String;
    procedure SwitchOptions(ArcTypeChange: Boolean);
    procedure AddArchiveType(const FileExt, ArcType: String);
  public
    { public declarations }
  end;

  // Frees 'Files'.
  function ShowPackDlg(TheOwner: TComponent;
                       const SourceFileSource: IFileSource;
                       const TargetFileSource: IArchiveFileSource;
                       var Files: TFiles;
                       TargetArchivePath: String;
                       TargetPathInArchive: String;
                       bNewArchive : Boolean = True): Boolean;

implementation

{$R *.lfm}

uses
  StrUtils, WcxPlugin, uGlobs, uDCUtils, uFileSourceOperation, uLng, uOSUtils,
  uOperationsManager, uArchiveFileSourceUtil, uMultiArchiveFileSource,
  uWcxArchiveCopyInOperation, uMultiArchiveCopyInOperation, uMasks,
  DCStrUtils;

function ShowPackDlg(TheOwner: TComponent;
                     const SourceFileSource: IFileSource;
                     const TargetFileSource: IArchiveFileSource;
                     var Files: TFiles;
                     TargetArchivePath: String;
                     TargetPathInArchive: String;
                     bNewArchive : Boolean = True): Boolean;
var
  I: Integer;
  NewTargetFileSource: IArchiveFileSource = nil;
  aFlags : PtrInt;
  Operation: TFileSourceOperation;
  PackDialog: TfrmPackDlg;
  aFile: TFile = nil;
  aFiles: TFiles = nil;

  procedure Pack(var FilesToPack: TFiles; QueueId: TOperationsManagerQueueIdentifier);
  var
    sPassword,
    sPasswordTmp: String;
  begin
    with PackDialog do
    begin
      if Assigned(NewTargetFileSource) then
        begin
          // Set flags according to user selection in the pack dialog.
          aFlags := 0;
          if cbMoveToArchive.Checked then aFlags := aFlags or PK_PACK_MOVE_FILES;
          if cbStoreDir.Checked then aFlags := aFlags or PK_PACK_SAVE_PATHS;
          if cbEncrypt.Checked then aFlags := aFlags or PK_PACK_ENCRYPT;

          Operation := NewTargetFileSource.CreateCopyInOperation(
                                 SourceFileSource,
                                 FilesToPack,
                                 TargetPathInArchive);

          if Assigned(Operation) then
            begin
              // TODO: Check if another operation is not running first (for WCX).

              if NewTargetFileSource.IsInterface(IWcxArchiveFileSource) then
                begin
                  with Operation as TWcxArchiveCopyInOperation do
                  begin
                    PackingFlags := aFlags;
                    TarBefore:= cbPutInTarFirst.Checked;
                  end;
                end
              else if NewTargetFileSource.IsInterface(IMultiArchiveFileSource) then
                begin
                  with Operation as TMultiArchiveCopyInOperation do
                  begin
                    if cbEncrypt.Checked then
                      repeat
                        if not InputQuery(Caption, rsMsgPasswordEnter, True, sPassword) then
                          Exit;
                        if gRepeatPassword then
                          begin
                            if not InputQuery(Caption, rsMsgPasswordVerify, True, sPasswordTmp) then
                              Exit;
                          end
                        else
                          sPasswordTmp:= sPassword;
                        if sPassword <> sPasswordTmp then
                          ShowMessage(rsMsgPasswordDiff)
                        else
                          Password:= sPassword;
                      until sPassword = sPasswordTmp;
                    if cbMultivolume.Checked then
                      VolumeSize:= InputBox(Caption, rsMsgVolumeSizeEnter, EmptyStr);
                    PackingFlags := aFlags;
                    CustomParams:= FCustomParams;
                    TarBefore:= cbPutInTarFirst.Checked;
                  end;
                end;

              // Start operation.
              OperationsManager.AddOperation(Operation, QueueId, False, True);
            end;
        end;
    end;
  end;

var
  QueueId: TOperationsManagerQueueIdentifier;
begin
  PackDialog := TfrmPackDlg.Create(TheOwner);
{$IF DEFINED(LCLGTK2)}
  // TRadioGroup.ItemIndex:= -1 will not work under Gtk2
  // if items have been added dynamically, this workaround fixes it
  PackDialog.rgPacker.Items.Add(EmptyStr);
  PackDialog.rgPacker.Items.Clear;
{$ENDIF}
  try
    with PackDialog do
      begin
        FArchiveType:= 'none';
        FSourceFileSource:= SourceFileSource;
        if bNewArchive then  // create new archive
          begin
            if Files.Count = 1 then // if one file selected
              begin
                FHasFolder:= Files[0].IsDirectory;
                FArchiveName:= Files[0].NameNoExt;
                edtPackCmd.Text := TargetArchivePath + FArchiveName + ExtensionSeparator + FArchiveType;
              end
            else   // if some files selected
              begin
                FHasFolder:= False;
                for I:= 0 to Files.Count - 1 do
                begin
                  if Files[I].IsDirectory then
                  begin
                    FHasFolder:= True;
                    Break;
                  end;
                end;
                FArchiveName:= MakeFileName(Files.Path, 'archive');
                edtPackCmd.Text := TargetArchivePath + FArchiveName + ExtensionSeparator + FArchiveType;
              end
          end
        else  // pack in exsists archive
          begin
            if Assigned(TargetFileSource) then
              edtPackCmd.Text := TargetFileSource.ArchiveFileName;
          end;

        Result:= (ShowModal = mrOK);

        if Result then
          begin
            if Assigned(TargetFileSource) then
            begin
              // Already have a target file source.
              // It must be an archive file source.
              if not (TargetFileSource.IsClass(TArchiveFileSource)) then
                raise Exception.Create('Invalid target file source type');

              NewTargetFileSource := TargetFileSource;
            end
            else // Create a new target file source.
            begin
              // If create separate archives, one per selected file/dir
              if cbCreateSeparateArchives.Checked then
                try
                  // If files count > 1 then put to queue
                  if (Files.Count > 1) and (QueueIdentifier = FreeOperationsQueueId) then
                    QueueId := OperationsManager.GetNewQueueIdentifier
                  else begin
                    QueueId := QueueIdentifier;
                  end;
                  // Pack all selected files
                  for I:= 0 to Files.Count - 1 do
                  begin
                    // Fill files to pack
                    aFiles:= TFiles.Create(Files.Path);
                    aFiles.Add(Files[I].Clone);

                    try
                      try
                        // Check if there is an ArchiveFileSource for possible archive.
                        aFile := SourceFileSource.CreateFileObject(ExtractFilePath(edtPackCmd.Text));
                        aFile.Name := Files[I].Name + FTarExt + FArchiveExt;
                        NewTargetFileSource := GetArchiveFileSource(SourceFileSource, aFile, FArchiveType, False, True);
                      except
                        on e: EFileSourceException do
                          begin
                            MessageDlg(e.Message, mtError, [mbOK], 0);
                            Exit;
                          end;
                      end;

                      // Pack current item
                      Pack(aFiles, QueueId);
                    finally
                      FreeAndNil(aFile);
                    end;
                  end;

                finally
                  FreeAndNil(aFiles);
                end
              else
                begin
                  try
                    // Check if there is an ArchiveFileSource for possible archive.
                    aFile := SourceFileSource.CreateFileObject(ExtractFilePath(edtPackCmd.Text));
                    aFile.Name := ExtractFileName(edtPackCmd.Text);
                    NewTargetFileSource := GetArchiveFileSource(SourceFileSource, aFile, FArchiveType, False, True);
                  except
                    on e: EFileSourceException do
                      begin
                        MessageDlg(e.Message, mtError, [mbOK], 0);
                        Exit;
                      end;
                  end;

                  // Pack files
                  Pack(Files, QueueIdentifier);
                end;
            end;

            // Save last used packer
            gLastUsedPacker:= FArchiveType;
          end;
      end;

  finally
    FreeAndNil(PackDialog);
    FreeAndNil(Files);
    FreeAndNil(aFile);
  end;
end;

const
  TAR_EXT = '.tar';

{ TfrmPackDlg }

procedure TfrmPackDlg.FormShow(Sender: TObject);
var
 I, J : Integer;
 sExt : String;
begin
  FArchiveTypeCount := 0;
  FExistsArchive := (FArchiveType <> 'none');

  // WCX plugins
  for I:=0 to gWCXPlugins.Count - 1 do
    if gWCXPlugins.Enabled[I] then
    begin
      if (gWCXPlugins.Flags[I] and PK_CAPS_NEW) = PK_CAPS_NEW then
        begin
          AddArchiveType(FArchiveType, gWCXPlugins.Ext[I]);
        end;
    end;
  // MultiArc addons
  for I:= 0 to gMultiArcList.Count - 1 do
    if gMultiArcList[I].FEnabled and (gMultiArcList[I].FAdd <> EmptyStr) then
    begin
      J:= 1;
      repeat
        sExt:= ExtractDelimited(J, gMultiArcList[I].FExtension, [',']);
        if Length(sExt) = 0 then Break;
        AddArchiveType(FArchiveType, sExt);
        Inc(J);
      until False;
    end;

    if (rgPacker.Items.Count > 0) and (rgPacker.ItemIndex < 0) and (not cbOtherPlugins.Checked) then
      rgPacker.ItemIndex := 0;
    if cbPackerList.Items.Count > 0 then
      begin
        cbOtherPlugins.Visible := True;
        cbPackerList.Visible := True;

        if FExistsArchive then
          cbPackerList.Enabled:= False
        else
          cbOtherPlugins.Enabled := True;

        if cbPackerList.ItemIndex < 0 then
          cbPackerList.ItemIndex := 0;
      end
    else
      btnConfig.AnchorToCompanion(akTop, 6, rgPacker);
end;

procedure TfrmPackDlg.btnConfigClick(Sender: TObject);
var
  WcxFileSource: IWcxArchiveFileSource;
begin
  try
    WcxFileSource := TWcxArchiveFileSource.CreateByArchiveName(FSourceFileSource, edtPackCmd.Text, True);
    if Assigned(WcxFileSource) then // WCX plugin
      try
        WcxFileSource.WcxModule.VFSConfigure(Handle);
      finally
        WcxFileSource := nil; // free interface
      end
    else // MultiArc addon
      begin
        FCustomParams:= InputBox(Caption, rsMsgArchiverCustomParams, FCustomParams);
      end;
  except
    on e: Exception do
      MessageDlg(e.Message, mtError, [mbOK], 0);
  end;
end;

procedure TfrmPackDlg.cbCreateSeparateArchivesChange(Sender: TObject);
begin
  if cbCreateSeparateArchives.Checked then
    edtPackCmd.Text:= ExtractFilePath(edtPackCmd.Text) + '*.*' + FTarExt + FArchiveExt
  else
    edtPackCmd.Text:= ExtractFilePath(edtPackCmd.Text) + FArchiveName + FTarExt + FArchiveExt;
end;

procedure TfrmPackDlg.cbCreateSFXClick(Sender: TObject);
var
  State: Boolean;
begin
  if cbCreateSFX.Tag = 0 then
  begin
    cbCreateSFX.Tag:= 1;
    // Save check box state
    State:= cbCreateSFX.Checked;
    if State then
      FArchiveExt:= GetSfxExt
    else
      FArchiveExt:= ExtensionSeparator + FArchiveType;
    edtPackCmd.Text := ChangeFileExt(edtPackCmd.Text, FArchiveExt);
    // Switch archiver options
    SwitchOptions(False);
    // Restore check box state
    cbCreateSFX.Checked:= State;
    cbCreateSFX.Tag:= 0;
  end;
end;

procedure TfrmPackDlg.cbOtherPluginsChange(Sender: TObject);
begin
  if cbOtherPlugins.Checked then
    begin
      FArchiveType:= cbPackerList.Text;
      FArchiveExt:= ExtensionSeparator + FArchiveType;
      edtPackCmd.Text := ChangeFileExt(edtPackCmd.Text, FArchiveExt);
      rgPacker.ItemIndex := -1;
    end
  else
    begin
      if rgPacker.ItemIndex = -1 then
        rgPacker.ItemIndex := 0;
    end;
  FCustomParams:= EmptyStr;
  cbPackerList.Enabled := cbOtherPlugins.Checked;
  SwitchOptions(True);
end;

procedure TfrmPackDlg.cbPutInTarFirstChange(Sender: TObject);
begin
  if cbPutInTarFirst.Checked then
    begin
      FTarExt:= TAR_EXT;
      edtPackCmd.Text := ChangeFileExt(edtPackCmd.Text, TAR_EXT + FArchiveExt);
    end
  else
    begin
      FTarExt:= EmptyStr;
      edtPackCmd.Text := StringReplace(edtPackCmd.Text, TAR_EXT + FArchiveExt, FArchiveExt, [rfIgnoreCase]);
    end;
end;

procedure TfrmPackDlg.edtPackCmdAcceptDirectory(Sender: TObject; var Value: String);
begin
  Value := IncludeTrailingPathDelimiter(Value) + ExtractFileName(edtPackCmd.Text);
end;

procedure TfrmPackDlg.arbChange(Sender: TObject);
begin
  if rgPacker.ItemIndex >= 0 then
    begin
      FArchiveType:= rgPacker.Items[rgPacker.ItemIndex];
      FArchiveExt:= ExtensionSeparator + FArchiveType;
      edtPackCmd.Text := ChangeFileExt(edtPackCmd.Text, FArchiveExt);
      cbOtherPlugins.Checked := False;
    end;
  FCustomParams:= EmptyStr;
  SwitchOptions(True);
end;

procedure TfrmPackDlg.SwitchOptions(ArcTypeChange: Boolean); // Ugly but working
var
  I: LongInt;
  sCmd: String;
begin
  if ArcTypeChange then
  begin
    // Reset some options
    cbCreateSFX.Checked:= False;
  end;

  // WCX plugins
  for I:= 0 to gWCXPlugins.Count - 1 do
    if gWCXPlugins.Enabled[I] and (gWCXPlugins.Ext[I] = FArchiveType) then
    begin
      // If plugin supports packing with password
      EnableControl(cbEncrypt, ((gWCXPlugins.Flags[I] and PK_CAPS_ENCRYPT) <> 0));
      // If archive can not contain multiple files
      if ((gWCXPlugins.Flags[I] and PK_CAPS_MULTIPLE) = 0) then
      begin
        // If file list contain directory then
        // put to the tar archive first is needed
        if not FHasFolder then
          cbCreateSeparateArchives.Checked:= True
        else
          begin
            cbPutInTarFirst.Checked:= True;
            EnableControl(cbPutInTarFirst, False);
          end;
      end
      else
        begin
          cbPutInTarFirst.Checked:= False;
          EnableControl(cbPutInTarFirst, True);
          cbCreateSeparateArchives.Checked:= False;
        end;
      // Options that supported by plugins
      EnableControl(cbStoreDir, True);
      // Options that don't supported by plugins
      EnableControl(cbMultivolume, False);
      Exit;
    end;

  // MultiArc addons
  for I := 0 to gMultiArcList.Count - 1 do
    with gMultiArcList.Items[I] do
    begin
      if FEnabled and MatchesMaskList(FArchiveType, FExtension, ',') then
      begin
        // Archive can contain multiple files
        cbCreateSeparateArchives.Checked:= False;
        // If addon supports create self extracting archive
        EnableControl(cbCreateSFX, (Length(FAddSelfExtract) <> 0));

        if cbCreateSFX.Enabled and cbCreateSFX.Checked then
          sCmd:= FAddSelfExtract
        else
          sCmd:= FAdd;

        // If addon supports create multi volume archive
        EnableControl(cbMultivolume, (Pos('%V', sCmd) <> 0));
        // If addon supports packing with password
        EnableControl(cbEncrypt, (Pos('%W', sCmd) <> 0));

        // Options that supported by addons
        EnableControl(cbPutInTarFirst, True);
        // Options that don't supported by addons
        cbStoreDir.Checked:= True;
        EnableControl(cbStoreDir, False);
        Exit;
      end;
    end;
end;

procedure TfrmPackDlg.AddArchiveType(const FileExt, ArcType: String);
var
  iIndex: Integer;
begin
  // First 9 plugins we display as  RadioButtons
  if FArchiveTypeCount < 9 then
    begin
      iIndex := rgPacker.Items.Add(ArcType);
      if FExistsArchive then
        begin
          if (FileExt = ArcType) then
            rgPacker.ItemIndex := iIndex
          else
            rgPacker.Controls[iIndex + 1].Enabled := False;
        end
      else if (gLastUsedPacker = ArcType) then
        begin
           rgPacker.ItemIndex := iIndex;
        end;
      FArchiveTypeCount := FArchiveTypeCount + 1;
     end
   else  // Other plugins we add in ComboBox
    begin
      iIndex := cbPackerList.Items.Add(ArcType);
      if (gLastUsedPacker = ArcType) or (FExistsArchive and (FileExt = ArcType)) then
      begin
        cbPackerList.ItemIndex := iIndex;
        cbOtherPlugins.Checked := True;
      end;
    end;
end;

end.

