doublecmd/src/uwcxmodule.pas
2009-07-10 18:50:23 +00:00

1558 lines
45 KiB
ObjectPascal

{
Double commander
-------------------------------------------------------------------------
Archive File support - class for manage WCX plugins (Version 2.10)
Copyright (C) 2006-2009 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 uWCXmodule;
interface
uses
uWCXprototypes, uWCXhead, uFileList, uTypes, dynlibs, Classes, uVFSModule,
uVFSTypes, uVFSUtil, fFileOpDlg, Dialogs, DialogAPI, uClassesEx,
StringHashList;
{$H+}
Type
TWCXOperation = (OP_EXTRACT, OP_PACK, OP_DELETE);
TWCXModule = class;
{ TWCXHeaderData }
{ Handles THeaderData and THeaderDataEx }
TWCXHeader = class
private
function PCharLToUTF8(CharString: PChar; MaxSize: Integer): UTF8String;
public
ArcName: UTF8String;
FileName: UTF8String;
Flags,
HostOS,
FileCRC,
FileTime,
UnpVer,
Method,
FileAttr: Longint;
PackSize,
UnpSize: Int64;
Cmt: UTF8String;
CmtState: Longint;
constructor Create(const Data: PHeaderData); overload;
constructor Create(const Data: PHeaderDataEx); overload;
constructor Create; overload; // allows creating empty record
end;
{ Packing/Unpacking thread }
TWCXCopyThread = class(TThread)
private
FOperation: TWCXOperation;
FFileList: TFileList;
FPath: String;
FFlags: Integer;
FWCXModule : TWCXModule;
protected
procedure Execute; override;
procedure Terminating(Sender: TObject);
public
constructor Create(WCXModule: TWCXModule;
Operation: TWCXOperation; var FileList: TFileList;
Path: String; Flags: Integer);
end;
{ TWCXModule }
TWCXModule = class (TVFSModule)
private
FArcFileList : TList;
FPackerCaps : Integer;
FFolder : String;
FFilesSize: Int64;
FPercent : Double;
CT : TWCXCopyThread; // Packing/Unpacking thread
FFileOpDlg: TfrmFileOp; // progress window
procedure ShowErrorMessage;
// These 3 functions handle freeing FileList.
{Extract files from archive}
function WCXCopyOut(var FileList: TFileList; sDestPath: String; Flags: Integer) : Boolean;
{Pack files in archive}
function WCXCopyIn(var FileList: TFileList; sDestPath: String; Flags: Integer) : Boolean;
{Delete files from archive}
function WCXDelete(var FileList: TFileList) : Boolean;
{en
Counts size of all files in archive that match selection in FileList
and given file mask.
}
procedure CountFiles(const FileList: TFileList; FileMask: String);
{en
Creates neccessary paths before extracting files from archive.
Also counts size of all files that will be extracted.
@param(FileList
List of files/directories to extract (relative to archive root).)
@param(FileMask
Only directories containing files matching this mask will be created.)
@param(sDestPath
Destination path where the files will be extracted.)
@param(CurrentArchiveDir
Path inside the archive from where the files will be extracted.)
@param(CreatedPaths
This list will be filled with absolute paths to directories
that were created, together with their attributes.)}
procedure CreateDirsAndCountFiles(const FileList: TFileList; FileMask: String;
sDestPath: String; CurrentArchiveDir: String;
var CreatedPaths: TStringHashList);
{en
Sets attributes for directories.
@param(Paths
The list of absolute paths, which attributes are to be set.
Each list item's data field must be a pointer to THeaderData,
from where the attributes are retrieved.}
function SetDirsAttributes(const Paths: TStringHashList): Boolean;
{ Frees current archive file list (fArcFileList). }
procedure DeleteArchiveFileList;
{ Initializes and shows progress dialog. }
procedure PrepareDialog(Operation: TWCXOperation);
{ Closes progress dialog and cleans up. }
procedure FinishDialog;
{ Reads WCX header using ReadHeaderEx if available or ReadHeader. }
function ReadWCXHeader(hArcData: TArcHandle;
out HeaderData: TWCXHeader): Integer;
protected
// module's functions
//**mandatory:
OpenArchive : TOpenArchive;
ReadHeader : TReadHeader;
ProcessFile : TProcessFile;
CloseArchive : TCloseArchive;
//**optional:
ReadHeaderEx : TReadHeaderEx;
PackFiles : TPackFiles;
DeleteFiles : TDeleteFiles;
GetPackerCaps : TGetPackerCaps;
ConfigurePacker : TConfigurePacker;
SetChangeVolProc : TSetChangeVolProc;
SetProcessDataProc : TSetProcessDataProc;
StartMemPack : TStartMemPack;
PackToMem : TPackToMem;
DoneMemPack : TDoneMemPack;
CanYouHandleThisFile : TCanYouHandleThisFile;
PackSetDefaultParams : TPackSetDefaultParams;
// Dialog API
SetDlgProc: TSetDlgProc;
FModuleHandle:TLibHandle; // Handle to .DLL or .so
FArchiveName : String;
public
constructor Create;
destructor Destroy; override;
function LoadModule(const sName:String):Boolean;override; {Load WCX plugin}
procedure UnloadModule;override; {UnLoad WCX plugin}
function VFSInit(Data: PtrInt):Boolean;override;
procedure VFSDestroy;override;
function VFSCaps : TVFSCaps;override;
function VFSConfigure(Parent: THandle):Boolean;override;
function VFSOpen(const sName:String; bCanYouHandleThisFile : Boolean = False):Boolean;override;
function VFSClose:Boolean;override;
function VFSRefresh : Boolean;override;
function VFSMkDir(const sDirName:String ):Boolean;override;{Create a directory}
function VFSRmDir(const sDirName:String):Boolean;override; {Remove a directory}
function VFSCopyOut(var flSrcList : TFileList; sDstPath:String; Flags: Integer):Boolean;override;{Extract files from archive}
function VFSCopyIn(var flSrcList : TFileList; sDstName:String; Flags : Integer):Boolean;override;{Pack files in archive}
function VFSCopyOutEx(var flSrcList : TFileList; sDstPath:String; Flags: Integer):Boolean;override;{Extract files from archive in thread}
function VFSCopyInEx(var flSrcList : TFileList; sDstName:String; Flags : Integer):Boolean;override;{Pack files in archive in thread}
function VFSRename(const sSrcName, sDstName:String):Boolean;override;{Rename or move file}
function VFSRun(const sName:String):Boolean;override;
function VFSDelete(var flNameList:TFileList):Boolean;override;{Delete files from archive}
function VFSList(const sDir:String; var fl:TFileList ):Boolean;override;{Return the filelist of archive}
function VFSMisc : PtrUInt;override;
end;
{ TWCXModuleList }
TWCXModuleList = class(TStringList)
private
function GetAEnabled(Index: Integer): Boolean;
function GetAExt(Index: Integer): String;
function GetAFileName(Index: Integer): String;
function GetAFlags(Index: Integer): PtrInt;
procedure SetAEnabled(Index: Integer; const AValue: Boolean);
procedure SetAFileName(Index: Integer; const AValue: String);
procedure SetAFlags(Index: Integer; const AValue: PtrInt);
procedure SetExt(Index: Integer; const AValue: String);
public
procedure Load(Ini: TIniFileEx);
procedure Save(Ini: TIniFileEx);
function Add(Ext: String; Flags: PtrInt; FileName: String): Integer; reintroduce;
function FindFirstEnabledByName(Name: String): Integer;
property FileName[Index: Integer]: String read GetAFileName write SetAFileName;
property Flags[Index: Integer]: PtrInt read GetAFlags write SetAFlags;
property Ext[Index: Integer]: String read GetAExt write SetExt;
property Enabled[Index: Integer]: Boolean read GetAEnabled write SetAEnabled;
end;
function IsBlocked : Boolean;
implementation
uses Forms, SysUtils, Masks, uFileOp, uGlobs, uLog, uOSUtils, LCLProc,
uDCUtils, uLng, Controls, fPackInfoDlg, fDialogBox, uGlobsPaths, FileUtil;
const
WcxIniFileName = 'wcx.ini';
var
WCXModule : TWCXModule = nil; // used in ProcessDataProc
iResult : Integer;
constructor TWCXModule.Create;
begin
FFilesSize:= 0;
FPercent := 0;
FArcFileList := nil;
CT := nil;
FFileOpDlg := nil;
end;
destructor TWCXModule.Destroy;
begin
UnloadModule;
WCXModule := nil; // clear global variable pointing to self
end;
function TWCXModule.LoadModule(const sName:String):Boolean;
var
PackDefaultParamStruct : TPackDefaultParamStruct;
SetDlgProcInfo: TSetDlgProcInfo;
sPluginDir: WideString;
sPluginConfDir: WideString;
begin
FModuleHandle := mbLoadLibrary(sName);
Result := (FModuleHandle <> 0);
if FModuleHandle = 0 then exit;
//DebugLN('FModuleHandle =', FModuleHandle);
// mandatory functions
OpenArchive:= TOpenArchive(GetProcAddress(FModuleHandle,'OpenArchive'));
@ReadHeader:= GetProcAddress(FModuleHandle,'ReadHeader');
@ReadHeaderEx:= GetProcAddress(FModuleHandle,'ReadHeaderEx');
@ProcessFile:= GetProcAddress(FModuleHandle,'ProcessFile');
@CloseArchive:= GetProcAddress(FModuleHandle,'CloseArchive');
if ((@OpenArchive = nil)or(@ReadHeader = nil)or
(@ProcessFile = nil)or(@CloseArchive = nil)) then
begin
OpenArchive := nil;
ReadHeader:= nil;
ProcessFile := nil;
CloseArchive := nil;
Result := False;
Exit;
end;
// optional functions
@PackFiles:= GetProcAddress(FModuleHandle,'PackFiles');
@DeleteFiles:= GetProcAddress(FModuleHandle,'DeleteFiles');
@GetPackerCaps:= GetProcAddress(FModuleHandle,'GetPackerCaps');
@ConfigurePacker:= GetProcAddress(FModuleHandle,'ConfigurePacker');
@SetChangeVolProc:= GetProcAddress(FModuleHandle,'SetChangeVolProc');
@SetProcessDataProc:= GetProcAddress(FModuleHandle,'SetProcessDataProc');
@StartMemPack:= GetProcAddress(FModuleHandle,'StartMemPack');
@PackToMem:= GetProcAddress(FModuleHandle,'PackToMem');
@DoneMemPack:= GetProcAddress(FModuleHandle,'DoneMemPack');
@CanYouHandleThisFile:= GetProcAddress(FModuleHandle,'CanYouHandleThisFile');
@PackSetDefaultParams:= GetProcAddress(FModuleHandle,'PackSetDefaultParams');
// Dialog API function
@SetDlgProc:= GetProcAddress(FModuleHandle,'SetDlgProc');
if Assigned(PackSetDefaultParams) then
begin
with PackDefaultParamStruct do
begin
Size := SizeOf(PackDefaultParamStruct);
PluginInterfaceVersionLow := 10;
PluginInterfaceVersionHi := 2;
DefaultIniName := gpIniDir + WcxIniFileName;
end;
PackSetDefaultParams(@PackDefaultParamStruct);
end;
// Dialog API
if Assigned(SetDlgProc) then
begin
sPluginDir := UTF8Decode(ExtractFilePath(sName));
sPluginConfDir := UTF8Decode(gpIniDir);
with SetDlgProcInfo do
begin
PluginDir:= PWideChar(sPluginDir);
PluginConfDir:= PWideChar(sPluginConfDir);
InputBox:= @fDialogBox.InputBox;
MessageBox:= @fDialogBox.MessageBox;
DialogBox:= @fDialogBox.DialogBox;
DialogBoxEx:= @fDialogBox.DialogBoxEx;
SendDlgMsg:= @fDialogBox.SendDlgMsg;
end;
SetDlgProc(SetDlgProcInfo);
end;
end;
procedure TWCXModule.UnloadModule;
begin
if FModuleHandle <> 0 then
FreeLibrary(FModuleHandle);
FModuleHandle := 0;
@OpenArchive:= nil;
@ReadHeader:= nil;
@ReadHeaderEx:= nil;
@ProcessFile:= nil;
@CloseArchive:= nil;
@PackFiles:= nil;
@DeleteFiles:= nil;
@GetPackerCaps:= nil;
@ConfigurePacker:= nil;
@SetChangeVolProc:= nil;
@SetProcessDataProc:= nil;
@StartMemPack:= nil;
@PackToMem:= nil;
@DoneMemPack:= nil;
@CanYouHandleThisFile:= nil;
@PackSetDefaultParams:= nil;
end;
procedure ShowErrorMsg(iErrorMsg : Integer);
var
sErrorMsg : String;
begin
case iErrorMsg of
E_END_ARCHIVE : sErrorMsg := rsMsgErrEndArchive;
E_NO_MEMORY : sErrorMsg := rsMsgErrNoMemory;
E_BAD_DATA : sErrorMsg := rsMsgErrBadData;
E_BAD_ARCHIVE : sErrorMsg := rsMsgErrBadArchive;
E_UNKNOWN_FORMAT : sErrorMsg := rsMsgErrUnknownFormat;
E_EOPEN : sErrorMsg := rsMsgErrEOpen;
E_ECREATE : sErrorMsg := rsMsgErrECreate;
E_ECLOSE : sErrorMsg := rsMsgErrEClose;
E_EREAD : sErrorMsg := rsMsgErrERead;
E_EWRITE : sErrorMsg := rsMsgErrEWrite;
E_SMALL_BUF : sErrorMsg := rsMsgErrSmallBuf;
E_EABORTED : sErrorMsg := rsMsgErrEAborted;
E_NO_FILES : sErrorMsg := rsMsgErrNoFiles;
E_TOO_MANY_FILES : sErrorMsg := rsMsgErrTooManyFiles;
E_NOT_SUPPORTED : sErrorMsg := rsMsgErrNotSupported;
end;
// write log error
if (log_arc_op in gLogOptions) and (log_errors in gLogOptions) then
logWrite(rsMsgLogError + sErrorMsg, lmtError);
// Standart error modal dialog
ShowMessage(sErrorMsg);
end;
function ChangeVolProc(ArcName : Pchar; Mode:Longint):Longint; stdcall;
begin
case Mode of
PK_VOL_ASK:
ArcName := PChar(UTF8ToSys(Dialogs.InputBox ('Double Commander', rsMsgSelLocNextVol, SysToUTF8(ArcName))));
PK_VOL_NOTIFY:
ShowMessage(rsMsgNextVolUnpack);
end;
end;
function ProcessDataProc(FileName: PChar; Size: Integer): Integer; stdcall;
begin
//DebugLn('Working ' + FileName + ' Size = ' + IntToStr(Size));
Result := 1;
if Assigned(WCXModule) then
with WCXModule do
begin
if not Assigned(FFileOpDlg) then Exit;
if FFileOpDlg.ModalResult = mrCancel then // Cancel operation
Result := 0;
FFileOpDlg.sFileNameFrom := SysToUTF8(FileName);
if not (Size < 0) then
begin
if FFilesSize <> 0 then
FPercent := FPercent + ((Size * 100) / FFilesSize);
//DebugLn('Percent = ' + IntToStr(Round(FPercent)));
FFileOpDlg.iProgress1Pos := 100;
FFileOpDlg.iProgress2Pos := Round(FPercent);
end
else // For plugins which unpack in CloseArchive
if (Size >= -100) and (Size <= -1) then // first percent bar
begin
FFileOpDlg.iProgress1Pos := (Size * -1);
//DebugLn('Working ' + FileName + ' Percent1 = ' + IntToStr(FFileOpDlg.iProgress1Pos));
end
else if (Size >= -1100) and (Size <= -1000) then // second percent bar
begin
FFileOpDlg.iProgress2Pos := (Size * -1) - 1000;
//DebugLn('Working ' + FileName + ' Percent2 = ' + IntToStr(FFileOpDlg.iProgress2Pos));
end;
if Assigned(CT) then
CT.Synchronize(FFileOpDlg.UpdateDlg)
else
begin
FFileOpDlg.UpdateDlg;
Application.ProcessMessages;
end;
end; //with
end;
procedure TWCXModule.ShowErrorMessage;
begin
ShowErrorMsg(iResult);
end;
procedure TWCXModule.DeleteArchiveFileList;
var
i: Integer;
begin
if Assigned(FArcFileList) then
begin
for i := 0 to FArcFileList.Count - 1 do
begin
if Assigned(FArcFileList.Items[i]) then
begin
TWCXHeader(FArcFileList.Items[i]).Free;
FArcFileList.Items[i] := nil;
end;
end;
FreeAndNil(FArcFileList);
end;
end;
function TWCXModule.VFSInit(Data: PtrInt): Boolean;
begin
FPackerCaps:= Data;
end;
procedure TWCXModule.VFSDestroy;
begin
DeleteArchiveFileList;
UnloadModule;
end;
function TWCXModule.VFSCaps: TVFSCaps;
begin
Result := [];
Include(Result, VFS_CAPS_COPYOUT);
if Assigned(PackFiles) then
Include(Result, VFS_CAPS_COPYIN);
if Boolean(FPackerCaps and PK_CAPS_DELETE) and Assigned(DeleteFiles) then
Include(Result, VFS_CAPS_DELETE);
end;
function TWCXModule.VFSConfigure(Parent: THandle): Boolean;
begin
if Assigned(ConfigurePacker) then
ConfigurePacker(Parent, FModuleHandle);
end;
function TWCXModule.VFSOpen(const sName: String; bCanYouHandleThisFile : Boolean = False): Boolean;
procedure CollectDirs(Path: PAnsiChar; var DirsList: TStringHashList);
var
I : Integer;
Dir : AnsiString;
begin
// Scan from the second char from the end, to the second char from the beginning.
for I := strlen(Path) - 2 downto 1 do
begin
if Path[I] = PathDelim then
begin
SetString(Dir, Path, I);
if DirsList.Find(Dir) = -1 then
// Add directory and continue scanning for parent directories.
DirsList.Add(Dir)
else
// This directory is already in the list and we assume
// that all parent directories are too.
Exit;
end
end;
end;
var
ArcHandle : TArcHandle;
ArcFile : tOpenArchiveData;
Header: TWCXHeader;
AllDirsList, ExistsDirList : TStringHashList;
I : Integer;
NameLength: Integer;
begin
FArchiveName := sName;
DebugLN('FArchiveName = ' + FArchiveName);
if not mbFileAccess(FArchiveName, fmOpenRead) then
begin
Result := False;
Exit;
end;
if bCanYouHandleThisFile and Assigned(CanYouHandleThisFile) then
begin
Result := CanYouHandleThisFile(PChar(UTF8ToSys(sName)));
if not Result then Exit;
end;
DebugLN('Open Archive');
(*Open Archive*)
FillChar(ArcFile, SizeOf(ArcFile), #0);
ArcFile.ArcName := PChar(UTF8ToSys(sName));
ArcFile.OpenMode := PK_OM_LIST;
try
ArcHandle := OpenArchive(ArcFile);
except
ArcHandle := 0;
end;
if ArcHandle = 0 then
begin
if not bCanYouHandleThisFile then
ShowErrorMsg(ArcFile.OpenResult);
Result := False;
Exit;
end;
WCXModule := Self; // set WCXModule variable to current module
SetChangeVolProc(ArcHandle, ChangeVolProc);
SetProcessDataProc(ArcHandle, ProcessDataProc);
DebugLN('Get File List');
(*Get File List*)
DeleteArchiveFileList;
FArcFileList := TList.Create;
ExistsDirList := TStringHashList.Create(True);
AllDirsList := TStringHashList.Create(True);
try
while (ReadWCXHeader(ArcHandle, Header) = E_SUCCESS) do
begin
// Some plugins end directories with path delimiter. Delete it if present.
if FPS_ISDIR(Header.FileAttr) then
begin
NameLength := Length(Header.FileName);
if (Header.FileName[NameLength] = PathDelim) then
Delete(Header.FileName, NameLength, 1);
//****************************
(* Workaround for plugins that don't give a list of folders
or the list does not include all of the folders. *)
// Collect directories that the plugin supplies.
if (ExistsDirList.Find(Header.FileName) < 0) then
ExistsDirList.Add(Header.FileName);
end;
// Collect all directories.
CollectDirs(PAnsiChar(Header.FileName), AllDirsList);
//****************************
FArcFileList.Add(Header);
// get next file
iResult := ProcessFile(ArcHandle, PK_SKIP, nil, nil);
//Check for errors
if iResult <> E_SUCCESS then
ShowErrorMessage;
end; // while
(* if plugin does not give a list of folders *)
for I := 0 to AllDirsList.Count - 1 do
begin
// Add only those directories that were not supplied by the plugin.
if ExistsDirList.Find((AllDirsList.List + I)^.Key) < 0 then
begin
Header := TWCXHeader.Create;
try
Header.FileName := (AllDirsList.List + I)^.Key;
Header.ArcName := FArchiveName;
Header.FileAttr := faFolder;
Header.FileTime := mbFileAge(FArchiveName);
FArcFileList.Add(Header);
except
FreeAndNil(Header);
end;
end;
end;
finally
AllDirsList.Free;
ExistsDirList.Free;
CloseArchive(ArcHandle);
end;
Result := True;
end;
function TWCXModule.VFSClose: Boolean;
begin
DeleteArchiveFileList;
end;
function TWCXModule.VFSRefresh: Boolean;
begin
Result := VFSOpen(FArchiveName)
end;
function TWCXModule.VFSMkDir(const sDirName: String): Boolean;
begin
end;
function TWCXModule.VFSRmDir(const sDirName: String): Boolean;
begin
end;
function ExcludeFrontPathDelimiter(s:String): String;
begin
if (Length(s) > 0) and (s[1] = PathDelim) then
Result := Copy(s, 2, Length(s) - 1)
else
Result := s;
end;
function GetFileList(var fl:TFileList; Operation: TWCXOperation) : String;
var
I : Integer;
FileName : String;
begin
Result := '';
for I := 0 to fl.Count - 1 do
begin
// Filenames must be relative to archive root and shouldn't start with path delimiter.
FileName := ExcludeFrontPathDelimiter(fl.GetItem(I)^.sName);
// Special treatment of directories.
if FPS_ISDIR(fl.GetItem(I)^.iMode) then
begin
case Operation of
OP_PACK:
FileName := IncludeTrailingPathDelimiter(FileName);
OP_DELETE:
FileName := IncludeTrailingPathDelimiter(FileName) + '*.*';
end;
end;
Result := Result + FileName + #0;
end;
Result := Result + #0;
end;
function TWCXModule.WCXCopyOut(var FileList: TFileList; sDestPath: String; Flags: Integer) : Boolean;
var
ArcHandle : TArcHandle;
ArcFile : tOpenArchiveData;
Header : TWCXHeader;
TargetFileName: String;
FileMask: String;
CreatedPaths: TStringHashList;
begin
FPercent := 0;
FillChar(ArcFile, SizeOf(ArcFile), #0);
ArcFile.ArcName := PChar(UTF8ToSys(FArchiveName));
ArcFile.OpenMode := PK_OM_EXTRACT;
ArcHandle := OpenArchive(ArcFile);
if ArcHandle = 0 then
begin
if Assigned(CT) then
begin
iResult := ArcFile.OpenResult;
CT.Synchronize(ShowErrorMessage);
end
else
ShowErrorMsg(ArcFile.OpenResult);
Result := False;
FreeAndNil(FileList);
Exit;
end;
FileMask := ExtractFileName(sDestPath);
if FileMask = '' then FileMask := '*'; // extract all selected files/folders
sDestPath := ExtractFilePath(sDestPath);
// Convert file list so that filenames are relative to archive root.
ChangeFileListRoot(FArchiveName + PathDelim, FileList);
CreatedPaths := TStringHashList.Create(True);
try
// Count total files size and create needed directories.
CreateDirsAndCountFiles(FileList, FileMask,
sDestPath, FileList.CurrentDirectory,
CreatedPaths);
WCXModule := Self; // set WCXModule variable to current module
SetChangeVolProc(ArcHandle, ChangeVolProc);
SetProcessDataProc(ArcHandle, ProcessDataProc);
while (ReadWCXHeader(ArcHandle, Header) = E_SUCCESS) do
try
// Now check if the file is to be extracted.
if (not FPS_ISDIR(Header.FileAttr)) // Omit directories (we handle them ourselves).
and MatchesFileList(FileList, Header.FileName) // Check if it's included in the filelist
and ((FileMask = '*.*') or (FileMask = '*') // And name matches file mask
or MatchesMaskList(ExtractFileName(Header.FileName), FileMask))
then
begin
TargetFileName := sDestPath + ExtractDirLevel(FileList.CurrentDirectory, Header.FileName);
iResult := ProcessFile(ArcHandle, PK_EXTRACT, nil, PChar(UTF8ToSys(TargetFileName)));
//Check for errors
if iResult <> E_SUCCESS then
begin
if Assigned(CT) then
begin
// write log error
if (log_arc_op in gLogOptions) and (log_errors in gLogOptions) then
logWrite(CT, Format(rsMsgLogError+rsMsgLogExtract, [FArchiveName + PathDelim + Header.FileName + ' -> ' + TargetFileName]), lmtError);
// Standart error modal dialog
CT.Synchronize(ShowErrorMessage)
end
else
begin
// write log error
if (log_arc_op in gLogOptions) and (log_errors in gLogOptions) then
logWrite(Format(rsMsgLogError+rsMsgLogExtract, [FArchiveName + PathDelim + Header.FileName + ' -> ' + TargetFileName]), lmtError);
// Standart error modal dialog
ShowErrorMessage;
end;
// user abort operation
if iResult = E_EABORTED then Break;
end // Error
else
begin
if Assigned(CT) then
begin
// write log success
if (log_arc_op in gLogOptions) and (log_success in gLogOptions) then
logWrite(CT, Format(rsMsgLogSuccess+rsMsgLogExtract, [FArchiveName + PathDelim + Header.FileName +' -> ' + TargetFileName]), lmtSuccess);
end
else
begin
// write log success
if (log_arc_op in gLogOptions) and (log_success in gLogOptions) then
logWrite(Format(rsMsgLogSuccess+rsMsgLogExtract, [FArchiveName + PathDelim + Header.FileName + ' -> ' + TargetFileName]), lmtSuccess);
end;
end; // Success
end // Extract
else // Skip
begin
iResult := ProcessFile(ArcHandle, PK_SKIP, nil, nil);
//Check for errors
if iResult <> E_SUCCESS then
if Assigned(CT) then
CT.Synchronize(ShowErrorMessage)
else
ShowErrorMessage;
end; // Skip
finally
FreeAndNil(Header);
end;
CloseArchive(ArcHandle);
Result := True;
SetDirsAttributes(CreatedPaths);
finally
FreeAndNil(FileList);
FreeAndNil(CreatedPaths);
end;
end;
function TWCXModule.WCXCopyIn(var FileList: TFileList; sDestPath: String; Flags: Integer) : Boolean;
var
pDestPath : PChar;
begin
DebugLN('VFSCopyIn =' + FArchiveName);
FPercent := 0;
sDestPath := ExtractDirLevel(FArchiveName + PathDelim, sDestPath);
sDestPath := ExcludeTrailingPathDelimiter(sDestPath);
DebugLN('sDstPath == ' + sDestPath);
sDestPath := UTF8ToSys(sDestPath);
if sDestPath = '' then
pDestPath := nil
else
pDestPath := PChar(sDestPath); // Make pointer to local variable
(* Add in file list files from subfolders *)
(* Make filenames relative to FileList.CurrentDirectory *)
FillAndCount(FileList, FFilesSize);
DebugLN('Curr Dir := ' + FileList.CurrentDirectory);
WCXModule := Self; // set WCXModule variable to current module
SetChangeVolProc(INVALID_HANDLE_VALUE, ChangeVolProc);
SetProcessDataProc(INVALID_HANDLE_VALUE, ProcessDataProc);
iResult := PackFiles(PChar(UTF8ToSys(FArchiveName)),
pDestPath, // no trailing path delimiter here
PChar(UTF8ToSys(IncludeTrailingPathDelimiter(FileList.CurrentDirectory))), // end with path delimiter here
PChar(UTF8ToSys(GetFileList(FileList, OP_PACK))), // Convert TFileList into PChar
Flags);
//Check for errors
if iResult <> E_SUCCESS then
begin
if Assigned(CT) then
begin
// write log error
if (log_arc_op in gLogOptions) and (log_errors in gLogOptions) then
logWrite(CT, Format(rsMsgLogError+rsMsgLogPack, [FArchiveName]), lmtError);
// Standart error modal dialog
CT.Synchronize(ShowErrorMessage)
end
else
begin
// write log error
if (log_arc_op in gLogOptions) and (log_errors in gLogOptions) then
logWrite(Format(rsMsgLogError+rsMsgLogPack, [FArchiveName]), lmtError);
// Standart error modal dialog
ShowErrorMessage;
end;
end // Error
else
begin
if Assigned(CT) then
begin
// write log success
if (log_arc_op in gLogOptions) and (log_success in gLogOptions) then
logWrite(CT, Format(rsMsgLogSuccess+rsMsgLogPack, [FArchiveName]), lmtSuccess);
end
else
begin
// write log success
if (log_arc_op in gLogOptions) and (log_success in gLogOptions) then
logWrite(Format(rsMsgLogSuccess+rsMsgLogPack, [FArchiveName]), lmtSuccess);
end;
end; // Success
FreeAndNil(FileList);
Result := True;
end;
function TWCXModule.WCXDelete(var FileList: TFileList) : Boolean;
var
iResult: Integer;
begin
FPercent := 0;
// Convert file list so that filenames are relative to archive root.
ChangeFileListRoot(FArchiveName + PathDelim, FileList);
CountFiles(FileList, '*.*');
WCXModule := Self; // set WCXModule variable to current module
SetChangeVolProc(INVALID_HANDLE_VALUE, ChangeVolProc);
SetProcessDataProc(INVALID_HANDLE_VALUE, ProcessDataProc);
iResult := DeleteFiles(PChar(UTF8ToSys(FArchiveName)),
PChar(UTF8ToSys(GetFileList(FileList, OP_DELETE))));
//Check for errors
if iResult <> E_SUCCESS then
begin
if Assigned(CT) then
CT.Synchronize(ShowErrorMessage)
else
ShowErrorMessage;
end;
FreeAndNil(FileList);
end;
procedure TWCXModule.CreateDirsAndCountFiles(const FileList: TFileList; FileMask: String;
sDestPath: String; CurrentArchiveDir: String;
var CreatedPaths: TStringHashList);
var
// List of paths that we know must be created.
PathsToCreate: TStringHashList;
// List of possible directories to create with their attributes.
// This hash list is created to speed up searches for attributes in archive file list.
DirsAttributes: TStringHashList;
i: Integer;
CurrentFileName: String;
Header: TWCXHeader;
Directories: TStringList;
PathIndex: Integer;
ListIndex: Integer;
TargetDir: String;
begin
FFilesSize := 0;
{ First, collect all the paths that need to be created and their attributes. }
PathsToCreate := TStringHashList.Create(True);
DirsAttributes := TStringHashList.Create(True);
for i := 0 to FArcFileList.Count - 1 do
begin
Header := TWCXHeader(FArcFileList.Items[i]);
// Check if the file from the archive fits the selection given via FileList.
if not MatchesFileList(FileList, Header.FileName) then
Continue;
if FPS_ISDIR(Header.FileAttr) then
begin
CurrentFileName := ExtractDirLevel(CurrentArchiveDir, Header.FileName);
// Save this directory and a pointer to its entry.
DirsAttributes.Add(CurrentFileName, Header);
// If extracting all files and directories, add this directory
// to PathsToCreate so that empty directories are also created.
if (FileMask = '*.*') or (FileMask = '*') then
begin
// Paths in PathsToCreate list must end with path delimiter.
CurrentFileName := IncludeTrailingPathDelimiter(CurrentFileName);
if PathsToCreate.Find(CurrentFileName) < 0 then
PathsToCreate.Add(CurrentFileName);
end;
end
else
begin
if ((FileMask = '*.*') or (FileMask = '*') or
MatchesMaskList(ExtractFileName(Header.FileName), FileMask)) then
begin
Inc(FFilesSize, Header.UnpSize);
CurrentFileName := ExtractDirLevel(CurrentArchiveDir, ExtractFilePath(Header.FileName));
// If CurrentFileName is empty now then it was a file in current archive
// directory, therefore we don't have to create any paths for it.
if Length(CurrentFileName) > 0 then
if PathsToCreate.Find(CurrentFileName) < 0 then
PathsToCreate.Add(CurrentFileName);
end;
end;
end;
{ Second, create paths and save which paths were created and their attributes. }
Directories := TStringList.Create;
try
sDestPath := IncludeTrailingPathDelimiter(sDestPath);
// Create path to destination directory (we don't have attributes for that).
ForceDirectory(sDestPath);
CreatedPaths.Clear;
for PathIndex := 0 to PathsToCreate.Count - 1 do
begin
Directories.Clear;
// Create also all parent directories of the path to create.
// This adds directories to list in order from the outer to inner ones,
// for example: dir, dir/dir2, dir/dir2/dir3.
if GetDirs((PathsToCreate.List + PathIndex)^.Key, Directories) <> -1 then
try
for i := 0 to Directories.Count - 1 do
begin
TargetDir := sDestPath + Directories.Strings[i];
if (CreatedPaths.Find(TargetDir) = -1) and
(not DirPathExists(TargetDir)) then
begin
if ForceDirectory(TargetDir) = False then
begin
// Error, cannot create directory.
Break; // Don't try to create subdirectories.
end
else
begin
// Retrieve attributes for this directory, if they are stored.
ListIndex := DirsAttributes.Find(Directories.Strings[i]);
if ListIndex <> -1 then
Header := (DirsAttributes.List + ListIndex)^.Data
else
Header := nil;
CreatedPaths.Add(TargetDir, Header);
end;
end;
end;
except
end;
end;
finally
FreeAndNil(PathsToCreate);
FreeAndNil(DirsAttributes);
FreeAndNil(Directories);
end;
end;
function TWCXModule.SetDirsAttributes(const Paths: TStringHashList): Boolean;
var
PathIndex: Integer;
TargetDir: String;
Header: TWCXHeader;
Time: Longint;
begin
Result := True;
for PathIndex := 0 to Paths.Count - 1 do
begin
// Get attributes.
Header := TWCXHeader((Paths.List + PathIndex)^.Data);
if Assigned(Header) then
begin
TargetDir := (Paths.List + PathIndex)^.Key;
try
{$IF DEFINED(MSWINDOWS)}
// Restore attributes, e.g., hidden, read-only.
// On Unix attributes value would have to be translated somehow.
mbFileSetAttr(TargetDir, Header.FileAttr);
{$ENDIF}
Time := Header.FileTime;
// Set creation, modification time
mbFileSetTime(TargetDir, Time, Time, Time);
except
Result := False;
end;
end;
end;
end;
procedure TWCXModule.CountFiles(const FileList: TFileList; FileMask: String);
var
i: Integer;
Header: TWCXHeader;
begin
FFilesSize := 0;
for i := 0 to FArcFileList.Count - 1 do
begin
Header := TWCXHeader(FArcFileList.Items[I]);
// Check if the file from the archive fits the selection given via FileList.
if (not FPS_ISDIR(Header.FileAttr)) // Omit directories
and MatchesFileList(FileList, Header.FileName) // Check if it's included in the filelist
and ((FileMask = '*.*') or (FileMask = '*') // And name matches file mask
or MatchesMaskList(ExtractFileName(Header.FileName), FileMask))
then
Inc(FFilesSize, Header.UnpSize);
end;
end;
procedure TWCXModule.PrepareDialog(Operation: TWCXOperation);
begin
FFileOpDlg:= TfrmFileOp.Create(nil);
FFileOpDlg.iProgress1Max:=100;
FFileOpDlg.iProgress2Max:=100;
case Operation of
OP_EXTRACT: FFileOpDlg.Caption := rsDlgExtract;
OP_PACK : FFileOpDlg.Caption := rsDlgPack;
OP_DELETE : FFileOpDlg.Caption := rsDlgDel;
end;
FFileOpDlg.Thread := TThread(CT);
FFileOpDlg.Show;
end;
procedure TWCXModule.FinishDialog;
begin
FFileOpDlg.Close;
FFileOpDlg := nil;
end;
function TWCXModule.ReadWCXHeader(hArcData: TArcHandle;
out HeaderData: TWCXHeader): Integer;
var
ArcHeader : THeaderData;
ArcHeaderEx : THeaderDataEx;
begin
HeaderData := nil;
if Assigned(ReadHeaderEx) then
begin
FillChar(ArcHeaderEx, SizeOf(ArcHeaderEx), #0);
Result := ReadHeaderEx(hArcData, ArcHeaderEx);
if Result = E_SUCCESS then
begin
HeaderData := TWCXHeader.Create(PHeaderDataEx(@ArcHeaderEx));
end;
end
else if Assigned(ReadHeader) then
begin
FillChar(ArcHeader, SizeOf(ArcHeader), #0);
Result := ReadHeader(hArcData, ArcHeader);
if Result = E_SUCCESS then
begin
HeaderData := TWCXHeader.Create(PHeaderData(@ArcHeader));
end;
end
else
begin
Result := E_NOT_SUPPORTED;
end;
end;
{Extract files from archive}
function TWCXModule.VFSCopyOut(var flSrcList: TFileList; sDstPath: String;
Flags: Integer): Boolean;
begin
CT := nil;
PrepareDialog(OP_EXTRACT);
Result := WCXCopyOut(flSrcList, sDstPath, Flags);
FinishDialog;
end;
{Pack files}
function TWCXModule.VFSCopyIn(var flSrcList: TFileList; sDstName: String; Flags : Integer
): Boolean;
begin
CT := nil;
PrepareDialog(OP_PACK);
Result := WCXCopyIn(flSrcList, sDstName, Flags);
FinishDialog;
end;
{Extract files from archive in thread}
function TWCXModule.VFSCopyOutEx(var flSrcList: TFileList; sDstPath: String;
Flags: Integer): Boolean;
begin
// check if other operations are running
if not IsBlocked then
begin
CT := TWCXCopyThread.Create(Self, OP_EXTRACT, flSrcList, sDstPath, Flags);
PrepareDialog(OP_EXTRACT);
CT.Resume;
Result := True;
end
else
begin
Result := False;
FreeAndNil(flSrcList);
end;
end;
{Pack files in thread}
function TWCXModule.VFSCopyInEx(var flSrcList: TFileList; sDstName: String; Flags : Integer
): Boolean;
begin
// check if other operations are running
if not IsBlocked then
begin
CT := TWCXCopyThread.Create(Self, OP_PACK, flSrcList, sDstName, Flags);
PrepareDialog(OP_PACK);
CT.Resume;
Result := True;
end
else
begin
Result := False;
FreeAndNil(flSrcList);
end;
end;
function TWCXModule.VFSRename(const sSrcName, sDstName: String): Boolean;
begin
end;
function TWCXModule.VFSRun(const sName: String): Boolean;
var
iCount, I: Integer;
Header: TWCXHeader;
begin
iCount := FArcFileList.Count - 1;
for I := 0 to iCount do
begin
Header := TWCXHeader(FArcFileList.Items[I]);
if PathDelim + Header.FileName = FFolder + sName then
begin
Result := ShowPackInfoDlg(Self, Header);
Exit;
end;
end;
Result:= False;
end;
function TWCXModule.VFSDelete(var flNameList: TFileList): Boolean;
begin
CT := nil;
PrepareDialog(OP_DELETE);
Result := WCXDelete(flNameList);
FinishDialog;
end;
function TWCXModule.VFSList(const sDir: String; var fl: TFileList): Boolean;
var
fr : TFileRecItem;
I, Count : Integer;
CurrFileName : String; // Current file name
begin
fl.Clear;
FFolder := sDir; // save current folder
AddUpLevel(GetParentDir(sDir), fl);
DebugLN('GetParentDir(sDir) = ' + GetParentDir(sDir));
Count := FArcFileList.Count - 1;
for I := 0 to Count do
begin
CurrFileName := PathDelim + TWCXHeader(FArcFileList.Items[I]).FileName;
if not IsInPath(sDir, CurrFileName, False) then
Continue;
FillByte(fr, SizeOf(fr), 0);
with fr, TWCXHeader(FArcFileList.Items[I]) do
begin
sName := ExtractFileName(CurrFileName);
iMode := FileAttr;
sModeStr := AttrToStr(iMode);
bLinkIsDir := False;
bSelected := False;
if FPS_ISDIR(iMode) then
sExt:=''
else
sExt:=ExtractFileExt(sName);
sNameNoExt:=Copy(sName,1,length(sName)-length(sExt));
sPath := sDir;
try
fTimeI := FileDateToDateTime(FileTime);
except
fTimeI := 0;
end;
sTime := FormatDateTime(gDateTimeFormat, fTimeI);
iSize := UnpSize;
end; //with
fl.AddItem(@fr);
end;
end;
function TWCXModule.VFSMisc: PtrUInt;
begin
if Assigned(GetPackerCaps) then
Result := GetPackerCaps
else
Result := 0;
end;
{ TWCXCopyThread }
constructor TWCXCopyThread.Create(WCXModule: TWCXModule;
Operation: TWCXOperation; var FileList: TFileList;
Path: String; Flags: Integer);
begin
inherited Create(True, DefaultStackSize);
FreeOnTerminate := True;
OnTerminate := Terminating;
FWCXModule := WCXModule;
FOperation := Operation;
FFileList := FileList;
FPath := Path;
FFlags:= Flags;
end;
procedure TWCXCopyThread.Execute;
begin
// main archive thread code started here
with FWCXModule do
begin
try
case FOperation of
OP_EXTRACT:
WCXCopyOut(FFileList, FPath, FFlags);
OP_PACK:
WCXCopyIn(FFileList, FPath, FFlags);
OP_DELETE:
WCXDelete(FFileList);
end;
except
DebugLN('Error in "WCXCopyThread.Execute"');
end;
Synchronize(FinishDialog);
CT := nil;
end; //with
end;
procedure TWCXCopyThread.Terminating(Sender: TObject);
begin
// Last chance to clean up if there was an error.
if Assigned(FFileList) then
FreeAndNil(FFileList);
if Assigned(FWCXModule) and Assigned(FWCXModule.FFileOpDlg) then
Synchronize(FWCXModule.FinishDialog);
FWCXModule.CT := nil;
end;
function IsBlocked : Boolean;
begin
Result := Assigned(WCXModule);
if Result then
with WCXModule do
begin
Result := Assigned(FFileOpDlg);
if Result then
if Assigned(CT) then
CT.Synchronize(FFileOpDlg.ShowOnTop)
else
FFileOpDlg.ShowOnTop;
end; // with
end;
{ TWCXModuleList }
function TWCXModuleList.GetAEnabled(Index: Integer): Boolean;
begin
Result:= Boolean(Objects[Index]);
end;
function TWCXModuleList.GetAExt(Index: Integer): String;
begin
Result:= Names[Index];
end;
function TWCXModuleList.GetAFileName(Index: Integer): String;
var
sCurrPlugin: String;
iPosComma : Integer;
begin
sCurrPlugin:= ValueFromIndex[Index];
iPosComma:= Pos(',', sCurrPlugin);
//get file name
Result:= Copy(sCurrPlugin, iPosComma + 1, Length(sCurrPlugin) - iPosComma);
end;
function TWCXModuleList.GetAFlags(Index: Integer): PtrInt;
var
sCurrPlugin: String;
iPosComma : Integer;
begin
sCurrPlugin:= ValueFromIndex[Index];
iPosComma:= Pos(',', sCurrPlugin);
// get packer flags
Result:= StrToInt(Copy(sCurrPlugin, 1, iPosComma-1));
end;
procedure TWCXModuleList.SetAEnabled(Index: Integer; const AValue: Boolean);
begin
Objects[Index]:= TObject(AValue);
end;
procedure TWCXModuleList.SetAFileName(Index: Integer; const AValue: String);
begin
ValueFromIndex[Index]:= IntToStr(GetAFlags(Index)) + #44 + AValue;
end;
procedure TWCXModuleList.SetAFlags(Index: Integer; const AValue: PtrInt);
begin
ValueFromIndex[Index]:= IntToStr(AValue) + #44 + GetAFileName(Index);
end;
procedure TWCXModuleList.SetExt(Index: Integer; const AValue: String);
var
sValue : String;
begin
sValue:= ValueFromIndex[Index];
Self[Index]:= AValue + '=' + sValue;
end;
procedure TWCXModuleList.Load(Ini: TIniFileEx);
var
I: Integer;
sCurrPlugin,
sValue: String;
begin
Ini.ReadSectionRaw('PackerPlugins', Self);
for I:= 0 to Count - 1 do
if Pos('#', Names[I]) = 0 then
begin
Enabled[I]:= True;
end
else
begin
sCurrPlugin:= Names[I];
sValue:= ValueFromIndex[I];
Self[I]:= Copy(sCurrPlugin, 2, Length(sCurrPlugin) - 1) + '=' + sValue;
Enabled[I]:= False;
end;
end;
procedure TWCXModuleList.Save(Ini: TIniFileEx);
var
I: Integer;
begin
Ini.EraseSection('PackerPlugins');
for I := 0 to Count - 1 do
begin
if Boolean(Objects[I]) then
begin
Ini.WriteString('PackerPlugins', Names[I], ValueFromIndex[I])
end
else
begin
Ini.WriteString('PackerPlugins', '#' + Names[I], ValueFromIndex[I]);
end;
end;
end;
function TWCXModuleList.Add(Ext: String; Flags: PtrInt; FileName: String): Integer;
begin
Result:= AddObject(Ext + '=' + IntToStr(Flags) + #44 + FileName, TObject(True));
end;
function TWCXModuleList.FindFirstEnabledByName(Name: String): Integer;
begin
Result:=0;
while Result < Count do
begin
if Enabled[Result] and (DoCompareText(Names[Result], Name) = 0) then
Exit
else
Result := Result + 1;
end;
if Result=Count then Result:=-1;
end;
{ TWCXHeader }
constructor TWCXHeader.Create(const Data: PHeaderData);
begin
ArcName := PCharLToUTF8(Data^.ArcName, SizeOf(Data^.ArcName));
FileName := PCharLToUTF8(Data^.FileName, SizeOf(Data^.FileName));
Flags := Data^.Flags;
HostOS := Data^.HostOS;
FileCRC := Data^.FileCRC;
FileTime := Data^.FileTime;
UnpVer := Data^.UnpVer;
Method := Data^.Method;
FileAttr := Data^.FileAttr;
PackSize := Data^.PackSize;
UnpSize := Data^.UnpSize;
if Assigned(Data^.CmtBuf) then
Cmt := PCharLToUTF8(Data^.CmtBuf, Data^.CmtSize);
CmtState := Data^.CmtState;
end;
constructor TWCXHeader.Create(const Data: PHeaderDataEx);
function Combine64(High, Low: Longint): Int64;
begin
Result := Int64(High) shl (SizeOf(Int64) shl 2);
Result := Result + Int64(Low);
end;
begin
ArcName := PCharLToUTF8(Data^.ArcName, SizeOf(Data^.ArcName));
FileName := PCharLToUTF8(Data^.FileName, SizeOf(Data^.FileName));
Flags := Data^.Flags;
HostOS := Data^.HostOS;
FileCRC := Data^.FileCRC;
FileTime := Data^.FileTime;
UnpVer := Data^.UnpVer;
Method := Data^.Method;
FileAttr := Data^.FileAttr;
PackSize := Combine64(Data^.PackSizeHigh, Data^.PackSize);
UnpSize := Combine64(Data^.UnpSizeHigh, Data^.UnpSize);
if Assigned(Data^.CmtBuf) then
Cmt := PCharLToUTF8(Data^.CmtBuf, Data^.CmtSize);
CmtState := Data^.CmtState;
end;
constructor TWCXHeader.Create;
begin
end;
function TWCXHeader.PCharLToUTF8(CharString: PChar; MaxSize: Integer): UTF8String;
var
NameLength: Integer;
TempString: AnsiString;
begin
NameLength := strlen(CharString);
if NameLength > MaxSize then
NameLength := MaxSize;
SetString(TempString, CharString, NameLength);
Result := SysToUTF8(TempString);
end;
end.