431 lines
14 KiB
ObjectPascal
431 lines
14 KiB
ObjectPascal
(* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is TurboPower Abbrevia
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Joel Haynie
|
|
* Craig Peterson
|
|
*
|
|
* Portions created by the Initial Developer are Copyright (C) 1997-2002
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* ***** END LICENSE BLOCK ***** *)
|
|
|
|
{*********************************************************}
|
|
{* ABBREVIA: AbBzip2Typ.pas *}
|
|
{*********************************************************}
|
|
{* ABBREVIA: TAbBzip2Archive, TAbBzip2Item classes *}
|
|
{*********************************************************}
|
|
{* Misc. constants, types, and routines for working *}
|
|
{* with Bzip2 files *}
|
|
{*********************************************************}
|
|
|
|
unit AbBzip2Typ;
|
|
|
|
{$I AbDefine.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
AbArcTyp, AbTarTyp, AbUtils;
|
|
|
|
const
|
|
{ Default Stream Header for Bzip2s is 'BZhX', where X is the block size setting 1-9 in ASCII }
|
|
{ Each block has the following header: '1AY&SY', and are in units of 100kilobytes NOT 100kibiBytes }
|
|
AB_BZIP2_FILE_HEADER = 'BZh';
|
|
AB_BZIP2_BLOCK_SIZE = ['1','2','3','4','5','6','7','8','9'];
|
|
AB_BZIP2_BLOCK_HEADER = '1AY&SY'; { Note: $314159265359, BCD for Pi :) }
|
|
{ Note that Blocks are bit aligned, as such the only time you will "for sure" see
|
|
the block header is on the start of stream/File }
|
|
AB_BZIP2_FILE_TAIL =#23#114#36#83#133#9#0; { $1772245385090, BCD for sqrt(Pi) :) }
|
|
{ This is odd as the blocks are bit allgned so this is a string that is 13*4 bits = 52 bits }
|
|
|
|
type
|
|
PAbBzip2Header = ^TAbBzip2Header; { File Header }
|
|
TAbBzip2Header = packed record { SizeOf(TAbBzip2Header) = 10 }
|
|
FileHeader : array[0..2] of AnsiChar;{ 'BZh'; $42,5A,68 }
|
|
BlockSize : AnsiChar; { '1'..'9'; $31-$39 }
|
|
BlockHeader : array[0..5] of AnsiChar;{ '1AY&SY'; $31,41,59,26,53,59 }
|
|
end;
|
|
|
|
{ The Purpose for this Item is the placeholder for aaAdd and aaDelete Support. }
|
|
{ For all intents and purposes we could just use a TAbArchiveItem }
|
|
type
|
|
TAbBzip2Item = class(TabArchiveItem);
|
|
|
|
TAbBzip2ArchiveState = (gsBzip2, gsTar);
|
|
|
|
TAbBzip2Archive = class(TAbTarArchive)
|
|
private
|
|
FBzip2Stream : TStream; { stream for Bzip2 file}
|
|
FBzip2Item : TAbArchiveList; { item in bzip2 (only one, but need polymorphism of class)}
|
|
FTarStream : TStream; { stream for possible contained Tar }
|
|
FTarList : TAbArchiveList; { items in possible contained Tar }
|
|
FTarAutoHandle: Boolean;
|
|
FState : TAbBzip2ArchiveState;
|
|
FIsBzippedTar : Boolean;
|
|
|
|
procedure DecompressToStream(aStream: TStream);
|
|
procedure SetTarAutoHandle(const Value: Boolean);
|
|
procedure SwapToBzip2;
|
|
procedure SwapToTar;
|
|
|
|
protected
|
|
{ Inherited Abstract functions }
|
|
function CreateItem(const FileSpec : string): TAbArchiveItem; override;
|
|
procedure ExtractItemAt(Index : Integer; const NewName : string); override;
|
|
procedure ExtractItemToStreamAt(Index : Integer; aStream : TStream); override;
|
|
procedure LoadArchive; override;
|
|
procedure SaveArchive; override;
|
|
procedure TestItemAt(Index : Integer); override;
|
|
function GetSupportsEmptyFolders : Boolean; override;
|
|
|
|
public {methods}
|
|
constructor CreateFromStream(aStream : TStream; const aArchiveName : string); override;
|
|
destructor Destroy; override;
|
|
|
|
procedure DoSpanningMediaRequest(Sender : TObject; ImageNumber : Integer;
|
|
var ImageName : string; var Abort : Boolean); override;
|
|
|
|
{ Properties }
|
|
property TarAutoHandle : Boolean
|
|
read FTarAutoHandle write SetTarAutoHandle;
|
|
|
|
property IsBzippedTar : Boolean
|
|
read FIsBzippedTar write FIsBzippedTar;
|
|
end;
|
|
|
|
function VerifyBzip2(Strm : TStream) : TAbArchiveType;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFDEF MSWINDOWS}
|
|
Windows, // Fix inline warnings
|
|
{$ENDIF}
|
|
StrUtils, SysUtils,
|
|
AbBzip2, AbExcept, AbVMStrm, AbBitBkt;
|
|
|
|
{ ****************** Helper functions Not from Classes Above ***************** }
|
|
function VerifyHeader(const Header : TAbBzip2Header) : Boolean;
|
|
begin
|
|
Result := (Header.FileHeader = AB_BZIP2_FILE_HEADER) and
|
|
(Header.BlockSize in AB_BZIP2_BLOCK_SIZE) and
|
|
(Header.BlockHeader = AB_BZIP2_BLOCK_HEADER);
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
function VerifyBzip2(Strm : TStream) : TAbArchiveType;
|
|
var
|
|
Hdr : TAbBzip2Header;
|
|
CurPos : int64;
|
|
DecompStream, TarStream: TStream;
|
|
begin
|
|
Result := atUnknown;
|
|
|
|
CurPos := Strm.Position;
|
|
Strm.Seek(0, soBeginning);
|
|
|
|
try
|
|
if (Strm.Read(Hdr, SizeOf(Hdr)) = SizeOf(Hdr)) and VerifyHeader(Hdr) then begin
|
|
Result := atBzip2;
|
|
{ Check for embedded TAR }
|
|
Strm.Seek(0, soBeginning);
|
|
DecompStream := TBZDecompressionStream.Create(Strm);
|
|
try
|
|
TarStream := TMemoryStream.Create;
|
|
try
|
|
TarStream.CopyFrom(DecompStream, 512 * 2);
|
|
TarStream.Seek(0, soBeginning);
|
|
if VerifyTar(TarStream) = atTar then
|
|
Result := atBzippedTar;
|
|
finally
|
|
TarStream.Free;
|
|
end;
|
|
finally
|
|
DecompStream.Free;
|
|
end;
|
|
end;
|
|
except
|
|
on EReadError do
|
|
Result := atUnknown;
|
|
end;
|
|
Strm.Position := CurPos; { Return to original position. }
|
|
end;
|
|
|
|
|
|
{ ****************************** TAbBzip2Archive ***************************** }
|
|
constructor TAbBzip2Archive.CreateFromStream(aStream: TStream;
|
|
const aArchiveName: string);
|
|
begin
|
|
inherited CreateFromStream(aStream, aArchiveName);
|
|
FState := gsBzip2;
|
|
FBzip2Stream := FStream;
|
|
FBzip2Item := FItemList;
|
|
FTarStream := TAbVirtualMemoryStream.Create;
|
|
FTarList := TAbArchiveList.Create(True);
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.SwapToTar;
|
|
begin
|
|
FStream := FTarStream;
|
|
FItemList := FTarList;
|
|
FState := gsTar;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.SwapToBzip2;
|
|
begin
|
|
FStream := FBzip2Stream;
|
|
FItemList := FBzip2Item;
|
|
FState := gsBzip2;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
function TAbBzip2Archive.CreateItem(const FileSpec: string): TAbArchiveItem;
|
|
begin
|
|
if IsBzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
Result := inherited CreateItem(FileSpec);
|
|
end
|
|
else begin
|
|
SwapToBzip2;
|
|
Result := TAbBzip2Item.Create;
|
|
try
|
|
Result.DiskFileName := ExpandFileName(FileSpec);
|
|
Result.FileName := FixName(FileSpec);
|
|
except
|
|
Result.Free;
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
destructor TAbBzip2Archive.Destroy;
|
|
begin
|
|
SwapToBzip2;
|
|
FTarList.Free;
|
|
FTarStream.Free;
|
|
inherited Destroy;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.ExtractItemAt(Index: Integer;
|
|
const NewName: string);
|
|
var
|
|
OutStream : TFileStream;
|
|
begin
|
|
if IsBzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited ExtractItemAt(Index, NewName);
|
|
end
|
|
else begin
|
|
SwapToBzip2;
|
|
OutStream := TFileStream.Create(NewName, fmCreate or fmShareDenyNone);
|
|
try
|
|
try
|
|
ExtractItemToStreamAt(Index, OutStream);
|
|
finally
|
|
OutStream.Free;
|
|
end;
|
|
{ Bz2 doesn't store the last modified time or attributes, so don't set them }
|
|
except
|
|
on E : EAbUserAbort do begin
|
|
FStatus := asInvalid;
|
|
if FileExists(NewName) then
|
|
DeleteFile(NewName);
|
|
raise;
|
|
end else begin
|
|
if FileExists(NewName) then
|
|
DeleteFile(NewName);
|
|
raise;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.ExtractItemToStreamAt(Index: Integer;
|
|
aStream: TStream);
|
|
begin
|
|
if IsBzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited ExtractItemToStreamAt(Index, aStream);
|
|
end
|
|
else begin
|
|
SwapToBzip2;
|
|
{ Index ignored as there's only one item in a Bz2 }
|
|
DecompressToStream(aStream);
|
|
end;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
function TAbBzip2Archive.GetSupportsEmptyFolders : Boolean;
|
|
begin
|
|
Result := IsBzippedTar and TarAutoHandle;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.LoadArchive;
|
|
var
|
|
Item: TAbBzip2Item;
|
|
Abort: Boolean;
|
|
ItemName: string;
|
|
begin
|
|
if FBzip2Stream.Size = 0 then
|
|
Exit;
|
|
|
|
if IsBzippedTar and TarAutoHandle then begin
|
|
{ Decompress and send to tar LoadArchive }
|
|
DecompressToStream(FTarStream);
|
|
SwapToTar;
|
|
inherited LoadArchive;
|
|
end
|
|
else begin
|
|
SwapToBzip2;
|
|
Item := TAbBzip2Item.Create;
|
|
Item.Action := aaNone;
|
|
{ Filename isn't stored, so constuct one based on the archive name }
|
|
ItemName := ExtractFileName(ArchiveName);
|
|
if ItemName = '' then
|
|
Item.FileName := 'unknown'
|
|
else if AnsiEndsText('.tbz', ItemName) or AnsiEndsText('.tbz2', ItemName) then
|
|
Item.FileName := ChangeFileExt(ItemName, '.tar')
|
|
else
|
|
Item.FileName := ChangeFileExt(ItemName, '');
|
|
Item.DiskFileName := Item.FileName;
|
|
FItemList.Add(Item);
|
|
end;
|
|
DoArchiveProgress(100, Abort);
|
|
FIsDirty := False;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.SaveArchive;
|
|
var
|
|
CompStream: TStream;
|
|
i: Integer;
|
|
CurItem: TAbBzip2Item;
|
|
InputFileStream: TStream;
|
|
begin
|
|
if IsBzippedTar and TarAutoHandle then
|
|
begin
|
|
SwapToTar;
|
|
inherited SaveArchive;
|
|
FTarStream.Position := 0;
|
|
FBzip2Stream.Size := 0;
|
|
CompStream := TBZCompressionStream.Create(bs9, FBzip2Stream);
|
|
try
|
|
CompStream.CopyFrom(FTarStream, 0);
|
|
finally
|
|
CompStream.Free;
|
|
end;
|
|
end
|
|
else begin
|
|
{ Things we know: There is only one file per archive.}
|
|
{ Actions we have to address in SaveArchive: }
|
|
{ aaNone & aaMove do nothing, as the file does not change, only the meta data }
|
|
{ aaDelete could make a zero size file unless there are two files in the list.}
|
|
{ aaAdd, aaStreamAdd, aaFreshen, & aaReplace will be the only ones to take action. }
|
|
SwapToBzip2;
|
|
for i := 0 to pred(Count) do begin
|
|
FCurrentItem := ItemList[i];
|
|
CurItem := TAbBzip2Item(ItemList[i]);
|
|
case CurItem.Action of
|
|
aaNone, aaMove: Break;{ Do nothing; bz2 doesn't store metadata }
|
|
aaDelete: ; {doing nothing omits file from new stream}
|
|
aaAdd, aaFreshen, aaReplace, aaStreamAdd: begin
|
|
FBzip2Stream.Size := 0;
|
|
CompStream := TBZCompressionStream.Create(bs9, FBzip2Stream);
|
|
try
|
|
if CurItem.Action = aaStreamAdd then
|
|
CompStream.CopyFrom(InStream, 0){ Copy/compress entire Instream to FBzip2Stream }
|
|
else begin
|
|
InputFileStream := TFileStream.Create(CurItem.DiskFileName, fmOpenRead or fmShareDenyWrite );
|
|
try
|
|
CompStream.CopyFrom(InputFileStream, 0);{ Copy/compress entire Instream to FBzip2Stream }
|
|
finally
|
|
InputFileStream.Free;
|
|
end;
|
|
end;
|
|
finally
|
|
CompStream.Free;
|
|
end;
|
|
Break;
|
|
end; { End aaAdd, aaFreshen, aaReplace, & aaStreamAdd }
|
|
end; { End of CurItem.Action Case }
|
|
end; { End Item for loop }
|
|
end; { End Tar Else }
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.SetTarAutoHandle(const Value: Boolean);
|
|
begin
|
|
if Value then
|
|
SwapToTar
|
|
else
|
|
SwapToBzip2;
|
|
FTarAutoHandle := Value;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.DecompressToStream(aStream: TStream);
|
|
const
|
|
BufSize = $F000;
|
|
var
|
|
DecompStream: TBZDecompressionStream;
|
|
Buffer: PByte;
|
|
N: Integer;
|
|
begin
|
|
DecompStream := TBZDecompressionStream.Create(FBzip2Stream);
|
|
try
|
|
GetMem(Buffer, BufSize);
|
|
try
|
|
N := DecompStream.Read(Buffer^, BufSize);
|
|
while N > 0 do begin
|
|
aStream.WriteBuffer(Buffer^, N);
|
|
N := DecompStream.Read(Buffer^, BufSize);
|
|
end;
|
|
finally
|
|
FreeMem(Buffer, BufSize);
|
|
end;
|
|
finally
|
|
DecompStream.Free;
|
|
end;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.TestItemAt(Index: Integer);
|
|
var
|
|
Bzip2Type: TAbArchiveType;
|
|
BitBucket: TAbBitBucketStream;
|
|
begin
|
|
if IsBzippedTar and TarAutoHandle then begin
|
|
SwapToTar;
|
|
inherited TestItemAt(Index);
|
|
end
|
|
else begin
|
|
{ note Index ignored as there's only one item in a GZip }
|
|
Bzip2Type := VerifyBzip2(FBzip2Stream);
|
|
if not (Bzip2Type in [atBzip2, atBzippedTar]) then
|
|
raise EAbGzipInvalid.Create;// TODO: Add bzip2-specific exceptions }
|
|
BitBucket := TAbBitBucketStream.Create(1024);
|
|
try
|
|
DecompressToStream(BitBucket);
|
|
finally
|
|
BitBucket.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
{ -------------------------------------------------------------------------- }
|
|
procedure TAbBzip2Archive.DoSpanningMediaRequest(Sender: TObject;
|
|
ImageNumber: Integer; var ImageName: string; var Abort: Boolean);
|
|
begin
|
|
Abort := False;
|
|
end;
|
|
|
|
end.
|