242 lines
6.9 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
* TurboPower Software
*
* Portions created by the Initial Developer are Copyright (C) 1997-2002
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* ***** END LICENSE BLOCK ***** *)
{*********************************************************}
{* ABBREVIA: AbBitBkt.pas *}
{*********************************************************}
{* ABBREVIA: Bit bucket memory stream class *}
{*********************************************************}
unit AbBitBkt;
{$I AbDefine.inc}
interface
uses
Classes,
AbUtils;
type
TAbBitBucketStream = class(TStream)
private
FBuffer : {$IFDEF UNICODE}PByte{$ELSE}PAnsiChar{$ENDIF};
FBufSize : longint;
FBufPosn : longint;
FPosn : Int64;
FSize : Int64;
FTail : longint;
protected
public
constructor Create(aBufSize : cardinal);
destructor Destroy; override;
function Read(var Buffer; Count : Longint) : Longint; override;
function Write(const Buffer; Count : Longint) : Longint; override;
function Seek(const Offset : Int64; Origin : TSeekOrigin) : Int64; override;
procedure ForceSize(aSize : Int64);
end;
implementation
uses
Math, SysUtils, AbExcept;
{Notes: The buffer is a circular queue without a head pointer; FTail
is where data is next going to be written and it wraps
indescriminately. The buffer can never be empty--it is always
full (initially it is full of binary zeros.
The class is designed to act as a bit bucket for the test
feature of Abbrevia's zip code; it is not intended as a
complete class with many possible applications. It is designed
to be written to in a steady progression with some reading
back in the recently written stream (the buffer size details
how far back the Seek method will work). Seeking outside this
buffer will result in exceptions being generated.
For testing deflated files, the buffer size should be 32KB,
for imploded files, either 8KB or 4KB. The Create constructor
limits the buffer size to these values.}
{===TAbBitBucketStream===============================================}
constructor TAbBitBucketStream.Create(aBufSize : cardinal);
begin
inherited Create;
if (aBufSize <> 4096) and
(aBufSize <> 8192) and
(aBufSize <> 32768) then
FBufSize := 32768
else
FBufSize := aBufSize;
{add a 1KB leeway}
inc(FBufSize, 1024);
GetMem(FBuffer, FBufSize);
end;
{--------}
destructor TAbBitBucketStream.Destroy;
begin
if (FBuffer <> nil) then
FreeMem(FBuffer, FBufSize);
inherited Destroy;
end;
{--------}
procedure TAbBitBucketStream.ForceSize(aSize : Int64);
begin
FSize := aSize;
end;
{--------}
function TAbBitBucketStream.Read(var Buffer; Count : Longint) : Longint;
var
Chunk2Size : longint;
Chunk1Size : longint;
OutBuffer : PByte;
begin
OutBuffer := @Buffer;
{we cannot read more bytes than there is buffer}
if (Count > FBufSize) then
raise EAbBBSReadTooManyBytes.Create(Count);
{calculate the size of the chunks}
if (FBufPosn <= FTail) then begin
Chunk1Size := FTail - FBufPosn;
if (Chunk1Size > Count) then
Chunk1Size := Count;
Chunk2Size := 0;
end
else begin
Chunk1Size := FBufSize - FBufPosn;
if (Chunk1Size > Count) then begin
Chunk1Size := Count;
Chunk2Size := 0;
end
else begin
Chunk2Size := FTail;
if (Chunk2Size > (Count - Chunk1Size)) then
Chunk2Size := Count - Chunk1Size;
end
end;
{we cannot read more bytes than there are available}
if (Count > (Chunk1Size + Chunk2Size)) then
raise EAbBBSReadTooManyBytes.Create(Count);
{perform the read}
if (Chunk1Size > 0) then begin
Move(FBuffer[FBufPosn], OutBuffer^, Chunk1Size);
inc(FBufPosn, Chunk1Size);
inc(FPosn, Chunk1Size);
end;
if (Chunk2Size > 0) then begin
{we've wrapped}
Move(FBuffer[0], PByte(PtrInt(OutBuffer) + PtrInt(Chunk1Size))^, Chunk2Size);
FBufPosn := Chunk2Size;
inc(FPosn, Chunk2Size);
end;
Result := Count;
end;
{--------}
function TAbBitBucketStream.Write(const Buffer; Count : Longint) : Longint;
var
Chunk2Size : longint;
Chunk1Size : longint;
InBuffer : PByte;
Overage : longint;
begin
Result := Count;
InBuffer := @Buffer;
{we cannot write more bytes than there is buffer}
while Count > FBufSize do begin
Overage := Min(FBufSize, Count - FBufSize);
Write(InBuffer^, Overage);
Inc(PtrInt(InBuffer), Overage);
Dec(Count, Overage);
end;
{calculate the size of the chunks}
Chunk1Size := FBufSize - FTail;
if (Chunk1Size > Count) then begin
Chunk1Size := Count;
Chunk2Size := 0;
end
else begin
Chunk2Size := Count - Chunk1Size;
end;
{write the first chunk}
if (Chunk1Size > 0) then begin
Move(InBuffer^, FBuffer[FTail], Chunk1Size);
inc(FTail, Chunk1Size);
end;
{if the second chunk size is not zero, write the second chunk; note
that we have wrapped}
if (Chunk2Size > 0) then begin
Move(PByte(PtrInt(InBuffer) + PtrInt(Chunk1Size))^, FBuffer[0], Chunk2Size);
FTail := Chunk2Size;
end;
{the stream size and position have changed}
inc(FSize, Count);
FPosn := FSize;
FBufPosn := FTail;
end;
{--------}
function TAbBitBucketStream.Seek(const Offset : Int64; Origin : TSeekOrigin): Int64;
var
Posn : Int64;
BytesBack : longint;
begin
{calculate the new position}
case Origin of
soBeginning :
Posn := Offset;
soCurrent :
Posn := FPosn + Offset;
soEnd :
if (Offset = 0) then begin
{special case: position at end of stream}
FBufPosn := FTail;
FPosn := FSize;
Result := FSize;
Exit;
end
else begin
Posn := FSize + Offset;
end;
else
raise EAbBBSInvalidOrigin.Create;
end;
{calculate whether the new position is within the buffer; if not,
raise exception}
if (Posn > FSize) or
(Posn <= (FSize - FBufSize)) then
raise EAbBBSSeekOutsideBuffer.Create;
{set the internal fields for the new position}
FPosn := Posn;
BytesBack := FSize - Posn;
if (BytesBack <= FTail) then
FBufPosn := FTail - BytesBack
else
FBufPosn := longint(FTail) + FBufSize - BytesBack;
{return the new position}
Result := Posn;
end;
{====================================================================}
end.