rsvk/Component/ArchiveFileUnit.pas

519 lines
15 KiB
Plaintext

unit ArchiveFileUnit;
{-------------------------------------------------------------------------------
Archive File Unit
-----------------
reSource v2.6
Copyright (C) 1998-2001 Victor Kasenda / gruv
http://go.to/gruv
email: vickas@singnet.com.sg
Notes:
Anything related to the file itself physically.
Seek/Read/Write
-------------------------------------------------------------------------------}
(**) interface (**)
uses SysUtils, Classes,
// resource units
bit_file_unit, Contnrs;
type
TArchiveFile = class;
TCentralDir = class(TObjectList)
private
ArchiveFile: TArchiveFile;
central_dir_offset: integer;
// info from end of central dir record
fblock_size: integer;
public
property block_size: integer read fblock_size;
constructor Create(_ArchiveFile: TArchiveFile);
{destructor Destroy; override;}
{procedure Clear;
procedure Delete(Index: Integer);}
// read/write from assigned file
procedure Read;
procedure Write;
procedure WriteToFile(NArchiveFile: TArchiveFile); // write to another archive file
function GetCentralDirOffset: integer;
function SeekToCentralDir: boolean;
function FileNameExists(filename: string): boolean;
end;
TArchiveFile = class(TBitFile)
private
protected
public
CentralDir: TCentralDir;
filename: string;
constructor CreateNew(const _filename: string; OpenExisting: boolean);
//constructor OpenExisting(const _filename: string);
destructor Destroy; override;
procedure ReadString(var s: string);
procedure ReadLongint(var a: longint);
procedure ReadLongword(var a: longword);
procedure WriteString(const s: string);
procedure WriteLongint(const a: longint);
procedure WriteLongword(const a: longword);
// new style function overloading
procedure ReadData(var a: longint); overload;
procedure ReadData(var a: longword); overload;
procedure WriteData(const a: longint); overload;
procedure WriteData(const a: longword); overload;
procedure ReserveSpace(const num_bytes: integer);
{function IsEmptyArchive: boolean;}
// Archive related
procedure SeekToDataStart;
end;
procedure ArchiveFileBlockCopy(SourceFile, DestFile: TArchiveFile; size: integer);
(**) implementation (**)
uses StructsUnit, ErrorUnit, ArchiveHeadersUnit;
{-------------------------------------------------------------------------------
ArchiveFileBlockCopy
--------------------
Works similarly to TStream.CopyFrom but is more efficient in that a bigger
buffer is used (64kbytes).
IN Assertion:
Buffering has been disabled for both files.
The files have been seeked to the position to read/write.
-------------------------------------------------------------------------------}
procedure ArchiveFileBlockCopy(SourceFile, DestFile: TArchiveFile; size: integer);
var
buf: P64kBlock;
bytes_to_read: integer;
const
bufsize = sizeof(T64kBlock);
begin
New(buf);
while (size > 0) do
begin
if (size > bufsize) then
bytes_to_read := bufsize
else
bytes_to_read := size;
dec(size, bytes_to_read);
SourceFile.Read(buf^, bytes_to_read);
DestFile.Write(buf^, bytes_to_read);
end;
Dispose(buf);
end;
(*******************************************************************************
TCentralDir
-----------
Central Directory class
*******************************************************************************)
constructor TCentralDir.Create(_ArchiveFile: TArchiveFile);
begin
inherited Create;
ArchiveFile := _ArchiveFile;
central_dir_offset := -1;
Read;
end;
{-------------------------------------------------------------------------------
GetCentralDirOffset
Returns the central_dir_offset of it has not been read yet.
Desc:
The central directory offset is stored in the last 4 bytes of the file.
It is inefficient to keep reading this porition to get the central directory
offset. So it is cached and stored in central_dir_offset. If it has not
been read yet, a -1 is assigned.
-------------------------------------------------------------------------------}
function TCentralDir.GetCentralDirOffset: integer;
begin
if (central_dir_offset = -1) then
begin
// the offset has not been read in yet
// read it in
// seek to the start of the central directory
with ArchiveFile do
begin
if (Size > 0) then
begin
DisableBuf;
SmartSeek(-4, soFromEnd); // seek to last four bytes
Read(central_dir_offset, 4); // get main_directory_offset
EnableBuf;
end;
end;
end;
result := central_dir_offset;
end;
{-------------------------------------------------------------------------------
SeekToCentralDir
----------------
Seeks to the central dir in the archive file.
returns false if CentralDir does not exist (archive is empty)
-------------------------------------------------------------------------------}
function TCentralDir.SeekToCentralDir: boolean;
begin
if (GetCentralDirOffset >= 0) then
begin
// seek to central directory
with ArchiveFile do
begin
DisableBuf;
SmartSeek(central_dir_offset, soFromBeginning);
EnableBuf;
end;
result := true;
end
else
result := false;
end;
{-------------------------------------------------------------------------------
Read
----
Reads the CentralDir from the archive file
-------------------------------------------------------------------------------}
procedure TCentralDir.Read;
var
ArchiveHeader: TArchiveHeader;
CentralDirEndHeader: TCentralDirEndHeader;
begin
Clear;
with ArchiveFile do
begin
{if not IsEmptyArchive then
begin}
if SeekToCentralDir then
begin
{GetCentralDirOffset;
// seek to central directory
DisableBuf;
SmartSeek(central_dir_offset, soFromBeginning);
EnableBuf;}
// read in the central file headers until an end of central dir rec is encountered
repeat
ArchiveHeader := GetArchiveHeader(ArchiveFile);
if (ArchiveHeader is TCentralFileHeader) then
Add(ArchiveHeader)
else
break;
until false;
CentralDirEndHeader := (ArchiveHeader as TCentralDirEndHeader);
fblock_size := CentralDirEndHeader.block_size;
CentralDirEndHeader.free;
end; // SeekToCentralDir
{end; // if not IsEmptyArchive}
end; // with Archive File
end; // procedure
{-------------------------------------------------------------------------------
WriteToFile
-----------
IN Assertion: The file has been seeked to the correct location to write the
the CentralDir Info.
Writes:
[central file header] ...
[end of central directory record]
-------------------------------------------------------------------------------}
procedure TCentralDir.Write;
begin
WriteToFile(ArchiveFile);
end;
procedure TCentralDir.WriteToFile(NArchiveFile: TArchiveFile);
var
i: integer; // counter
CentralFileHeader: TCentralFileHeader;
CentralDirEndHeader: TCentralDirEndHeader;
begin
// the CentralDirEndHeader will be written last
CentralDirEndHeader := TCentralDirEndHeader.Create;
with CentralDirEndHeader do
begin
block_size := BlockSize;
central_file_header_offset := NArchiveFile.Position;
end;
// write [central file header]
for i := 0 to Count - 1 do
begin
CentralFileHeader := TCentralFileHeader(items[i]);
CentralFileHeader.WriteToFile(NArchiveFile);
end;
CentralDirEndHeader.WriteToFile(NArchiveFile);
CentralDirEndHeader.free;
end;
{-------------------------------------------------------------------------------
FileNameExists
--------------
returns true if a CentralFileHeader with the same filename exists
Notes:
Used to check for duplicate file names when a file is to be added to
the archive.
Desc:
Does a case insensitive comparison of all the entries in CentralDir to look
for the filename.
-------------------------------------------------------------------------------}
function TCentralDir.FileNameExists(filename: string): boolean;
var
i: integer; // counter
CFH: TCentralFileHeader; // entry in CentralDir
begin
filename := UpperCase(filename);
result := false;
for i := 0 to Count-1 do
begin
CFH := Items[i] as TCentralFileHeader;
if (filename = Uppercase(CFH.filename)) then
begin
result := true;
break;
end;
end;
end;
(*******************************************************************************
TArchiveFile
------------
The Archive file class
*******************************************************************************)
{-------------------------------------------------------------------------------
CreateNew
---------
Creates a new archive with filename.
Desc:
If the file exists, it will be zeroed.
The Resource archive signature and an empty central directory will be added
to it to make it a valid archive.
The CentralDir is read again at the end to obtain its offset.
-------------------------------------------------------------------------------}
constructor TArchiveFile.CreateNew(const _filename: string; OpenExisting: boolean);
var
RAH: TResourceArchiveHeader;
begin
if OpenExisting then
begin
inherited Create(_filename, fmOpenRead or fmShareDenyWrite);
filename := _filename;
// test the signature to see if it is a valid archive
RAH := TResourceArchiveHeader.Create;
RAH.ReadFromFile(Self);
RAH.Free;
// create a new central dir and read it from the file
CentralDir := TCentralDir.Create(Self);
CentralDir.Read;
end
else
begin
inherited Create(_filename, fmCreate);
filename := _filename;
// write the signature to make it a valid archive
RAH := TResourceArchiveHeader.Create;
RAH.WriteToFile(Self);
RAH.Free;
// create a new central dir and write it
CentralDir := TCentralDir.Create(Self);
CentralDir.Write;
ResetBuffer;
CentralDir.Read;
end;
end;
{-------------------------------------------------------------------------------
Open
----
Opens an existing file of filename.
Desc:
The signature of the file will be checked to ensure it is valid.
Notes:
If the file does not exist an exception will occur.
-------------------------------------------------------------------------------}
{constructor TArchiveFile.OpenExisting(const _filename: string);
var
RAH: TResourceArchiveHeader;
begin
inherited Create(_filename, fmOpenRead or fmShareDenyWrite);
filename := _filename;
// test the signature to see if it is a valid archive
RAH := TResourceArchiveHeader.Create;
RAH.ReadFromFile(Self);
RAH.Free;
// create a new central dir and read it from the file
CentralDir := TCentralDir.Create(Self);
CentralDir.Read;
end;}
{-------------------------------------------------------------------------------
Destroy
-------
Frees up resources allocated by the constructor.
-------------------------------------------------------------------------------}
destructor TArchiveFile.Destroy;
begin
CentralDir.Free;
inherited Destroy;
end;
{function TArchiveFile.IsEmptyArchive: boolean;
begin
// if the file is 0 bytes long, then this is a new or empty archive
result := (Size = 0);
end;}
{-------------------------------------------------------------------------------
ReadString
----------
Desc:
Strings are null terminated
Read in characters until a null is encountered
-------------------------------------------------------------------------------}
procedure TArchiveFile.ReadString(var s: string);
var
c: char;
begin
repeat
GetNextByte(byte(c));
if (c = #0) then break;
s := s + c;
until false;
end;
{-------------------------------------------------------------------------------
ReadLongInt
-----------
Reads in a longinteger from the buffer
-------------------------------------------------------------------------------}
procedure TArchiveFile.ReadLongint(var a: longint);
begin
ReadBuf(a, sizeof(a));
end;
procedure TArchiveFile.ReadLongword(var a: longword);
begin
ReadBuf(a, sizeof(a));
end;
procedure TArchiveFile.ReadData(var a: longint);
begin
ReadBuf(a, sizeof(a));
end;
procedure TArchiveFile.ReadData(var a: longword);
begin
ReadBuf(a, sizeof(a));
end;
{-------------------------------------------------------------------------------
WriteString
-----------
writes out the string s and a null terminator
-------------------------------------------------------------------------------}
procedure TArchiveFile.WriteString(const s: string);
var
i: integer;
begin
for i := 1 to length(s) do
WriteByte(byte(s[i]));
WriteByte(0); // write the string terminator
end;
{-------------------------------------------------------------------------------
WriteLongInt
------------
Writes out the longinteger 'a' to the buffer
-------------------------------------------------------------------------------}
procedure TArchiveFile.WriteLongint(const a: longint);
begin
WriteBuf(a, sizeof(a));
end;
procedure TArchiveFile.WriteLongword(const a: longword);
begin
WriteBuf(a, sizeof(a));
end;
procedure TArchiveFile.WriteData(const a: longint);
begin
WriteBuf(a, sizeof(a));
end;
procedure TArchiveFile.WriteData(const a: longword);
begin
WriteBuf(a, sizeof(a));
end;
{-------------------------------------------------------------------------------
ReserveSpace
------------
writes out num_bytes of blank data to reserve the space for future use
-------------------------------------------------------------------------------}
procedure TArchiveFile.ReserveSpace(const num_bytes: integer);
var
i: integer;
begin
for i := 1 to num_bytes do
WriteByte(0);
end;
{-------------------------------------------------------------------------------
SeekToDataStart
---------------
seeks to the start of the data segment of the archive
Desc:
The data segment starts after the archive header (signature)
Seek to the position after the header
-------------------------------------------------------------------------------}
procedure TArchiveFile.SeekToDataStart;
begin
SmartSeek(RESOURCE_ARCHIVE_HEADER_SIZE, soFromBeginning);
end;
end.