948 lines
30 KiB
Plaintext
948 lines
30 KiB
Plaintext
unit ArchiveManagerUnit;
|
|
{-------------------------------------------------------------------------------
|
|
Archive Manager
|
|
---------------
|
|
reSource v2.6
|
|
Copyright (C) 1998-2001 Victor Kasenda / gruv
|
|
http://go.to/gruv
|
|
email: vickas@singnet.com.sg
|
|
|
|
|
|
Desc:
|
|
The archive manager is the engine to operate on the archive file.
|
|
It defines how the operations add/delete/extract/property change is
|
|
to be performed.
|
|
|
|
An ArchiveManager is assigned to each archive. Files can be added, deleted
|
|
and modified from the archive. File attributes can also be changed.
|
|
|
|
Notes:
|
|
The Add procedure chops a file into several smaller blocks and adds them
|
|
to an archive. If the file to compress is smaller than a block, the file size
|
|
is used instead.
|
|
|
|
To Use:
|
|
Create the archive manager. One archive manager can operate on only one
|
|
archive at a time.
|
|
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
(**) interface (**)
|
|
uses Windows, Forms, SysUtils, Classes, Dialogs,
|
|
{CG}
|
|
ErrorUnit,
|
|
BWTCompressUnit, BWTExpandUnit, EDosUnit, ArchiveFileUnit, StructsUnit;
|
|
|
|
type
|
|
{Exceptions}
|
|
EArchiveOpenError = class(Exception)
|
|
constructor Create;
|
|
end;
|
|
EUserCancel = class(Exception);
|
|
EFileNotExtracted = class(Exception);
|
|
EFileNothingDone = class(Exception);
|
|
|
|
TArchiveManager = class
|
|
private
|
|
Expander: TExpander;
|
|
|
|
// add properties. set only during add.
|
|
infile_size: integer;
|
|
|
|
protected
|
|
FOnCentralDirChange: TNotifyEvent;
|
|
procedure CentralDirChange;
|
|
|
|
function GetTempFileName: string;
|
|
|
|
procedure StartTempProcessing(var TempFile: TArchiveFile);
|
|
procedure EndTempProcessing(TempFile: TArchiveFile);
|
|
|
|
procedure ShowProgress(a: integer); virtual;
|
|
procedure ShowStatusMsg(s: string); virtual;
|
|
procedure AddLog(s: string); virtual; // for debugging. logging operations
|
|
public
|
|
ArchiveFile: TArchiveFile; // Contains the Central Dir (List of files in archive)
|
|
{archive file property}
|
|
archive_file_folder, archive_file_name, archive_file_full_path: string;
|
|
|
|
{------ MUST SET THE FOLLOWING -------------------}
|
|
{Parameters. Must set before calling any action}
|
|
TempDir: string; // The temporary dir to use
|
|
{extract options. set before calling ExtractFile}
|
|
dest_dir: string;
|
|
{-------------------------------------------------}
|
|
use_folder_names: boolean; // folders not implemented. ignore this
|
|
|
|
// used to count number of bytes processed in FSortUnit
|
|
// Archive manager will reset every file
|
|
bytes_processed: integer;
|
|
|
|
{These events can be assigned}
|
|
OnShowProgress: TIntEvent; {Progress bar not implemented because of new algo}
|
|
OnShowStatusMsg: TStrEvent;
|
|
OnAddLog: TStrEvent;
|
|
property OnCentralDirChange: TNotifyEvent read FOnCentralDirChange write FOnCentralDirChange;
|
|
|
|
{add properties. read only.}
|
|
property AddFileSize: integer read infile_size;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
{operations}
|
|
procedure OpenArchive(const new_archive_file_name: string; const create_new_prompt: boolean);
|
|
procedure CloseArchive;
|
|
function IsArchiveOpen: boolean;
|
|
function AddFiles(FileList: TStrings; const infile_dir: string): integer;
|
|
procedure ExtractList(List: TList;
|
|
var files_extracted, extracted_size: integer);
|
|
procedure ExtractListToTemp(List: TList;
|
|
var files_extracted, extracted_size: integer; var temp_dir: string);
|
|
procedure DeleteFiles;
|
|
procedure WriteCentralDir;
|
|
|
|
{operations support}
|
|
procedure CopyData(SourceFile, DestFile: TArchiveFile; SourceCentralDir: TCentralDir);
|
|
function GetTempDir: string;
|
|
end;
|
|
|
|
var
|
|
Compressor: TCompressor; // one compressor class use multiple. all memory is allocated upon creation
|
|
|
|
(**) implementation (**)
|
|
uses ArchiveHeadersUnit;
|
|
|
|
constructor EArchiveOpenError.Create;
|
|
begin
|
|
inherited Create('Error opening archive');
|
|
end;
|
|
|
|
{constructor EUserCancle.Create;
|
|
begin
|
|
inherited Create('User canceled operation');
|
|
end;}
|
|
|
|
|
|
|
|
constructor TArchiveManager.Create;
|
|
begin
|
|
inherited Create;
|
|
dest_dir := '';
|
|
TempDir := 'c:\';
|
|
Compressor := TCompressor.Create;
|
|
Expander := TExpander.Create;
|
|
end;
|
|
|
|
destructor TArchiveManager.Destroy;
|
|
begin
|
|
Compressor.Free;
|
|
Expander.free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
CentralDirChange
|
|
----------------
|
|
will call the event handler if it is assigned
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.CentralDirChange;
|
|
begin
|
|
if Assigned(FOnCentralDirChange) then FOnCentralDirChange(Self);
|
|
end;
|
|
|
|
procedure TArchiveManager.CloseArchive;
|
|
begin
|
|
FreeAndNil(ArchiveFile);
|
|
AddLog('Archive Closed - ' + archive_file_full_path);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
OpenArchive
|
|
-----------
|
|
will open the archive if new_archive_file_name
|
|
will display a prompt to create a new archive if requested
|
|
|
|
will append SRESOURCE_EXT to the end of the file name if an extension does not exist
|
|
to prevent a opening of a directory. opening files without extensions is not
|
|
supported.
|
|
|
|
Desc:
|
|
inits archive_file_name, archive_file_folder, archive_file_full_path
|
|
|
|
Notes:
|
|
The full path of the file should be passed to prevent any dir confusion.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.OpenArchive(const new_archive_file_name: string; const create_new_prompt: boolean);
|
|
begin
|
|
archive_file_full_path := ExpandFileName(new_archive_file_name);
|
|
archive_file_folder := ExtractFilePath(archive_file_full_path);
|
|
archive_file_name := ExtractFileName(archive_file_full_path);
|
|
|
|
// search for the ending 'dot' in the archive_file_name.
|
|
// if it does not exist, add one to archive_file_full_path and archive_file_name
|
|
if Pos('.', archive_file_name) = 0 then
|
|
begin
|
|
archive_file_full_path := archive_file_full_path + '.';
|
|
archive_file_name := archive_file_name + '.';
|
|
end;
|
|
|
|
// search for the extension. If it does not exist, add it.
|
|
if archive_file_name[length(archive_file_name)] = '.' then
|
|
begin
|
|
archive_file_full_path := archive_file_full_path + SRESOURCE_EXT;
|
|
archive_file_name := archive_file_name + SRESOURCE_EXT;
|
|
end;
|
|
|
|
|
|
// Change to the directory of the archive to open it
|
|
CHDir(archive_file_folder);
|
|
|
|
// Open the archive file.
|
|
// If the archive does not exist, then create a new one.
|
|
if FileExists(archive_file_name) then
|
|
ArchiveFile := TArchiveFile.CreateNew(archive_file_name, true)
|
|
else
|
|
begin
|
|
|
|
// check if the user really wants a new archive created
|
|
if create_new_prompt then
|
|
if (Application.MessageBox(PChar('The archive file ' + archive_file_full_path + ' does not exist. Do you want to create a new file?'),
|
|
'Create new archive?', MB_YESNOCANCEL) <> IDYES) then raise EUserCancel.Create('Create new archive cancelled');
|
|
|
|
ArchiveFile := TArchiveFile.CreateNew(archive_file_name, false);
|
|
end;
|
|
|
|
CentralDirChange;
|
|
|
|
AddLog('Archive Opened - ' + archive_file_full_path);
|
|
AddLog('CentralDir Loaded');
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
GetTempFileName
|
|
---------------
|
|
|
|
Desc:
|
|
Uses the winows API GetTempFileName. the temporary file will have an 'RS'
|
|
prefix.
|
|
-------------------------------------------------------------------------------}
|
|
function TArchiveManager.GetTempFileName: string;
|
|
var
|
|
name: PChar;
|
|
s: string;
|
|
begin
|
|
name := StrAlloc(MAX_PATH + 1);
|
|
Windows.GetTempFileName(PChar(TempDir), 'RSVICTORK', 0, name);
|
|
s := string(name);
|
|
StrDispose(name);
|
|
result := s;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
StartTempProcessing
|
|
-------------------
|
|
Creates a new TempFile and seeks to the position to start adding data
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.StartTempProcessing(var TempFile: TArchiveFile);
|
|
begin
|
|
TempFile := TArchiveFile.CreateNew(GetTempFileName, false);
|
|
TempFile.SeekToDataStart;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
EndTempProcessing
|
|
-----------------
|
|
|
|
Desc:
|
|
closes the current archive
|
|
deletes the archive
|
|
renames the temp archive to replace the current archive
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.EndTempProcessing(TempFile: TArchiveFile);
|
|
var
|
|
temp_file_name: string;
|
|
begin
|
|
// save file names since we are freeing the objects
|
|
temp_file_name := TempFile.filename;
|
|
// close TempFile and ArchiveFile to perform file operations
|
|
TempFile.Free;
|
|
CloseArchive;
|
|
// perform operations
|
|
DeleteFile(archive_file_full_path);
|
|
RenameFile(temp_file_name, archive_file_full_path);
|
|
// temp file is now the new archive file
|
|
OpenArchive(archive_file_full_path, true);
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
WriteCentralDir
|
|
---------------
|
|
Rewrites the central dir in memory to the archive file
|
|
Used when a file property in the archive changes and the CentralDir
|
|
has to be rewritten to reflect the change.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.WriteCentralDir;
|
|
var
|
|
TempFile: TArchiveFile;
|
|
begin
|
|
StartTempProcessing(TempFile);
|
|
CopyData(ArchiveFile, TempFile, ArchiveFile.CentralDir);
|
|
ArchiveFile.CentralDir.WriteToFile(TempFile);
|
|
AddLog('Write CentralDir OK.');
|
|
EndTempProcessing(TempFile);
|
|
CentralDirChange;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
CopyData
|
|
--------
|
|
|
|
Desc: Copies the data portion from SourceFile to DestFile, using
|
|
info from CentralDir (the source file's central dir)
|
|
If no data, the SourceFile and DestFile is seeked to DataStartPos
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.CopyData(SourceFile, DestFile: TArchiveFile; SourceCentralDir: TCentralDir);
|
|
var
|
|
bytes_to_copy: integer;
|
|
begin
|
|
bytes_to_copy := SourceCentralDir.GetCentralDirOffset - RESOURCE_ARCHIVE_HEADER_SIZE;
|
|
DestFile.DisableBuf;
|
|
SourceFile.SeekToDataStart;
|
|
DestFile.SeekToDataStart;
|
|
ArchiveFileBlockCopy(SourceFile, DestFile, bytes_to_copy);
|
|
DestFile.EnableBuf;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
GetTempDir
|
|
|
|
Returns a temporary directory that is empty
|
|
the temp dir is in the default temp dir
|
|
-------------------------------------------------------------------------------}
|
|
function TArchiveManager.GetTempDir: string;
|
|
var
|
|
s: string;
|
|
OK: boolean;
|
|
i: integer;
|
|
begin
|
|
i := 0;
|
|
OK := false;
|
|
result := '';
|
|
|
|
repeat
|
|
try
|
|
s := IncludeTrailingBackslash(TempDir) + 'rs' {'This folder is safe to delete if reSource is closed '} + IntToStr(i);
|
|
MKDir(s);
|
|
// if we reached here, then the dir has been created with no exception
|
|
OK := true;
|
|
except
|
|
on E: EInOutError do
|
|
begin
|
|
{183 - dir exists try again}
|
|
if (E.ErrorCode <> 183) then
|
|
begin
|
|
{5 and other values - drive not ready}
|
|
ShowError('Cannot create temp directory. ' + IntToStr(E.ErrorCode));
|
|
raise; // unable to handle. exit and abandon operation.
|
|
end;
|
|
end; // EInOutError
|
|
end;
|
|
inc(i);
|
|
|
|
{really cannot create after 300 attempts, then abandon operation}
|
|
if i = 300 then
|
|
begin
|
|
// ShowError('Temp dir may be full. Tried ' + IntToStr(i) + ' times.');
|
|
raise EInOutError.Create('Temp dir may be full. Tried ' + IntToStr(i) + ' times.');
|
|
end;
|
|
until OK;
|
|
|
|
result := s;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
DeleteFiles
|
|
|
|
Algo:
|
|
Basically works with the central file directory
|
|
- Delete the file headers from the central dir with the deleted flag set.
|
|
- rebuild a new archive with the new central directory, updating the central
|
|
dir with the new data offsets.
|
|
- delete old archive, rename new archive.
|
|
|
|
Notes:
|
|
verbal explanation of how total_data_size is calculated:
|
|
total_data_size := total_compressed_size + totol_size_of_data_headers
|
|
- total_size_of_data_headers := DATA_HEADER_SIZE * num_data_blocks
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.DeleteFiles;
|
|
var
|
|
i: integer; // counter
|
|
CentralFileHeader: TCentralFileHeader;
|
|
TempFile: TArchiveFile;
|
|
total_data_size: integer;
|
|
begin
|
|
with ArchiveFile do
|
|
begin
|
|
for i := CentralDir.Count-1 downto 0 do
|
|
begin
|
|
CentralFileHeader := TCentralFileHeader(CentralDir[i]);
|
|
if CentralFileHeader.Deleted then CentralDir.Delete(i);
|
|
end;
|
|
end;
|
|
|
|
// rebuild archive
|
|
// data: read from ArchiveFile, write to TempFile
|
|
StartTempProcessing(TempFile);
|
|
ArchiveFile.DisableBuf;
|
|
TempFile.DisableBuf;
|
|
for i := 0 to ArchiveFile.CentralDir.Count-1 do
|
|
begin
|
|
CentralFileHeader := ArchiveFile.CentralDir[i] as TCentralFileHeader;
|
|
ArchiveFile.Seek(CentralFileHeader.data_offset, soFromBeginning);
|
|
CentralFileHeader.data_offset := TempFile.Position;
|
|
total_data_size := CentralFileHeader.compressed_size + DATA_HEADER_SIZE * CentralFileHeader.num_blocks;
|
|
ArchiveFileBlockCopy(ArchiveFile, TempFile, total_data_size);
|
|
end;
|
|
|
|
// copy over the central dir
|
|
TempFile.EnableBuf;
|
|
ArchiveFile.CentralDir.WriteToFile(TempFile);
|
|
|
|
EndTempProcessing(TempFile);
|
|
CentralDirChange;
|
|
end;
|
|
|
|
{-------------------------------------------------------------------------------
|
|
ExtractList
|
|
|
|
IN Assertion:
|
|
dest_dir, the destination directory is set.
|
|
|
|
Desc:
|
|
Extracts files in a list, which contains pointers to the central file
|
|
header of the files.
|
|
|
|
Algo:
|
|
Sorts the list of indexes according to their data offsets in the archive.
|
|
This is to optimize extraction.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.ExtractList(List: TList;
|
|
var files_extracted, extracted_size: integer);
|
|
|
|
{-------------------------------------------------------------------------------
|
|
ExtractFile
|
|
|
|
IN Assertion:
|
|
dest_dir, the default destination directory is set.
|
|
|
|
Desc:
|
|
Extract the file referred by index in the CentralDir
|
|
Also imprint the file's attributes as stored in the CentralDir
|
|
|
|
Algo:
|
|
Get the CentralFileHeader for the file at index.
|
|
Create the file of filename
|
|
Seek to the data pos in ArchiveFile
|
|
BWTExpand the file
|
|
|
|
Extract directory:
|
|
If full path (drive+dir) specified, it is used.
|
|
If relative path (dir only), then add dest_dir to it.
|
|
If empty path, use dest_dir.
|
|
|
|
If UseFolderNames then
|
|
dir := CentralFileHeader.Folder
|
|
else
|
|
dir := '';
|
|
|
|
if (dir < 2) and (2nd char not a ':') then
|
|
dir := dest_dir + dir;
|
|
|
|
|
|
Notes:
|
|
Will check if destination file exist. EnsureDestFileClear will strip any
|
|
readonly or system bit from the file to overwrite. The Create para will then
|
|
rewrite the file.
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure ExtractFile(CFH: TCentralFileHeader);
|
|
var
|
|
//CentralFileHeader: TCentralFileHeader;
|
|
OutFile: TFileStream;
|
|
i: integer; // counter
|
|
extract_folder: string;
|
|
out_file_path: string;
|
|
|
|
procedure EnsureDestFileClear;
|
|
begin
|
|
if FileExists(out_file_path) then
|
|
case Application.MessageBox(PChar('The file ' + out_file_path + ' exists. Do you want to overwrite the file?'), 'Warning', MB_YESNOCANCEL) of
|
|
IDYES:
|
|
begin
|
|
if (FileSetAttr(out_file_path, faArchive) <> 0) then
|
|
raise EInOutError.Create('Cannot clear dest file attributes');
|
|
end;
|
|
IDNO: raise EFileNotExtracted.Create('Destination file exists. File not extracted.');
|
|
IDCANCEL: raise EUserCancel.Create('Extract operation cancelled');
|
|
end;
|
|
end;
|
|
|
|
|
|
begin
|
|
// reset progress bar
|
|
ShowProgress(0);
|
|
ShowStatusMsg('');
|
|
|
|
//CentralFileHeader := TCentralFileHeader(ArchiveFile.CentralDir[index]);
|
|
|
|
// determine the directory to extract to
|
|
If use_folder_names then
|
|
begin
|
|
extract_folder := CFH.Folder;
|
|
// if it is relative, then must add dest_dir
|
|
if (length(extract_folder) < 2) or
|
|
((length(extract_folder) > 2) and (extract_folder[2] <> ':')) then
|
|
extract_folder := dest_dir + extract_folder;
|
|
end
|
|
else
|
|
extract_folder := dest_dir;
|
|
|
|
// out_file_path is the final full path to the file
|
|
out_file_path := extract_folder + CFH.filename;
|
|
EnsureDestFileClear;
|
|
|
|
// status bar notice
|
|
ShowStatusMsg('Extracting ' + out_file_path);
|
|
AddLog('Total number of blocks - '+IntToStr(CFH.num_blocks));
|
|
|
|
OutFile := TFileStream.Create(out_file_path, fmCreate);
|
|
try
|
|
ArchiveFile.SmartSeek(CFH.data_offset, soFromBeginning);
|
|
for i := 1 to CFH.num_blocks do
|
|
begin
|
|
Expander.ExpandBlock(ArchiveFile, OutFile);
|
|
// update file progress bar and process paint messages
|
|
if Expander.GetLastCRC32Result = true then
|
|
AddLog('Block '+IntToStr(i-1)+' Expand and CRC32 Check OK.')
|
|
else
|
|
AddLog('Block '+IntToStr(i-1)+' Expand Error.');
|
|
|
|
ShowProgress(i * 100 div CFH.num_blocks);
|
|
Application.ProcessMessages;
|
|
end;
|
|
finally
|
|
// set attributes that require the file handle
|
|
FileSetDate(OutFile.Handle, CFH.time);
|
|
OutFile.free;
|
|
// set attributes that require the file path
|
|
FileSetAttr(out_file_path, CFH.attr);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
i: integer;
|
|
CFH: TCentralFileHeader;
|
|
begin
|
|
{Implement sort}
|
|
|
|
files_extracted := 0;
|
|
extracted_size := 0;
|
|
for i := 0 to List.Count-1 do
|
|
begin
|
|
try
|
|
CFH := TCentralFileHeader(List[i]);
|
|
ExtractFile(CFH);
|
|
inc(files_extracted);
|
|
inc(extracted_size, CFH.uncompressed_size);
|
|
except
|
|
on EFileNotExtracted do begin {nothing} end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TArchiveManager.ExtractListToTemp(List: TList;
|
|
var files_extracted, extracted_size: integer; var temp_dir: string);
|
|
begin
|
|
{Create the temporary directory.
|
|
Set dest_dir to the temp dir.
|
|
Call ExtractIndexes to do the extraction}
|
|
|
|
dest_dir := GetTempDir; // set the dest dir
|
|
EDos.AddSlash(dest_dir);
|
|
temp_dir := dest_dir; // return the dest dir
|
|
|
|
ExtractList(List, files_extracted, extracted_size);
|
|
end;
|
|
|
|
(*
|
|
{-------------------------------------------------------------------------------
|
|
ExtractSelIdx
|
|
|
|
IN Assertion:
|
|
dest_dir, the default destination directory is set.
|
|
|
|
Desc:
|
|
Extracts files with their indexes in the index list.
|
|
The index must be the same as the file's index in the central directory.
|
|
|
|
Algo:
|
|
Sorts the list of indexes according to their data offsets in the archive.
|
|
This is to optimize extraction.
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.ExtractIndexes(indexlist: TIndexList;
|
|
var files_extracted, extracted_size: integer);
|
|
|
|
{-------------------------------------------------------------------------------
|
|
ExtractFile
|
|
|
|
IN Assertion:
|
|
dest_dir, the default destination directory is set.
|
|
|
|
Desc:
|
|
Extract the file referred by index in the CentralDir
|
|
Also imprint the file's attributes as stored in the CentralDir
|
|
|
|
Algo:
|
|
Get the CentralFileHeader for the file at index.
|
|
Create the file of filename
|
|
Seek to the data pos in ArchiveFile
|
|
BWTExpand the file
|
|
|
|
Extract directory:
|
|
If full path (drive+dir) specified, it is used.
|
|
If relative path (dir only), then add dest_dir to it.
|
|
If empty path, use dest_dir.
|
|
|
|
If UseFolderNames then
|
|
dir := CentralFileHeader.Folder
|
|
else
|
|
dir := '';
|
|
|
|
if (dir < 2) and (2nd char not a ':') then
|
|
dir := dest_dir + dir;
|
|
|
|
|
|
Notes:
|
|
Will check if destination file exist. EnsureDestFileClear will strip any
|
|
readonly or system bit from the file to overwrite. The Create para will then
|
|
rewrite the file.
|
|
-------------------------------------------------------------------------------}
|
|
|
|
procedure ExtractFile(index: integer);
|
|
var
|
|
CentralFileHeader: TCentralFileHeader;
|
|
OutFile: TFileStream;
|
|
i: integer; // counter
|
|
extract_folder: string;
|
|
out_file_path: string;
|
|
|
|
procedure EnsureDestFileClear;
|
|
begin
|
|
if FileExists(out_file_path) then
|
|
case Application.MessageBox(PChar('The file ' + out_file_path + ' exists. Do you want to overwrite the file?'), 'Warning', MB_YESNOCANCEL) of
|
|
IDYES:
|
|
begin
|
|
if (FileSetAttr(out_file_path, faArchive) <> 0) then
|
|
raise EInOutError.Create('Cannot clear dest file attributes');
|
|
end;
|
|
IDNO: raise EFileNotExtracted.Create('Destination file exists. File not extracted.');
|
|
IDCANCEL: raise EUserCancel.Create('Extract operation cancelled');
|
|
end;
|
|
end;
|
|
|
|
|
|
begin
|
|
// reset progress bar
|
|
MainForm.ShowProgress(0);
|
|
MainForm.ShowStatusMessage('');
|
|
|
|
CentralFileHeader := TCentralFileHeader(ArchiveFile.CentralDir[index]);
|
|
|
|
// determine the directory to extract to
|
|
If use_folder_names then
|
|
begin
|
|
extract_folder := CentralFileHeader.Folder;
|
|
// if it is relative, then must add dest_dir
|
|
if (length(extract_folder) < 2) or
|
|
((length(extract_folder) > 2) and (extract_folder[2] <> ':')) then
|
|
extract_folder := dest_dir + extract_folder;
|
|
end
|
|
else
|
|
extract_folder := dest_dir;
|
|
|
|
// out_file_path is the final full path to the file
|
|
out_file_path := extract_folder + CentralFileHeader.filename;
|
|
EnsureDestFileClear;
|
|
|
|
// status bar notice
|
|
MainForm.ShowStatusMessage('Extracting ' + out_file_path);
|
|
|
|
OutFile := TFileStream.Create(out_file_path, fmCreate);
|
|
try
|
|
ArchiveFile.SmartSeek(CentralFileHeader.data_offset, soFromBeginning);
|
|
for i := 1 to CentralFileHeader.num_blocks do
|
|
begin
|
|
Expander.ExpandBlock(ArchiveFile, OutFile);
|
|
// update file progress bar and process paint messages
|
|
MainForm.ShowProgress(i * 100 div CentralFileHeader.num_blocks);
|
|
Application.ProcessMessages;
|
|
end;
|
|
finally
|
|
// set attributes that require the file handle
|
|
FileSetDate(OutFile.Handle, CentralFileHeader.time);
|
|
OutFile.free;
|
|
// set attributes that require the file path
|
|
FileSetAttr(out_file_path, CentralFileHeader.attr);
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
var
|
|
i: integer;
|
|
begin
|
|
{Implement sort}
|
|
|
|
files_extracted := 0;
|
|
extracted_size := 0;
|
|
for i := 0 to length(indexlist)-1 do
|
|
begin
|
|
try
|
|
ExtractFile(indexlist[i]);
|
|
inc(files_extracted);
|
|
//inc(extracted_size, CentralFileHeader.uncompressed_size);
|
|
except
|
|
on EFileNotExtracted do begin {nothing} end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
|
|
{-------------------------------------------------------------------------------
|
|
ExtractIndexesToTemp
|
|
|
|
Creates the temp dir and extracts to the temp dir
|
|
-------------------------------------------------------------------------------}
|
|
procedure TArchiveManager.ExtractIndexesToTemp(indexlist: TIndexList;
|
|
var files_extracted, extracted_size: integer; var temp_dir: string);
|
|
begin
|
|
{Create the temporary directory.
|
|
Set dest_dir to the temp dir.
|
|
Call ExtractIndexes to do the extraction}
|
|
|
|
dest_dir := GetTempDir; // set the dest dir
|
|
EDos.AddSlash(dest_dir);
|
|
temp_dir := dest_dir; // return the dest dir
|
|
|
|
ExtractIndexes(indexlist, files_extracted, extracted_size);
|
|
end;
|
|
*)
|
|
|
|
{-------------------------------------------------------------------------------
|
|
AddFiles
|
|
|
|
Desc:
|
|
Add multiple files to the archive
|
|
the files are in the directory infile_dir
|
|
|
|
Notes:
|
|
The files to add are in a TStrings
|
|
if full paths are transferred in FileList, then infile_dir must be null.
|
|
if FileList count is 0 it will exit.
|
|
Will check if files added is a directory.
|
|
|
|
Algo:
|
|
Open Temp File
|
|
Do the following for all files in FileList
|
|
1) Check if it is a folder. Folders cannot be added.
|
|
2) Check if there is a file of a duplicate name. Warn the user if so.
|
|
3) Compress the block and append the block (new data).
|
|
4) Add the file info to the central directory.
|
|
Write the central directory.
|
|
Close Temp File
|
|
-------------------------------------------------------------------------------}
|
|
function TArchiveManager.AddFiles(FileList: TStrings; const infile_dir: string): integer;
|
|
var
|
|
TempFile: TArchiveFile; // temp archive
|
|
CentralFileHeader: TCentralFileHeader;
|
|
infile_name: string;
|
|
|
|
{-----------------------------------------------------------------------------
|
|
AppendNewData
|
|
|
|
Compresses the new file and appends the new data to the file.
|
|
IN Assertion: TempFile has been seeked to the correct position to add the
|
|
new data
|
|
-----------------------------------------------------------------------------}
|
|
procedure AppendNewData;
|
|
var
|
|
InFile: TFileStream; // file to add
|
|
block: PBlock;
|
|
bytes_read, block_compressed_size: integer;
|
|
EstimatedNumBlocks: integer;
|
|
//infile_size: integer;
|
|
begin
|
|
InFile := TFileStream.create(infile_name, fmOpenRead or fmShareDenyWrite);
|
|
|
|
infile_size := InFile.Size;
|
|
|
|
{Msg}
|
|
if infile_size > 0 then
|
|
begin
|
|
EstimatedNumBlocks := infile_size div BlockSize;
|
|
if (EstimatedNumBlocks = 0) or ((infile_size mod BlockSize) > 0) then
|
|
inc(EstimatedNumBlocks);
|
|
end
|
|
else
|
|
EstimatedNumBlocks := 0;
|
|
|
|
AddLog('File size = ' + IntToStr(infile_size)
|
|
+' bytes (Num blocks='+IntToStr(EstimatedNumBlocks)+')');
|
|
// ShowProgress(0); {Progress bar does not work}
|
|
bytes_processed := 0; // reset counter
|
|
|
|
// Compress the infile block by block to tempfile
|
|
block := Compressor.GetInBlock;
|
|
CentralFileHeader.data_offset := TempFile.Position;
|
|
bytes_read := infile.Read(block^[0], BlockSize);
|
|
while (bytes_read > 0) do
|
|
begin
|
|
//TempFile.SmartSeek(TempFile.Position, soFromBeginning);
|
|
|
|
Compressor.CompressInBlockToFile(bytes_read, TempFile, block_compressed_size);
|
|
with CentralFileHeader do
|
|
begin
|
|
inc(num_blocks);
|
|
inc(compressed_size, block_compressed_size);
|
|
inc(uncompressed_size, bytes_read);
|
|
end;
|
|
with CentralFileHeader do
|
|
AddLog('Block ' + IntToStr(num_blocks-1)+' OK, (Raw size='+
|
|
IntToStr(bytes_read)+' Compressed= ' + IntToStr(block_compressed_size) + ')');
|
|
|
|
block := Compressor.GetInBlock; // in_block may have been swapped again.
|
|
bytes_read := infile.Read(block^[0], BlockSize);
|
|
//MainForm.ShowProgress(CentralFileHeader.uncompressed_size * 100 div infile_size);
|
|
//Application.ProcessMessages;
|
|
|
|
//TempFile.ResetBuffer;
|
|
|
|
end;
|
|
|
|
InFile.Free;
|
|
end;
|
|
|
|
var
|
|
i: integer;
|
|
SearchRec: TSearchRec;
|
|
files_added: integer;
|
|
begin
|
|
if (FileList.Count = 0) then
|
|
begin
|
|
result := 0; // nothing to do if no files
|
|
Exit;
|
|
end;
|
|
|
|
StartTempProcessing(TempFile);
|
|
// copy existing data to tempfile
|
|
CopyData(ArchiveFile, TempFile, ArchiveFile.CentralDir);
|
|
files_added := 0;
|
|
|
|
// change to the directory to add the file from
|
|
if (infile_dir <> '') then
|
|
CHDir(infile_dir);
|
|
|
|
// append new data to tempfile
|
|
for i := 0 to FileList.Count-1 do
|
|
begin
|
|
infile_name := FileList[i];
|
|
ShowStatusMsg('Adding file - ' + infile_name);
|
|
FindFirst(infile_name, faAnyFile, SearchRec); // get file stats
|
|
|
|
// Check if it is a folders. Adding folders is not supported.
|
|
if (SearchRec.Attr and faDirectory <> 0) then
|
|
begin
|
|
Application.MessageBox(PChar('Could not add: ''' + infile_name + '''. Adding of folders is not supported.'),
|
|
'Error', MB_OK);
|
|
// move on to next file
|
|
Continue;
|
|
end;
|
|
|
|
// Check if another file with a duplicate name exists
|
|
if (ArchiveFile.CentralDir.FileNameExists(ExtractFileName(infile_name))) then
|
|
begin
|
|
if (Application.MessageBox(PChar('A file of name ''' + ExtractFileName(infile_name) + ''' already exists in the archive. Do you still want to add the file?'),
|
|
'Confirmation', MB_YESNO) = IDNo) then Continue;
|
|
end;
|
|
|
|
CentralFileHeader := TCentralFileHeader.Create;
|
|
with CentralFileHeader do
|
|
begin
|
|
// these values filled in later
|
|
compressed_size := 0;
|
|
uncompressed_size := 0;
|
|
num_blocks := 0;
|
|
|
|
// init file attr
|
|
filename := infile_name;
|
|
time := SearchRec.Time;
|
|
attr := SearchRec.Attr;
|
|
end;
|
|
|
|
try
|
|
AppendNewData; // this may raise EFOpenError for input file
|
|
ArchiveFile.CentralDir.Add(CentralFileHeader);
|
|
inc(files_added);
|
|
except
|
|
on EFOpenError do
|
|
begin
|
|
// file cannot be opened. may have to skip it.
|
|
Application.Messagebox(PChar('Cannot open file: ''' + infile_name + '''.' + #13 + 'It will not be added.'), ' Error', 0);
|
|
end;
|
|
end; {except}
|
|
end;
|
|
|
|
// write out the CentralDir
|
|
ArchiveFile.CentralDir.WriteToFile(TempFile);
|
|
EndTempProcessing(TempFile);
|
|
CentralDirChange;
|
|
|
|
// return the number of files added
|
|
result := files_added;
|
|
end;
|
|
|
|
|
|
|
|
|
|
procedure TArchiveManager.ShowProgress(a: integer);
|
|
begin
|
|
if Assigned(OnShowProgress) then OnShowProgress(Self, a);
|
|
end;
|
|
|
|
procedure TArchiveManager.ShowStatusMsg(s: string);
|
|
begin
|
|
if s <> '' then
|
|
AddLog(s);
|
|
if Assigned(OnShowStatusMsg) then OnShowStatusMsg(Self, s);
|
|
end;
|
|
|
|
procedure TArchiveManager.AddLog(s: string);
|
|
begin
|
|
if Assigned(OnAddLog) then OnAddLog(Self, 'ArchiveMan: ' + s);
|
|
end;
|
|
|
|
function TArchiveManager.IsArchiveOpen: boolean;
|
|
begin
|
|
result := ArchiveFile <> nil;
|
|
end;
|
|
|
|
end.
|