mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-27 17:53:43 +02:00
456 lines
12 KiB
ObjectPascal
456 lines
12 KiB
ObjectPascal
{
|
|
$Id$
|
|
This file is part of the Free Pascal run time library.
|
|
Copyright (c) 1999-2000 by the Free Pascal development team
|
|
|
|
Implementation of compression streams.
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
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.
|
|
|
|
**********************************************************************}
|
|
unit zstream;
|
|
|
|
{ ---------------------------------------------------------------------
|
|
On linux, the default is to use the zlib libraries.
|
|
On all other platforms we use paszlib. If you want to use
|
|
paszlib in all cases, just define -dUsePasZlib
|
|
---------------------------------------------------------------------}
|
|
|
|
{$ifndef linux}
|
|
{$define usepaszlib}
|
|
{$endif}
|
|
|
|
interface
|
|
|
|
uses
|
|
Sysutils, Classes
|
|
{$ifdef usepaszlib}
|
|
,paszlib
|
|
{$else}
|
|
,zlib
|
|
{$endif}
|
|
;
|
|
|
|
{$H+}
|
|
|
|
type
|
|
// Error reporting.
|
|
EZlibError = class(EStreamError);
|
|
ECompressionError = class(EZlibError);
|
|
EDecompressionError = class(EZlibError);
|
|
|
|
TCustomZlibStream = class(TStream)
|
|
private
|
|
FStrm: TStream;
|
|
FStrmPos: Integer;
|
|
FOnProgress: TNotifyEvent;
|
|
FZRec: TZStream;
|
|
FBuffer: array [Word] of Byte;
|
|
protected
|
|
procedure Progress(Sender: TObject); dynamic;
|
|
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
|
|
public
|
|
constructor Create(Strm: TStream);
|
|
end;
|
|
|
|
TCompressionLevel = (clNone, clFastest, clDefault, clMax);
|
|
|
|
TCompressionStream = class(TCustomZlibStream)
|
|
private
|
|
function GetCompressionRate: extended;
|
|
function CompressionCheck(code: Integer): Integer;
|
|
procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
|
|
var OutBuf: Pointer; var OutBytes: Integer);
|
|
public
|
|
constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream);
|
|
destructor Destroy; override;
|
|
function Read(var Buffer; Count: Longint): Longint; override;
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
function Seek(Offset: Longint; Origin: Word): Longint; override;
|
|
property CompressionRate: extended read GetCompressionRate;
|
|
property OnProgress;
|
|
end;
|
|
|
|
TDecompressionStream = class(TCustomZlibStream)
|
|
private
|
|
function DecompressionCheck(code: Integer): Integer;
|
|
procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
|
|
OutEstimate: Integer; var OutBuf: Pointer; var OutBytes: Integer);
|
|
public
|
|
constructor Create(Source: TStream);
|
|
destructor Destroy; override;
|
|
function Read(var Buffer; Count: Longint): Longint; override;
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
function Seek(Offset: Longint; Origin: Word): Longint; override;
|
|
property OnProgress;
|
|
end;
|
|
|
|
TGZOpenMode = (gzOpenRead,gzOpenWrite);
|
|
|
|
TGZFileStream = Class(TStream)
|
|
Private
|
|
FOpenMode : TGZOpenmode;
|
|
FFIle : gzfile;
|
|
Public
|
|
Constructor Create(FileName: String;FileMode: TGZOpenMode);
|
|
Destructor Destroy;override;
|
|
Function Read(Var Buffer; Count : longint): longint;override;
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
function Seek(Offset: Longint; Origin: Word): Longint; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
Const
|
|
ErrorStrings : array [0..6] of string =
|
|
('Unknown error %d','Z_ERRNO','Z_STREAM_ERROR',
|
|
'Z_DATA_ERROR','Z_MEM_ERROR','Z_BUF_ERROR','Z_VERSION_ERROR');
|
|
SCouldntOpenFile = 'Couldn''t open file : %s';
|
|
SReadOnlyStream = 'Decompression streams are read-only';
|
|
SWriteOnlyStream = 'Compression streams are write-only';
|
|
SSeekError = 'Compression stream seek error';
|
|
SInvalidSeek = 'Invalid Compression seek operation';
|
|
|
|
function zlibAllocMem(opaque:pointer; items:uInt; size:uInt):pointer;{$ifndef usepaszlib}cdecl;{$endif}
|
|
begin
|
|
Result:=GetMem(Items*Size);
|
|
end;
|
|
|
|
procedure zlibFreeMem(opaque:pointer; address:pointer);{$ifndef usepaszlib}cdecl;{$endif}
|
|
begin
|
|
FreeMem(address);
|
|
end;
|
|
|
|
|
|
procedure TCompressionStream.CompressBuf(const InBuf: Pointer; InBytes: Integer;
|
|
var OutBuf: Pointer; var OutBytes: Integer);
|
|
var
|
|
strm: TZStream;
|
|
P: Pointer;
|
|
Type
|
|
PByte = ^Byte;
|
|
begin
|
|
FillChar(strm, sizeof(strm), 0);
|
|
strm.zalloc := @zlibAllocMem;
|
|
strm.zfree := @zlibFreeMem;
|
|
OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255;
|
|
OutBuf:=GetMem(OutBytes);
|
|
try
|
|
strm.next_in := InBuf;
|
|
strm.avail_in := InBytes;
|
|
strm.next_out := OutBuf;
|
|
strm.avail_out := OutBytes;
|
|
CompressionCheck(deflateInit_(strm, Z_BEST_COMPRESSION, ZLIB_VERSION, sizeof(strm)));
|
|
try
|
|
while CompressionCheck(deflate(strm, Z_FINISH)) <> Z_STREAM_END do
|
|
begin
|
|
P := OutBuf;
|
|
Inc(OutBytes, 256);
|
|
ReallocMem(OutBuf,OutBytes);
|
|
strm.next_out := PChar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P)));
|
|
strm.avail_out := 256;
|
|
end;
|
|
finally
|
|
CompressionCheck(deflateEnd(strm));
|
|
end;
|
|
ReallocMem(OutBuf,strm.total_out);
|
|
OutBytes := strm.total_out;
|
|
except
|
|
FreeMem(OutBuf);
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDecompressionStream.DecompressBuf(const InBuf: Pointer; InBytes: Integer;
|
|
OutEstimate: Integer; var OutBuf: Pointer; var OutBytes: Integer);
|
|
var
|
|
strm: TZStream;
|
|
P: Pointer;
|
|
BufInc: Integer;
|
|
Type
|
|
PByte = ^Byte;
|
|
begin
|
|
FillChar(strm, sizeof(strm), 0);
|
|
strm.zalloc := @zlibAllocMem;
|
|
strm.zfree := @zlibFreeMem;
|
|
BufInc := (InBytes + 255) and not 255;
|
|
if OutEstimate = 0 then
|
|
OutBytes := BufInc
|
|
else
|
|
OutBytes := OutEstimate;
|
|
OutBuf:=GetMem(OutBytes);
|
|
try
|
|
strm.next_in := InBuf;
|
|
strm.avail_in := InBytes;
|
|
strm.next_out := OutBuf;
|
|
strm.avail_out := OutBytes;
|
|
DecompressionCheck(inflateInit_(strm, ZLIB_VERSION, sizeof(strm)));
|
|
try
|
|
while DecompressionCheck(inflate(strm, Z_FINISH)) <> Z_STREAM_END do
|
|
begin
|
|
P := OutBuf;
|
|
Inc(OutBytes, BufInc);
|
|
ReallocMem(OutBuf, OutBytes);
|
|
strm.next_out := Pchar(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P)));
|
|
strm.avail_out := BufInc;
|
|
end;
|
|
finally
|
|
DecompressionCheck(inflateEnd(strm));
|
|
end;
|
|
ReallocMem(OutBuf, strm.total_out);
|
|
OutBytes := strm.total_out;
|
|
except
|
|
FreeMem(OutBuf);
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
|
|
// TCustomZlibStream
|
|
|
|
constructor TCustomZLibStream.Create(Strm: TStream);
|
|
begin
|
|
inherited Create;
|
|
FStrm := Strm;
|
|
FStrmPos := Strm.Position;
|
|
FZRec.zalloc := @zlibAllocMem;
|
|
FZRec.zfree := @zlibFreeMem;
|
|
end;
|
|
|
|
procedure TCustomZLibStream.Progress(Sender: TObject);
|
|
begin
|
|
if Assigned(FOnProgress) then FOnProgress(Sender);
|
|
end;
|
|
|
|
|
|
// TCompressionStream
|
|
|
|
constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel;
|
|
Dest: TStream);
|
|
const
|
|
Levels: array [TCompressionLevel] of ShortInt =
|
|
(Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
|
|
begin
|
|
inherited Create(Dest);
|
|
FZRec.next_out := @FBuffer;
|
|
FZRec.avail_out := sizeof(FBuffer);
|
|
CompressionCheck(deflateInit_(FZRec, Levels[CompressionLevel], ZLIB_VERSION, sizeof(FZRec)));
|
|
end;
|
|
|
|
destructor TCompressionStream.Destroy;
|
|
begin
|
|
FZRec.next_in := nil;
|
|
FZRec.avail_in := 0;
|
|
try
|
|
if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
|
|
while (CompressionCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END)
|
|
and (FZRec.avail_out = 0) do
|
|
begin
|
|
FStrm.WriteBuffer(FBuffer, sizeof(FBuffer));
|
|
FZRec.next_out := @FBuffer;
|
|
FZRec.avail_out := sizeof(FBuffer);
|
|
end;
|
|
if FZRec.avail_out < sizeof(FBuffer) then
|
|
FStrm.WriteBuffer(FBuffer, sizeof(FBuffer) - FZRec.avail_out);
|
|
finally
|
|
deflateEnd(FZRec);
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCompressionStream.CompressionCheck(code: Integer): Integer;
|
|
begin
|
|
Result := code;
|
|
if (code < 0) then
|
|
if code < -6 then
|
|
raise ECompressionError.CreateFmt(Errorstrings[0],[Code])
|
|
else
|
|
raise ECompressionError.Create(ErrorStrings[Abs(Code)]);
|
|
end;
|
|
|
|
|
|
function TCompressionStream.Read(var Buffer; Count: Longint): Longint;
|
|
begin
|
|
raise ECompressionError.Create('Invalid stream operation');
|
|
result:=0;
|
|
end;
|
|
|
|
function TCompressionStream.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
FZRec.next_in := @Buffer;
|
|
FZRec.avail_in := Count;
|
|
if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
|
|
while (FZRec.avail_in > 0) do
|
|
begin
|
|
CompressionCheck(deflate(FZRec, 0));
|
|
if FZRec.avail_out = 0 then
|
|
begin
|
|
FStrm.WriteBuffer(FBuffer, sizeof(FBuffer));
|
|
FZRec.next_out := @FBuffer;
|
|
FZRec.avail_out := sizeof(FBuffer);
|
|
FStrmPos := FStrm.Position;
|
|
Progress(Self);
|
|
end;
|
|
end;
|
|
Result := Count;
|
|
end;
|
|
|
|
function TCompressionStream.Seek(Offset: Longint; Origin: Word): Longint;
|
|
begin
|
|
if (Offset = 0) and (Origin = soFromCurrent) then
|
|
Result := FZRec.total_in
|
|
else
|
|
raise ECompressionError.Create(SInvalidSeek);
|
|
end;
|
|
|
|
function TCompressionStream.GetCompressionRate: extended;
|
|
begin
|
|
Result:=0.0;
|
|
{ With FZrec do
|
|
if total_in = 0 then
|
|
GetCompressionRate:=0.0
|
|
else
|
|
GetCompressionRate:=1.0E2*(1.0E0-(total_out/total_in));
|
|
}
|
|
end;
|
|
|
|
|
|
// TDecompressionStream
|
|
|
|
constructor TDecompressionStream.Create(Source: TStream);
|
|
begin
|
|
inherited Create(Source);
|
|
FZRec.next_in := @FBuffer;
|
|
DecompressionCheck(inflateInit_(FZRec, ZLIB_VERSION, sizeof(FZRec)));
|
|
end;
|
|
|
|
destructor TDecompressionStream.Destroy;
|
|
begin
|
|
inflateEnd(FZRec);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TDecompressionStream.DecompressionCheck(code: Integer): Integer;
|
|
begin
|
|
Result := code;
|
|
If Code<0 then
|
|
if code < -6 then
|
|
raise EDecompressionError.CreateFmt(Errorstrings[0],[Code])
|
|
else
|
|
raise EDecompressionError.Create(ErrorStrings[Abs(Code)]);
|
|
end;
|
|
|
|
function TDecompressionStream.Read(var Buffer; Count: Longint): Longint;
|
|
begin
|
|
FZRec.next_out := @Buffer;
|
|
FZRec.avail_out := Count;
|
|
if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
|
|
while (FZRec.avail_out > 0) do
|
|
begin
|
|
if FZRec.avail_in = 0 then
|
|
begin
|
|
FZRec.avail_in := FStrm.Read(FBuffer, sizeof(FBuffer));
|
|
if FZRec.avail_in = 0 then
|
|
begin
|
|
Result := Count - FZRec.avail_out;
|
|
Exit;
|
|
end;
|
|
FZRec.next_in := @FBuffer;
|
|
FStrmPos := FStrm.Position;
|
|
Progress(Self);
|
|
end;
|
|
DeCompressionCheck(inflate(FZRec, 0));
|
|
end;
|
|
Result := Count;
|
|
end;
|
|
|
|
function TDecompressionStream.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
raise EDecompressionError.Create('Invalid stream operation');
|
|
result:=0;
|
|
end;
|
|
|
|
function TDecompressionStream.Seek(Offset: Longint; Origin: Word): Longint;
|
|
var
|
|
I: Integer;
|
|
Buf: array [0..4095] of Char;
|
|
begin
|
|
if (Offset = 0) and (Origin = soFromBeginning) then
|
|
begin
|
|
DecompressionCheck(inflateReset(FZRec));
|
|
FZRec.next_in := @FBuffer;
|
|
FZRec.avail_in := 0;
|
|
FStrm.Position := 0;
|
|
FStrmPos := 0;
|
|
end
|
|
else if ( (Offset >= 0) and (Origin = soFromCurrent)) or
|
|
( ((Offset - FZRec.total_out) > 0) and (Origin = soFromBeginning)) then
|
|
begin
|
|
if Origin = soFromBeginning then Dec(Offset, FZRec.total_out);
|
|
if Offset > 0 then
|
|
begin
|
|
for I := 1 to Offset div sizeof(Buf) do
|
|
ReadBuffer(Buf, sizeof(Buf));
|
|
ReadBuffer(Buf, Offset mod sizeof(Buf));
|
|
end;
|
|
end
|
|
else
|
|
raise EDecompressionError.Create(SInvalidSeek);
|
|
Result := FZRec.total_out;
|
|
end;
|
|
|
|
// TGZFileStream
|
|
|
|
Constructor TGZFileStream.Create(FileName: String;FileMode: TGZOpenMode);
|
|
|
|
Const OpenStrings : array[TGZOpenMode] of pchar = ('rb','wb');
|
|
|
|
begin
|
|
FOpenMode:=FileMode;
|
|
FFile:=gzopen (PChar(FileName),Openstrings[FileMode]);
|
|
If FFile=Nil then
|
|
Raise ezlibError.CreateFmt (SCouldntOpenFIle,[FileName]);
|
|
end;
|
|
|
|
Destructor TGZFileStream.Destroy;
|
|
begin
|
|
gzclose(FFile);
|
|
Inherited Destroy;
|
|
end;
|
|
|
|
Function TGZFileStream.Read(Var Buffer; Count : longint): longint;
|
|
begin
|
|
If FOpenMode=gzOpenWrite then
|
|
Raise ezliberror.create(SWriteOnlyStream);
|
|
Result:=gzRead(FFile,@Buffer,Count);
|
|
end;
|
|
|
|
function TGZFileStream.Write(const Buffer; Count: Longint): Longint;
|
|
begin
|
|
If FOpenMode=gzOpenRead then
|
|
Raise EzlibError.Create(SReadonlyStream);
|
|
Result:=gzWrite(FFile,@Buffer,Count);
|
|
end;
|
|
|
|
function TGZFileStream.Seek(Offset: Longint; Origin: Word): Longint;
|
|
begin
|
|
Result:=gzseek(FFile,Offset,Origin);
|
|
If Result=-1 then
|
|
Raise eZlibError.Create(SSeekError);
|
|
end;
|
|
|
|
end.
|
|
{
|
|
$Log$
|
|
Revision 1.14 2000-01-12 23:29:49 peter
|
|
* log added
|
|
|
|
}
|