{******************************************************************************} { } { Library: Fundamentals 4.00 } { File name: flcStreams.pas } { File version: 5.26 } { Description: Reader, Writer and Stream classes } { } { Copyright: Copyright (c) 1999-2020, David J Butler } { All rights reserved. } { Redistribution and use in source and binary forms, with } { or without modification, are permitted provided that } { the following conditions are met: } { Redistributions of source code must retain the above } { copyright notice, this list of conditions and the } { following disclaimer. } { THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND } { CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED } { WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED } { WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A } { PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL } { THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, } { INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR } { CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, } { PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF } { USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) } { HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER } { IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING } { NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE } { USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE } { POSSIBILITY OF SUCH DAMAGE. } { } { Github: https://github.com/fundamentalslib } { E-mail: fundamentals.library at gmail.com } { } { Revision history: } { } { 1999/03/01 0.01 Initial version. } { 2000/02/08 1.02 AStreamEx. } { 2000/05/08 1.03 ATRecordStream. } { 2000/06/01 1.04 TFixedLenRecordStreamer. } { 2002/05/13 3.05 Added TBufferedReader and TSplitBufferReader. } { 2002/05/29 3.06 Created cReaders and cWriters units from cStreams. } { 2002/07/13 3.07 Moved text reader functionality to AReaderEx. } { 2002/08/03 3.08 Moved TVarSizeAllocator to unit cVarAllocator. } { 2002/08/18 3.09 Added TReaderWriter as AStream. } { 2002/08/23 3.10 Added SelfTest procedure. } { 2003/03/06 3.11 Improvements to AReaderEx. } { 2003/03/17 3.12 Added new file open mode: fsomCreateOnWrite } { 2003/03/29 3.13 Added TStringWriter. } { 2003/04/09 3.14 Memory reader support for blocks with unknown size. } { 2004/02/21 3.15 Added TWideStringWriter. } { 2005/07/21 4.16 Added code from cReaders and cWriters units. } { 2005/08/26 4.17 Improved error messages. } { 2005/09/21 4.18 Revised for Fundamentals 4. } { 2005/12/06 4.19 Compilable under FreePascal 2.0.1 Linux i386. } { 2008/12/30 4.20 Revision. } { 2010/06/27 4.21 Compilable with FreePascal 2.4.0 OSX x86-64 } { 2011/10/14 4.22 Compilable with Delphi XE. } { 2015/03/31 4.23 Revision. } { 2016/01/17 5.24 Revised for Fundamentals 5. } { 2016/05/02 5.25 Change character handling in StringWriters and Readers. } { 2018/08/12 5.26 String type changes. } { } { Supported compilers: } { } { Delphi 7 Win32 5.26 2019/02/24 } { Delphi XE7 Win32 5.24 2016/01/17 } { Delphi XE7 Win64 5.24 2016/01/17 } { } {******************************************************************************} {$INCLUDE ..\flcInclude.inc} {$IFDEF FREEPASCAL} {$IFDEF DEBUG} {$WARNINGS OFF}{$HINTS OFF} {$ENDIF} {$ENDIF} {$IFDEF DEBUG} {$IFDEF TEST} {$DEFINE STREAMS_TEST} {$ENDIF} {$ENDIF} unit flcStreams; interface uses { System } SysUtils, {$IFDEF MSWIN} Windows, {$ENDIF} {$IFDEF FREEPASCAL}{$IFDEF UNIX} BaseUnix, {$ENDIF}{$ENDIF} { Fundamentals } flcStdTypes; { } { AReader } { Abstract base class for a Reader. } { } { Inherited classes must implement the abstract methods from AReader. } { } { Read returns the actual number of bytes copied to the buffer. } { Size returns -1 for reader's with unknown data size. } { } type AReader = class protected function GetPosition: Int64; virtual; abstract; procedure SetPosition(const Position: Int64); virtual; abstract; function GetSize: Int64; virtual; abstract; public function Read(var Buffer; const Size: Integer): Integer; virtual; abstract; property Position: Int64 read GetPosition write SetPosition; property Size: Int64 read GetSize; function EOF: Boolean; virtual; abstract; end; EReader = class(Exception); { } { AReaderEx } { Base class for Reader implementations. AReaderEx extends AReader with } { commonly used functions. } { } { All methods in AReaderEx is implemented using calls to the abstract } { methods in AReader. Reader implementations can override the virtual } { methods in AReaderEx with more efficient versions. } { } { Match functions return True when a match is found. Match leaves the } { reader's position unchanged except if a match is made and SkipOnMatch } { is True. } { } { Locate returns the offset (relative to the current position) of the } { first match in the stream. Locate preserves the reader's position. } { Locate returns -1 if a match was not made. } { } type TReaderEOLType = ( eolEOF, // EOF Files, Internet eolEOFAtEOF, // #26 at EOF Files eolCR, // #13 Unix, Internet eolLF, // #10 Internet eolCRLF, // #13#10 MS-DOS, Windows, Internet eolLFCR); // #10#13 Mac TReaderEOLTypes = Set of TReaderEOLType; const DefaultReaderEOLTypes = [eolEOF, eolEOFAtEOF, eolCR, eolLF, eolCRLF, eolLFCR]; type AReaderEx = class(AReader) private function SkipLineTerminator(const EOLTypes: TReaderEOLTypes): Integer; public procedure RaiseReadError(const Msg: String = ''); procedure RaiseSeekError; procedure ReadBuffer(var Buffer; const Size: Integer); function ReadCharA: AnsiChar; virtual; function ReadCharW: WideChar; virtual; function ReadStrB(const Len: Integer): RawByteString; virtual; function ReadStrU(const Len: Integer): UnicodeString; virtual; function GetToEOFB: RawByteString; virtual; function GetAsStringB: RawByteString; virtual; function ReadByte: Byte; virtual; function ReadWord: Word; function ReadLongWord: LongWord; function ReadLongInt: LongInt; function ReadInt64: Int64; function ReadSingle: Single; function ReadDouble: Double; function ReadFloat: Float; function ReadPackedRawByteString: RawByteString; function ReadPackedUTF8String: UTF8String; function ReadPackedUnicodeString: UnicodeString; function ReadPackedString: String; function ReadPackedRawByteStringArray: RawByteStringArray; function ReadPackedUnicodeStringArray: UnicodeStringArray; function ReadPackedStringArray: StringArray; function Peek(out Buffer; const Size: Integer): Integer; virtual; procedure PeekBuffer(out Buffer; const Size: Integer); function PeekStrB(const Len: Integer): RawByteString; virtual; function PeekStrU(const Len: Integer): UnicodeString; virtual; function PeekByte: Byte; virtual; function PeekWord: Word; function PeekLongWord: LongWord; function PeekLongInt: LongInt; function PeekInt64: Int64; function Match(const Buffer; const Size: Integer; const CaseSensitive: Boolean = True): Integer; virtual; function MatchBuffer(const Buffer; const Size: Integer; const CaseSensitive: Boolean = True): Boolean; function MatchStr(const S: RawByteString; const CaseSensitive: Boolean = True): Boolean; virtual; function MatchChar(const Ch: AnsiChar; const MatchNonMatch: Boolean = False; const SkipOnMatch: Boolean = False): Boolean; overload; function MatchChar(const C: ByteCharSet; var Ch: AnsiChar; const MatchNonMatch: Boolean = False; const SkipOnMatch: Boolean = False): Boolean; overload; procedure Skip(const Count: Integer = 1); virtual; procedure SkipByte; function SkipAll(const Ch: AnsiChar; const MatchNonMatch: Boolean = False; const MaxCount: Integer = -1): Integer; overload; function SkipAll(const C: ByteCharSet; const MatchNonMatch: Boolean = False; const MaxCount: Integer = -1): Integer; overload; function Locate(const Ch: AnsiChar; const LocateNonMatch: Boolean = False; const MaxOffset: Integer = -1): Integer; overload; virtual; function Locate(const C: ByteCharSet; const LocateNonMatch: Boolean = False; const MaxOffset: Integer = -1): Integer; overload; virtual; function LocateBuffer(const Buffer; const Size: Integer; const MaxOffset: Integer = -1; const CaseSensitive: Boolean = True): Integer; virtual; function LocateStrB(const S: RawByteString; const MaxOffset: Integer = -1; const CaseSensitive: Boolean = True): Integer; virtual; function ExtractAllB(const C: ByteCharSet; const ExtractNonMatch: Boolean = False; const MaxCount: Integer = -1): RawByteString; function ExtractToStrB(const S: RawByteString; const MaxLength: Integer = -1; const CaseSensitive: Boolean = True): RawByteString; function ExtractToCharB(const C: AnsiChar; const MaxLength: Integer = -1): RawByteString; overload; function ExtractToCharB(const C: ByteCharSet; const MaxLength: Integer = -1): RawByteString; overload; function ExtractQuotedB(const QuoteChars: ByteCharSet): RawByteString; function ExtractLineB(const MaxLineLength: Integer = -1; const EOLTypes: TReaderEOLTypes = DefaultReaderEOLTypes): RawByteString; function SkipLine(const MaxLineLength: Integer = -1; const EOLTypes: TReaderEOLTypes = DefaultReaderEOLTypes): Boolean; end; { } { TMemoryReader } { Reader implementation for a memory block. } { } { If the reader is initialized with Size = -1, the content is unsized and } { EOF will always return False. } { } type TMemoryReader = class(AReaderEx) protected FData : Pointer; FSize : Integer; FPos : Integer; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; public constructor Create(const Data: Pointer; const Size: Integer); property Data: Pointer read FData; property Size: Integer read FSize; procedure SetData(const Data: Pointer; const Size: Integer); function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; function ReadByte: Byte; override; function ReadLongInt: LongInt; function ReadInt64: Int64; function PeekByte: Byte; override; function Match(const Buffer; const Size: Integer; const CaseSensitive: Boolean = True): Integer; override; procedure Skip(const Count: Integer = 1); override; end; EMemoryReader = class(EReader); { } { TRawByteStringReader } { Memory reader implementation for a reference counted long string. } { } type TRawByteStringReader = class(TMemoryReader) protected FDataString : RawByteString; procedure SetDataString(const DataStr: RawByteString); public constructor Create(const DataStr: RawByteString); property DataString: RawByteString read FDataString write SetDataString; function GetAsStringB: RawByteString; override; function ReadCharW: WideChar; override; end; { } { TUnicodeStringReader } { Memory reader implementation for a reference counted UnicodeString. } { } type TUnicodeStringReader = class(TMemoryReader) protected FDataString : UnicodeString; procedure SetDataString(const DataStr: UnicodeString); public constructor Create(const DataStr: UnicodeString); property DataString: UnicodeString read FDataString write SetDataString; function GetAsStringU: UnicodeString; function ReadCharA: AnsiChar; override; end; { } { TFileReader } { Reader implementation for a file. } { } type TFileReaderAccessHint = ( frahNone, frahRandomAccess, frahSequentialAccess); TFileReader = class(AReaderEx) protected FHandle : Integer; FHandleOwner : Boolean; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; public constructor Create(const FileName: String; const AccessHint: TFileReaderAccessHint = frahNone); overload; constructor Create(const FileHandle: Integer; const HandleOwner: Boolean = False); overload; destructor Destroy; override; property Handle: Integer read FHandle; property HandleOwner: Boolean read FHandleOwner; function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; end; EFileReader = class(EReader); function ReadFileToStrB(const FileName: String): RawByteString; { } { AReaderProxy } { Base class for Reader Proxies. } { } type AReaderProxy = class(AReaderEx) protected FReader : AReaderEx; FReaderOwner : Boolean; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; public constructor Create(const Reader: AReaderEx; const ReaderOwner: Boolean = True); destructor Destroy; override; function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; property Reader: AReaderEx read FReader; property ReaderOwner: Boolean read FReaderOwner write FReaderOwner; end; { } { TReaderProxy } { Reader Proxy implementation. } { } { Proxies a block of data from Reader from the Reader's current position. } { Size specifies the size of the data to be proxied, or if Size = -1, } { all data up to EOF is proxied. } { } type TReaderProxy = class(AReaderProxy) protected FOffset : Int64; FSize : Int64; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; public constructor Create(const Reader: AReaderEx; const ReaderOwner: Boolean = True; const Size: Int64 = -1); function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; end; { } { TBufferedReader } { ReaderProxy implementation for buffered reading. } { } type TBufferedReader = class(AReaderProxy) protected FBufferSize : Integer; FPos : Int64; FBuffer : Pointer; FBufUsed : Integer; FBufPos : Integer; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; function FillBuf: Boolean; procedure BufferByte; function PosBuf(const C: ByteCharSet; const LocateNonMatch: Boolean; const MaxOffset: Integer): Integer; public constructor Create(const Reader: AReaderEx; const BufferSize: Integer = 128; const ReaderOwner: Boolean = True); destructor Destroy; override; function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; function ReadByte: Byte; override; function PeekByte: Byte; override; procedure Skip(const Count: Integer = 1); override; function Locate(const C: ByteCharSet; const LocateNonMatch: Boolean = False; const MaxOffset: Integer = -1): Integer; override; property BufferSize: Integer read FBufferSize; procedure InvalidateBuffer; end; { } { TSplitBufferedReader } { ReaderProxy implementation for split buffered reading. } { } { One buffer is used for read-ahead buffering, the other for seek-back } { buffering. } { } type TSplitBufferedReader = class(AReaderProxy) protected FBufferSize : Integer; FPos : Int64; FBuffer : array[0..1] of Pointer; FBufUsed : array[0..1] of Integer; FBufNr : Integer; FBufPos : Integer; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; function BufferStart: Integer; function BufferRemaining: Integer; function MoveBuf(var Dest: PByte; var Remaining: Integer): Boolean; function FillBuf(var Dest: PByte; var Remaining: Integer): Boolean; public constructor Create(const Reader: AReaderEx; const BufferSize: Integer = 128; const ReaderOwner: Boolean = True); destructor Destroy; override; property BufferSize: Integer read FBufferSize; function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; procedure InvalidateBuffer; end; { } { TBufferedFileReader } { TBufferedReader instance using a TFileReader. } { } type TBufferedFileReader = class(TBufferedReader) public constructor Create(const FileName: String; const BufferSize: Integer = 512); overload; constructor Create(const FileHandle: Integer; const HandleOwner: Boolean = False; const BufferSize: Integer = 512); overload; end; { } { TSplitBufferedFileReader } { TSplitBufferedReader instance using a TFileReader. } { } type TSplitBufferedFileReader = class(TSplitBufferedReader) public constructor Create(const FileName: String; const BufferSize: Integer = 512); end; { } { AWriter } { Writer abstract base class. } { } type AWriter = class protected function GetPosition: Int64; virtual; abstract; procedure SetPosition(const Position: Int64); virtual; abstract; function GetSize: Int64; virtual; abstract; procedure SetSize(const Size: Int64); virtual; abstract; public function Write(const Buffer; const Size: Integer): Integer; virtual; abstract; property Position: Int64 read GetPosition write SetPosition; property Size: Int64 read GetSize write SetSize; end; EWriter = class(Exception); { } { AWriterEx } { Base class for Writer implementations. AWriteEx extends AWriter with } { commonly used functions. } { } { All methods in AWriterEx are implemented using calls to the abstract } { methods in AWriter. Writer implementations can override the virtual } { methods in AWriterEx with more efficient versions. } { } type TWriterNewLineType = (nlCR, nlLF, nlCRLF, nlLFCR); AWriterEx = class(AWriter) protected function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); override; procedure SetAsStringB(const S: RawByteString); virtual; procedure SetAsStringU(const S: UnicodeString); virtual; public procedure RaiseWriteError; procedure Append; procedure Truncate; virtual; procedure Clear; virtual; property AsStringB: RawByteString write SetAsStringB; property AsStringU: UnicodeString write SetAsStringU; procedure WriteBuffer(const Buffer; const Size: Integer); procedure WriteCharA(const V: AnsiChar); virtual; procedure WriteCharW(const V: WideChar); virtual; {$IFDEF SupportAnsiString} procedure WriteStrA(const Buffer: AnsiString); virtual; {$ENDIF} procedure WriteStrB(const Buffer: RawByteString); virtual; procedure WriteStrU(const Buffer: UnicodeString); virtual; procedure WriteByte(const V: Byte); virtual; procedure WriteWord(const V: Word); virtual; procedure WriteLongWord(const V: LongWord); procedure WriteLongInt(const V: LongInt); procedure WriteInt64(const V: Int64); procedure WriteSingle(const V: Single); procedure WriteDouble(const V: Double); procedure WriteFloat(const V: Float); {$IFDEF SupportAnsiString} procedure WritePackedAnsiString(const V: AnsiString); {$ENDIF} procedure WritePackedRawByteString(const V: RawByteString); procedure WritePackedUnicodeString(const V: UnicodeString); procedure WritePackedString(const V: String); {$IFDEF SupportAnsiString} procedure WritePackedAnsiStringArray(const V: array of AnsiString); {$ENDIF} procedure WritePackedUnicodeStringArray(const V: array of UnicodeString); procedure WritePackedStringArray(const V: array of String); procedure WriteBufLine(const Buffer; const Size: Integer; const NewLineType: TWriterNewLineType = nlCRLF); procedure WriteLineB(const S: RawByteString; const NewLineType: TWriterNewLineType = nlCRLF); end; { } { TFileWriter } { Writer implementation for a file. } { } type TFileWriterOpenMode = ( fwomOpen, // Open existing fwomTruncate, // Open existing and truncate fwomCreate, // Always create fwomCreateIfNotExist); // Create if not exist else open existing TFileWriterAccessHint = ( fwahNone, fwahRandomAccess, fwahSequentialAccess); TFileWriterOptions = Set of ( fwoWriteThrough); TFileWriter = class(AWriterEx) protected FFileName : String; FHandle : Integer; FHandleOwner : Boolean; FFileCreated : Boolean; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); override; public constructor Create(const FileName: String; const OpenMode: TFileWriterOpenMode = fwomCreateIfNotExist; const Options: TFileWriterOptions = []; const AccessHint: TFileWriterAccessHint = fwahNone); overload; constructor Create(const FileHandle: Integer; const HandleOwner: Boolean); overload; destructor Destroy; override; property Handle: Integer read FHandle; property HandleOwner: Boolean read FHandleOwner; property FileCreated: Boolean read FFileCreated; function Write(const Buffer; const Size: Integer): Integer; override; procedure Flush; procedure DeleteFile; end; EFileWriter = class(EWriter); procedure WriteStrToFileB( const FileName: String; const S: RawByteString; const OpenMode: TFileWriterOpenMode = fwomCreate); procedure AppendStrToFileB(const FileName: String; const S: RawByteString); procedure WriteUnicodeStrToFile(const FileName: String; const S: UnicodeString; const OpenMode: TFileWriterOpenMode = fwomCreate); procedure AppendUnicodeStrToFile(const FileName: String; const S: UnicodeString); { } { TRawByteStringWriter } { Writer implementation for a long string. } { } type TRawByteStringWriter = class(AWriterEx) protected FDataStr : RawByteString; FDataSize : Integer; FPos : Integer; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); reintroduce; overload; override; procedure SetSize(const Size: Integer); reintroduce; overload; function GetAsStringB: RawByteString; procedure SetAsStringB(const S: RawByteString); override; public property DataString: RawByteString read FDataStr; property DataSize: Integer read FDataSize; property AsStringB: RawByteString read GetAsStringB write SetAsStringB; function Write(const Buffer; const Size: Integer): Integer; override; procedure WriteCharW(const V: WideChar); override; procedure WriteStrB(const Buffer: RawByteString); override; procedure WriteByte(const V: Byte); override; end; { } { TUnicodeStringWriter } { Writer implementation for a UnicodeString. } { } type TUnicodeStringWriter = class(AWriterEx) protected FDataStr : UnicodeString; FDataSize : Integer; FPos : Integer; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); reintroduce; overload; override; procedure SetSize(const Size: Integer); reintroduce; overload; function GetAsStringU: UnicodeString; procedure SetAsStringU(const S: UnicodeString); override; public property DataString: UnicodeString read FDataStr; property DataSize: Integer read FDataSize; property AsStringU: UnicodeString read GetAsStringU write SetAsStringU; function Write(const Buffer; const Size: Integer): Integer; override; procedure WriteCharA(const V: AnsiChar); override; procedure WriteCharW(const V: WideChar); override; procedure WriteStrU(const Buffer: UnicodeString); override; procedure WriteByte(const V: Byte); override; procedure WriteWord(const V: Word); override; end; { } { TOutputWriter } { Writer implementation for standard system output. } { } type TOutputWriter = class(AWriterEx) public function Write(const Buffer; const Size: Integer): Integer; override; end; { } { AStream } { Abstract base class for streams. } { } type AStream = class; AStreamCopyProgressEvent = procedure (const Source, Destination: AStream; const BytesCopied: Int64; var Abort: Boolean) of object; AStream = class protected FOnCopyProgress : AStreamCopyProgressEvent; function GetPosition: Int64; virtual; abstract; procedure SetPosition(const Position: Int64); virtual; abstract; function GetSize: Int64; virtual; abstract; procedure SetSize(const Size: Int64); virtual; abstract; function GetReader: AReaderEx; virtual; abstract; function GetWriter: AWriterEx; virtual; abstract; procedure TriggerCopyProgressEvent(const Source, Destination: AStream; const BytesCopied: Int64; var Abort: Boolean); virtual; public function Read(var Buffer; const Size: Integer): Integer; virtual; abstract; function Write(const Buffer; const Size: Integer): Integer; virtual; abstract; property Position: Int64 read GetPosition write SetPosition; property Size: Int64 read GetSize write SetSize; function EOF: Boolean; virtual; procedure Truncate; virtual; property Reader: AReaderEx read GetReader; property Writer: AWriterEx read GetWriter; procedure ReadBuffer(var Buffer; const Size: Integer); function ReadByte: Byte; function ReadStrB(const Size: Integer): RawByteString; procedure WriteBuffer(const Buffer; const Size: Integer); procedure WriteStrB(const S: RawByteString); procedure WriteStrU(const S: UnicodeString); procedure Assign(const Source: TObject); virtual; function WriteTo(const Destination: AStream; const BlockSize: Integer = 0; const Count: Int64 = -1): Int64; property OnCopyProgress: AStreamCopyProgressEvent read FOnCopyProgress write FOnCopyProgress; end; EStream = class(Exception); EStreamOperationAborted = class(EStream) constructor Create; end; { } { Stream proxies } { } type { } { TStreamReaderProxy } { } TStreamReaderProxy = class(AReaderEx) protected FStream : AStream; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; public constructor Create(const Stream: AStream); property Stream: AStream read FStream; function Read(var Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; end; { } { TStreamWriterProxy } { } TStreamWriterProxy = class (AWriterEx) protected FStream : AStream; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); override; public constructor Create(const Stream: AStream); property Stream: AStream read FStream; function Write(const Buffer; const Size: Integer): Integer; override; end; { } { Stream functions } { } type TCopyProgressProcedure = procedure (const Source, Destination: AStream; const BytesCopied: Int64; var Abort: Boolean); TCopyDataEvent = procedure (const Offset: Int64; const Data: Pointer; const DataSize: Integer) of object; function CopyStream(const Source, Destination: AStream; const SourceOffset: Int64 = 0; const DestinationOffset: Int64 = 0; const BlockSize: Integer = 0; const Count: Int64 = -1; const ProgressCallback: TCopyProgressProcedure = nil; const CopyFromBack: Boolean = False): Int64; overload; function CopyStream(const Source: AReaderEx; const Destination: AWriterEx; const BlockSize: Integer = 0; const CopyDataEvent: TCopyDataEvent = nil): Int64; overload; procedure DeleteStreamRange(const Stream: AStream; const Position, Count: Int64; const ProgressCallback: TCopyProgressProcedure = nil); procedure InsertStreamRange(const Stream: AStream; const Position, Count: Int64; const ProgressCallback: TCopyProgressProcedure = nil); { } { TReaderWriter } { Composition of a Reader and a Writer as a Stream. } { } type TReaderWriter = class(AStream) protected FReader : AReaderEx; FWriter : AWriterEx; FReaderOwner : Boolean; FWriterOwner : Boolean; procedure RaiseNoReaderError; procedure RaiseNoWriterError; function GetPosition: Int64; override; procedure SetPosition(const Position: Int64); override; function GetSize: Int64; override; procedure SetSize(const Size: Int64); override; function GetReader: AReaderEx; override; function GetWriter: AWriterEx; override; public constructor Create(const Reader: AReaderEx; const Writer: AWriterEx; const ReaderOwner: Boolean = True; const WriterOwner: Boolean = True); destructor Destroy; override; property Reader: AReaderEx read FReader; property Writer: AWriterEx read FWriter; property ReaderOwner: Boolean read FReaderOwner write FReaderOwner; property WriterOwner: Boolean read FWriterOwner write FWriterOwner; function Read(var Buffer; const Size: Integer): Integer; override; function Write(const Buffer; const Size: Integer): Integer; override; function EOF: Boolean; override; procedure Truncate; override; end; EReaderWriter = class (Exception); { } { TFileStream } { Stream implementation for a file. } { } type TFileStreamOpenMode = ( fsomRead, fsomReadWrite, fsomCreate, fsomCreateIfNotExist, fsomCreateOnWrite); TFileStreamAccessHint = ( fsahNone, fsahRandomAccess, fsahSequentialAccess); TFileStreamOptions = Set of ( fsoWriteThrough); TFileStream = class(TReaderWriter) protected FFileName : String; FOpenMode : TFileStreamOpenMode; FOptions : TFileStreamOptions; FAccessHint : TFileStreamAccessHint; procedure SetPosition(const Position: Int64); override; procedure SetSize(const Size: Int64); override; function GetReader: AReaderEx; override; function GetWriter: AWriterEx; override; function GetFileHandle: Integer; function GetFileCreated: Boolean; procedure EnsureCreateOnWrite; public constructor Create(const FileName: String; const OpenMode: TFileStreamOpenMode; const Options: TFileStreamOptions = []; const AccessHint: TFileStreamAccessHint = fsahNone); overload; constructor Create(const FileHandle: Integer; const HandleOwner: Boolean); overload; property FileName: String read FFileName; property FileHandle: Integer read GetFileHandle; property FileCreated: Boolean read GetFileCreated; procedure DeleteFile; function Write(const Buffer; const Size: Integer): Integer; override; end; EFileStream = class(EStream); { } { Test cases } { } {$IFDEF STREAMS_TEST} procedure Test; {$ENDIF} implementation uses {$IFDEF DELPHI}{$IFDEF POSIX} Posix.Unistd, {$ENDIF}{$ENDIF} { Fundamentals } flcUtils, flcSysUtils; resourcestring SReadError = 'Read error'; SWriteError = 'Write error'; SSeekError = 'Seek error'; SNotSupported = 'Not supported'; SFileError = 'File error: %s'; SFileOpenError = 'File open error: %s'; SFileSeekError = 'File seek error: %s'; SFileTruncateError = 'File truncate error: %s'; SFileResizeError = 'File resize error: %s'; SFileWriteError = 'File write error: %s'; SStreamReadError = 'Stream read error'; SStreamCopyError = 'Stream copy error'; SNoReader = 'No reader'; SNoWriter = 'No writer'; SModeNotImplemented = 'Mode not implemented'; SInvalidDataFormat = 'Invalid data format'; SInvalidFileName = 'Invalid file name'; SInvalidPosition = 'Invalid position'; SInvalidSize = 'Invalid size'; SInvalidParameter = 'Invalid parameter'; SCharacterSizeError = 'Character size error'; { } { AReaderEx } { } const DefaultBufSize = 2048; procedure AReaderEx.RaiseReadError(const Msg: String); var S : String; begin if Msg = '' then S := SReadError else S := Msg; raise EReader.Create(S); end; procedure AReaderEx.RaiseSeekError; begin raise EReader.Create(SSeekError); end; procedure AReaderEx.ReadBuffer(var Buffer; const Size: Integer); begin if Size <= 0 then exit; if Read(Buffer, Size) <> Size then RaiseReadError; end; function AReaderEx.ReadCharA: AnsiChar; begin if Read(Result, SizeOf(AnsiChar)) <> SizeOf(AnsiChar) then RaiseReadError; end; function AReaderEx.ReadCharW: WideChar; begin if Read(Result, SizeOf(WideChar)) <> SizeOf(WideChar) then RaiseReadError; end; function AReaderEx.ReadStrB(const Len: Integer): RawByteString; var L, I : Integer; begin if Len <= 0 then begin Result := ''; exit; end; SetLength(Result, Len); L := 0; for I := 1 to Len do begin if EOF then break; Result[I] := ReadCharA; Inc(L); end; if L <= 0 then begin Result := ''; exit; end; if L < Len then SetLength(Result, L); end; function AReaderEx.ReadStrU(const Len: Integer): UnicodeString; var L, I : Integer; begin if Len <= 0 then begin Result := ''; exit; end; SetLength(Result, Len); L := 0; for I := 1 to Len do begin if EOF then break; Result[I] := ReadCharW; Inc(L); end; if L <= 0 then begin Result := ''; exit; end; if L < Len then SetLength(Result, L); end; function AReaderEx.GetToEOFB: RawByteString; var S : Int64; B : RawByteString; I, J : Integer; L, N : Integer; R : Boolean; Q : PByteChar; begin S := GetSize; if S < 0 then // size unknown begin Q := nil; Result := ''; L := 0; // actual size N := 0; // allocated size R := EOF; while not R do begin B := ReadStrB(DefaultBufSize); I := Length(B); R := EOF; if I > 0 then begin J := L + I; if J > N then begin // allocate the exact buffer size for the first two reads // allocate double the buffer size from the third read if J <= DefaultBufSize * 2 then N := J else N := J * 2; SetLength(Result, N); Q := Pointer(Result); Inc(Q, L); end; Move(B[1], Q^, I); Inc(Q, I); Inc(L, I); end else if not R then RaiseReadError; end; if L < N then // set exact result size SetLength(Result, L); end else // size known Result := ReadStrB(S - GetPosition); end; function AReaderEx.GetAsStringB: RawByteString; begin SetPosition(0); Result := GetToEOFB; end; function AReaderEx.ReadByte: Byte; begin ReadBuffer(Result, Sizeof(Byte)); end; function AReaderEx.ReadWord: Word; begin ReadBuffer(Result, Sizeof(Word)); end; function AReaderEx.ReadLongWord: LongWord; begin ReadBuffer(Result, Sizeof(LongWord)); end; function AReaderEx.ReadLongInt: LongInt; begin ReadBuffer(Result, Sizeof(LongInt)); end; function AReaderEx.ReadInt64: Int64; begin ReadBuffer(Result, Sizeof(Int64)); end; function AReaderEx.ReadSingle: Single; begin ReadBuffer(Result, Sizeof(Single)); end; function AReaderEx.ReadDouble: Double; begin ReadBuffer(Result, Sizeof(Double)); end; function AReaderEx.ReadFloat: Float; begin ReadBuffer(Result, Sizeof(Float)); end; function AReaderEx.ReadPackedRawByteString: RawByteString; var L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); Result := ReadStrB(L); end; function AReaderEx.ReadPackedUTF8String: UTF8String; var L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); Result := ReadStrB(L); end; function AReaderEx.ReadPackedUnicodeString: UnicodeString; var L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); Result := ReadStrU(L); end; function AReaderEx.ReadPackedString: String; begin {$IFDEF StringIsUnicode} Result := ReadPackedUnicodeString; {$ELSE} Result := ReadPackedRawByteString; {$ENDIF} end; function AReaderEx.ReadPackedRawByteStringArray: RawByteStringArray; var I, L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); SetLength(Result, L); for I := 0 to L - 1 do Result[I] := ReadPackedRawByteString; end; function AReaderEx.ReadPackedUnicodeStringArray: UnicodeStringArray; var I, L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); SetLength(Result, L); for I := 0 to L - 1 do Result[I] := ReadPackedUnicodeString; end; function AReaderEx.ReadPackedStringArray: StringArray; var I, L : Integer; begin L := ReadLongInt; if L < 0 then raise EReader.Create(SInvalidDataFormat); SetLength(Result, L); for I := 0 to L - 1 do Result[I] := ReadPackedString; end; function AReaderEx.Peek(out Buffer; const Size: Integer): Integer; var P : Int64; begin P := GetPosition; Result := Read(Buffer, Size); if Result > 0 then SetPosition(P); end; procedure AReaderEx.PeekBuffer(out Buffer; const Size: Integer); begin if Size <= 0 then exit; if Peek(Buffer, Size) <> Size then RaiseReadError; end; function AReaderEx.PeekStrB(const Len: Integer): RawByteString; var L : Integer; begin if Len <= 0 then begin Result := ''; exit; end; SetLength(Result, Len); L := Peek(Pointer(Result)^, Len); if L <= 0 then begin Result := ''; exit; end; if L < Len then SetLength(Result, L); end; function AReaderEx.PeekStrU(const Len: Integer): UnicodeString; var L : Integer; begin if Len <= 0 then begin Result := ''; exit; end; SetLength(Result, Len); L := Peek(Pointer(Result)^, Len * SizeOf(WideChar)) div SizeOf(WideChar); if L <= 0 then begin Result := ''; exit; end; if L < Len then SetLength(Result, L); end; function AReaderEx.PeekByte: Byte; begin PeekBuffer(Result, Sizeof(Byte)); end; function AReaderEx.PeekWord: Word; begin PeekBuffer(Result, Sizeof(Word)); end; function AReaderEx.PeekLongWord: LongWord; begin PeekBuffer(Result, Sizeof(LongWord)); end; function AReaderEx.PeekLongInt: LongInt; begin PeekBuffer(Result, Sizeof(LongInt)); end; function AReaderEx.PeekInt64: Int64; begin PeekBuffer(Result, Sizeof(Int64)); end; { Returns the number of characters read and matched, or -1 if no match } function AReaderEx.Match(const Buffer; const Size: Integer; const CaseSensitive: Boolean): Integer; var B : Pointer; F : array[0..DefaultBufSize - 1] of Byte; M : Boolean; R : Boolean; begin if Size <= 0 then begin Result := -1; exit; end; M := Size > DefaultBufSize; if M then GetMem(B, Size) else B := @F[0]; try Result := Peek(B^, Size); if Result <= 0 then exit; if CaseSensitive then R := EqualMem(Buffer, B^, Result) else R := CompareMemNoAsciiCase(Buffer, B^, Result) = 0; if not R then Result := -1; finally if M then FreeMem(B); end; end; function AReaderEx.MatchBuffer(const Buffer; const Size: Integer; const CaseSensitive: Boolean): Boolean; var I : Integer; begin I := Match(Buffer, Size, CaseSensitive); if I < 0 then begin Result := False; exit; end; if I < Size then RaiseReadError; Result := True; end; function AReaderEx.MatchStr(const S: RawByteString; const CaseSensitive: Boolean): Boolean; begin Result := MatchBuffer(Pointer(S)^, Length(S), CaseSensitive); end; function AReaderEx.MatchChar(const Ch: AnsiChar; const MatchNonMatch: Boolean; const SkipOnMatch: Boolean): Boolean; begin if EOF then begin Result := False; exit; end; Result := (AnsiChar(PeekByte) = Ch) xor MatchNonMatch; if Result and SkipOnMatch then Skip(Sizeof(Byte)); end; function AReaderEx.MatchChar(const C: ByteCharSet; var Ch: AnsiChar; const MatchNonMatch: Boolean; const SkipOnMatch: Boolean): Boolean; begin if EOF then begin Result := False; exit; end; Ch := AnsiChar(PeekByte); Result := (Ch in C) xor MatchNonMatch; if Result and SkipOnMatch then Skip(Sizeof(Byte)); end; procedure AReaderEx.Skip(const Count: Integer); begin if Count < 0 then raise EReader.Create(SSeekError); if Count = 0 then exit; SetPosition(GetPosition + Count); end; procedure AReaderEx.SkipByte; begin Skip(Sizeof(Byte)); end; function AReaderEx.SkipAll(const Ch: AnsiChar; const MatchNonMatch: Boolean; const MaxCount: Integer): Integer; begin Result := 0; while (MaxCount < 0) or (Result < MaxCount) do if not MatchChar(Ch, MatchNonMatch, True) then exit else Inc(Result); end; function AReaderEx.SkipAll(const C: ByteCharSet; const MatchNonMatch: Boolean; const MaxCount: Integer): Integer; var Ch : AnsiChar; begin Result := 0; while (MaxCount < 0) or (Result < MaxCount) do if not MatchChar(C, Ch, MatchNonMatch, True) then exit else Inc(Result); end; function AReaderEx.Locate(const Ch: AnsiChar; const LocateNonMatch: Boolean; const MaxOffset: Integer): Integer; var P : Int64; I : Integer; begin P := GetPosition; I := 0; while not EOF and ((MaxOffset < 0) or (I <= MaxOffset)) do if (AnsiChar(ReadByte) = Ch) xor LocateNonMatch then begin SetPosition(P); Result := I; exit; end else Inc(I); SetPosition(P); Result := -1; end; function AReaderEx.Locate(const C: ByteCharSet; const LocateNonMatch: Boolean; const MaxOffset: Integer): Integer; var P : Int64; I : Integer; begin P := GetPosition; I := 0; while not EOF and ((MaxOffset < 0) or (I <= MaxOffset)) do if (AnsiChar(ReadByte) in C) xor LocateNonMatch then begin SetPosition(P); Result := I; exit; end else Inc(I); SetPosition(P); Result := -1; end; function AReaderEx.LocateBuffer(const Buffer; const Size: Integer; const MaxOffset: Integer; const CaseSensitive: Boolean): Integer; var P : Int64; I, J : Integer; B : Pointer; R, M : Boolean; F : array[0..DefaultBufSize - 1] of Byte; begin if Size <= 0 then begin Result := -1; exit; end; M := Size > DefaultBufSize; if M then GetMem(B, Size) else B := @F[0]; try P := GetPosition; I := 0; while not EOF and ((MaxOffset < 0) or (I <= MaxOffset)) do begin J := Read(B^, Size); if J < Size then begin if EOF then begin SetPosition(P); Result := -1; exit; end else RaiseReadError; end; if CaseSensitive then R := EqualMem(Buffer, B^, Size) else R := CompareMemNoAsciiCase(Buffer, B^, Size) = 0; if R then begin SetPosition(P); Result := I; exit; end else begin Inc(I); SetPosition(P + I); end; end; SetPosition(P); Result := -1; finally if M then FreeMem(B); end; end; function AReaderEx.LocateStrB(const S: RawByteString; const MaxOffset: Integer; const CaseSensitive: Boolean): Integer; begin Result := LocateBuffer(Pointer(S)^, Length(S), MaxOffset, CaseSensitive); end; function AReaderEx.ExtractAllB(const C: ByteCharSet; const ExtractNonMatch: Boolean; const MaxCount: Integer): RawByteString; var I : Integer; begin I := Locate(C, not ExtractNonMatch, MaxCount); if I = -1 then if MaxCount = -1 then Result := GetToEOFB else Result := ReadStrB(MaxCount) else Result := ReadStrB(I); end; function AReaderEx.ExtractToStrB(const S: RawByteString; const MaxLength: Integer; const CaseSensitive: Boolean): RawByteString; var I : Integer; begin I := LocateStrB(S, MaxLength, CaseSensitive); if I = -1 then if MaxLength = -1 then Result := GetToEOFB else Result := ReadStrB(MaxLength) else Result := ReadStrB(I); end; function AReaderEx.ExtractToCharB(const C: AnsiChar; const MaxLength: Integer = -1): RawByteString; var I : Integer; begin I := Locate(C, False, MaxLength); if I = -1 then if MaxLength = -1 then Result := GetToEOFB else Result := ReadStrB(MaxLength) else Result := ReadStrB(I); end; function AReaderEx.ExtractToCharB(const C: ByteCharSet; const MaxLength: Integer = -1): RawByteString; var I : Integer; begin I := Locate(C, False, MaxLength); if I = -1 then if MaxLength = -1 then Result := GetToEOFB else Result := ReadStrB(MaxLength) else Result := ReadStrB(I); end; function AReaderEx.ExtractQuotedB(const QuoteChars: ByteCharSet): RawByteString; var QuoteCh : AnsiChar; begin QuoteCh := AnsiChar(PeekByte); if not (QuoteCh in QuoteChars) then begin Result := ''; exit; end; SkipByte; Result := ExtractToStrB(QuoteCh, -1, True); SkipByte; end; const csNewLineNone : ByteCharSet = []; csNewLineCR : ByteCharSet = [#13]; csNewLineLF : ByteCharSet = [#10]; csNewLineEOF : ByteCharSet = [#26]; csNewLineCRLF : ByteCharSet = [#10, #13]; csNewLineCREOF : ByteCharSet = [#13, #26]; csNewLineLFEOF : ByteCharSet = [#10, #26]; csNewLineCRLFEOF : ByteCharSet = [#10, #13, #26]; procedure FirstNewLineCharsFromEOLTypes(const EOLTypes: TReaderEOLTypes; var Chars: PByteCharSet); begin if [eolCR, eolCRLF] * EOLTypes <> [] then if [eolLF, eolLFCR] * EOLTypes <> [] then if eolEOFAtEOF in EOLTypes then Chars := @csNewLineCRLFEOF else Chars := @csNewLineCRLF else if eolEOFAtEOF in EOLTypes then Chars := @csNewLineCREOF else Chars := @csNewLineCR else if [eolLF, eolLFCR] * EOLTypes <> [] then if eolEOFAtEOF in EOLTypes then Chars := @csNewLineLFEOF else Chars := @csNewLineLF else if eolEOFAtEOF in EOLTypes then Chars := @csNewLineEOF else Chars := @csNewLineNone; end; function AReaderEx.SkipLineTerminator(const EOLTypes: TReaderEOLTypes): Integer; var C, D : AnsiChar; R : Boolean; begin C := AnsiChar(ReadByte); if ((C = #10) and ([eolLFCR, eolLF] * EOLTypes = [eolLF])) or ((C = #13) and ([eolCRLF, eolCR] * EOLTypes = [eolCR])) then begin Result := 1; exit; end; if not (((C = #10) and (eolLFCR in EOLTypes)) or ((C = #13) and (eolCRLF in EOLTypes))) then begin SetPosition(GetPosition - 1); Result := 0; exit; end; R := EOF; if (C = #26) and (eolEOFAtEOF in EOLTypes) and R then begin Result := 1; exit; end; if R then begin if ((C = #10) and (eolLF in EOLTypes)) or ((C = #13) and (eolCR in EOLTypes)) then begin Result := 1; exit; end; SetPosition(GetPosition - 1); Result := 0; exit; end; D := AnsiChar(ReadByte); if ((C = #13) and (D = #10) and (eolCRLF in EOLTypes)) or ((C = #10) and (D = #13) and (eolLFCR in EOLTypes)) then begin Result := 2; exit; end; if ((C = #13) and (eolCR in EOLTypes)) or ((C = #10) and (eolLF in EOLTypes)) then begin SetPosition(GetPosition - 1); Result := 1; exit; end; SetPosition(GetPosition - 2); Result := 0; end; function AReaderEx.ExtractLineB(const MaxLineLength: Integer; const EOLTypes: TReaderEOLTypes): RawByteString; var NewLineChars : PByteCharSet; Fin : Boolean; begin if EOLTypes = [] then begin Result := ''; exit; end; if EOLTypes = [eolEOF] then begin Result := GetToEOFB; exit; end; FirstNewLineCharsFromEOLTypes(EOLTypes, NewLineChars); Result := ''; repeat Result := Result + ExtractAllB(NewLineChars^, True, MaxLineLength); if EOF then if eolEOF in EOLTypes then exit else RaiseReadError; Fin := (MaxLineLength >= 0) and (Length(Result) >= MaxLineLength); if not Fin then begin Fin := SkipLineTerminator(EOLTypes) > 0; if not Fin then Result := Result + AnsiChar(ReadByte); end; until Fin; end; function AReaderEx.SkipLine(const MaxLineLength: Integer; const EOLTypes: TReaderEOLTypes): Boolean; var NewLineChars : PByteCharSet; I : Integer; P, Q : Int64; Fin : Boolean; begin if EOLTypes = [] then begin Result := False; exit; end; Result := True; if EOLTypes = [eolEOF] then begin Position := Size; exit; end; FirstNewLineCharsFromEOLTypes(EOLTypes, NewLineChars); {$IFDEF DELPHI7} Fin := False; // Supress incorrect warning {$ENDIF} repeat I := Locate(NewLineChars^, False, MaxLineLength); if I < 0 then if MaxLineLength < 0 then begin Position := Size; exit; end else begin P := Position + MaxLineLength; Q := Size; if P > Q then P := Q; Position := P; exit; end else begin Skip(I); if EOF then exit; Fin := SkipLineTerminator(EOLTypes) > 0; if not Fin then SkipByte; end; until Fin; end; { } { TMemoryReader } { For Size < 0 the memory reader assumes no size limit. } { } constructor TMemoryReader.Create(const Data: Pointer; const Size: Integer); begin inherited Create; SetData(Data, Size); end; procedure TMemoryReader.SetData(const Data: Pointer; const Size: Integer); begin FData := Data; FSize := Size; FPos := 0; end; function TMemoryReader.GetPosition: Int64; begin Result := FPos; end; procedure TMemoryReader.SetPosition(const Position: Int64); var S : Integer; begin S := FSize; if (Position < 0) or ((S >= 0) and (Position > S)) then RaiseSeekError; FPos := Integer(Position); end; function TMemoryReader.GetSize: Int64; begin Result := FSize; end; function TMemoryReader.Read(var Buffer; const Size: Integer): Integer; var L, S, I : Integer; P : PByte; begin I := FPos; S := FSize; if (Size <= 0) or ((S >= 0) and (I >= S)) then begin Result := 0; exit; end; if S < 0 then L := Size else begin L := S - I; if Size < L then L := Size; end; P := FData; Inc(P, I); MoveMem(P^, Buffer, L); Result := L; Inc(FPos, L); end; function TMemoryReader.EOF: Boolean; var S : Integer; begin S := FSize; if S < 0 then Result := False else Result := FPos >= S; end; function TMemoryReader.ReadByte: Byte; var I, S : Integer; P : PByte; begin I := FPos; S := FSize; if (S >= 0) and (I >= S) then RaiseReadError; P := FData; Inc(P, I); Result := P^; Inc(FPos); end; function TMemoryReader.ReadLongInt: LongInt; var I, S : Integer; P : PByte; begin I := FPos; S := FSize; if (S >= 0) and (I + Sizeof(LongInt) > S) then RaiseReadError; P := FData; Inc(P, I); Result := PLongInt(P)^; Inc(FPos, Sizeof(LongInt)); end; function TMemoryReader.ReadInt64: Int64; var I, S : Integer; P : PByte; begin I := FPos; S := FSize; if (S >= 0) and (I + Sizeof(Int64) > S) then RaiseReadError; P := FData; Inc(P, I); Result := PInt64(P)^; Inc(FPos, Sizeof(Int64)); end; function TMemoryReader.PeekByte: Byte; var I, S : Integer; P : PByte; begin I := FPos; S := FSize; if (S >= 0) and (I >= S) then RaiseReadError; P := FData; Inc(P, I); Result := P^; end; function TMemoryReader.Match(const Buffer; const Size: Integer; const CaseSensitive: Boolean): Integer; var L, S : Integer; P : PByte; R : Boolean; begin S := FSize; if S < 0 then L := Size else begin L := S - FPos; if L > Size then L := Size; end; if L <= 0 then begin Result := -1; exit; end; P := FData; Inc(P, FPos); if CaseSensitive then R := EqualMem(Buffer, P^, L) else R := CompareMemNoAsciiCase(Buffer, P^, L) = 0; if R then Result := L else Result := -1; end; procedure TMemoryReader.Skip(const Count: Integer); var S, I : Integer; begin if Count <= 0 then exit; S := FSize; if S < 0 then Inc(FPos, Count) else begin I := FPos + Count; if I > S then RaiseSeekError; FPos := I; end; end; { } { TRawByteStringReader } { } constructor TRawByteStringReader.Create(const DataStr: RawByteString); begin FDataString := DataStr; inherited Create(Pointer(FDataString), Length(FDataString)); end; procedure TRawByteStringReader.SetDataString(const DataStr: RawByteString); begin FDataString := DataStr; SetData(Pointer(FDataString), Length(FDataString)); end; function TRawByteStringReader.GetAsStringB: RawByteString; begin Result := FDataString; end; function TRawByteStringReader.ReadCharW: WideChar; begin Result := WideChar(ReadCharA); end; { } { TUnicodeStringReader } { } constructor TUnicodeStringReader.Create(const DataStr: UnicodeString); begin FDataString := DataStr; inherited Create(Pointer(FDataString), Length(FDataString) * SizeOf(WideChar)); end; procedure TUnicodeStringReader.SetDataString(const DataStr: UnicodeString); begin FDataString := DataStr; SetData(Pointer(FDataString), Length(FDataString) * SizeOf(WideChar)); end; function TUnicodeStringReader.GetAsStringU: UnicodeString; begin Result := FDataString; end; function TUnicodeStringReader.ReadCharA: AnsiChar; var W : WideChar; begin W := ReadCharW; if Ord(W) > $FF then raise EReader.Create(SCharacterSizeError); Result := AnsiChar(Ord(W)); end; { } { TFileReader } { } constructor TFileReader.Create(const FileName: String; const AccessHint: TFileReaderAccessHint); {$IFDEF MSWIN} var F : LongWord; {$ENDIF} begin inherited Create; {$IFDEF MSWIN} F := FILE_ATTRIBUTE_NORMAL; case AccessHint of frahNone : ; frahRandomAccess : F := F or FILE_FLAG_RANDOM_ACCESS; frahSequentialAccess : F := F or FILE_FLAG_SEQUENTIAL_SCAN; end; {$IFDEF StringIsUnicode} FHandle := Integer(Windows.CreateFileW(PWideChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, F, 0)); {$ELSE} FHandle := Integer(Windows.CreateFileA(PAnsiChar(FileName), GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, F, 0)); {$ENDIF} {$ELSE} FHandle := FileOpen(FileName, fmOpenRead or fmShareDenyNone); {$ENDIF} if FHandle = -1 then raise EFileReader.CreateFmt(SFileOpenError, [GetLastOSErrorMessage]); FHandleOwner := True; end; constructor TFileReader.Create(const FileHandle: Integer; const HandleOwner: Boolean); begin inherited Create; FHandle := FileHandle; FHandleOwner := HandleOwner; end; destructor TFileReader.Destroy; begin if FHandleOwner and (FHandle <> -1) and (FHandle <> 0) then FileClose(FHandle); inherited Destroy; end; function TFileReader.GetPosition: Int64; begin Result := FileSeek(FHandle, Int64(0), 1); if Result = -1 then raise EFileReader.CreateFmt(SFileError, [GetLastOSErrorMessage]); end; procedure TFileReader.SetPosition(const Position: Int64); begin if FileSeek(FHandle, Position, 0) = -1 then raise EFileReader.CreateFmt(SFileSeekError, [GetLastOSErrorMessage]); end; function TFileReader.GetSize: Int64; var I : Int64; S : String; begin I := GetPosition; Result := FileSeek(FHandle, Int64(0), 2); if Result = -1 then S := GetLastOSErrorMessage; SetPosition(I); if Result = -1 then raise EFileReader.CreateFmt(SFileError, [S]); end; function TFileReader.Read(var Buffer; const Size: Integer): Integer; var I : Integer; begin if Size <= 0 then begin Result := 0; exit; end; I := FileRead(FHandle, Buffer, Size); if I <= 0 then begin Result := 0; exit; end; Result := I; end; function TFileReader.EOF: Boolean; begin Result := GetPosition >= GetSize; end; { ReadFileToStrB } function ReadFileToStrB(const FileName: String): RawByteString; var F : TFileReader; begin F := TFileReader.Create(FileName); try Result := F.GetAsStringB; finally F.Free; end; end; { } { AReaderProxy } { } constructor AReaderProxy.Create(const Reader: AReaderEx; const ReaderOwner: Boolean); begin Assert(Assigned(Reader)); inherited Create; FReader := Reader; FReaderOwner := ReaderOwner; end; destructor AReaderProxy.Destroy; begin if FReaderOwner then FreeAndNil(FReader); inherited Destroy; end; function AReaderProxy.Read(var Buffer; const Size: Integer): Integer; begin Result := FReader.Read(Buffer, Size); end; function AReaderProxy.EOF: Boolean; begin Result := FReader.EOF; end; function AReaderProxy.GetPosition: Int64; begin Result := FReader.GetPosition; end; procedure AReaderProxy.SetPosition(const Position: Int64); begin FReader.SetPosition(Position); end; function AReaderProxy.GetSize: Int64; begin Result := FReader.GetSize; end; { } { TReaderProxy } { } constructor TReaderProxy.Create(const Reader: AReaderEx; const ReaderOwner: Boolean; const Size: Int64); begin inherited Create(Reader, ReaderOwner); FOffset := Reader.GetPosition; FSize := Size; end; function TReaderProxy.GetPosition: Int64; begin Result := FReader.GetPosition - FOffset; end; procedure TReaderProxy.SetPosition(const Position: Int64); begin if Position < 0 then raise EReader.Create(SSeekError); if (FSize >= 0) and (Position > FOffset + FSize) then raise EReader.Create(SSeekError); FReader.SetPosition(FOffset + Position); end; function TReaderProxy.GetSize: Int64; begin Result := FReader.GetSize; if Result >= FOffset then Dec(Result, FOffset) else Result := -1; if (FSize >= 0) and (FSize < Result) then Result := FSize; end; function TReaderProxy.EOF: Boolean; begin Result := FReader.EOF; if Result or (FSize < 0) then exit; Result := FReader.Position >= FOffset + FSize; end; function TReaderProxy.Read(var Buffer; const Size: Integer): Integer; var L : Integer; M : Int64; begin L := Size; if FSize >= 0 then begin M := FSize - (FReader.Position - FOffset); if M < L then L := Integer(M); end; if L <= 0 then begin Result := 0; exit; end; Result := FReader.Read(Buffer, L); end; { } { TBufferedReader } { } constructor TBufferedReader.Create(const Reader: AReaderEx; const BufferSize: Integer; const ReaderOwner: Boolean); begin inherited Create(Reader, ReaderOwner); FBufferSize := BufferSize; GetMem(FBuffer, BufferSize); FPos := Reader.GetPosition; end; destructor TBufferedReader.Destroy; begin if Assigned(FBuffer) then FreeMem(FBuffer); inherited Destroy; end; function TBufferedReader.GetPosition: Int64; begin Result := FPos; end; function TBufferedReader.GetSize: Int64; begin Result := FReader.GetSize; end; procedure TBufferedReader.SetPosition(const Position: Int64); var B, C : Int64; begin B := Position - FPos; if B = 0 then exit; C := B + FBufPos; if (C >= 0) and (C <= FBufUsed) then begin Inc(FBufPos, Integer(B)); FPos := Position; exit; end; FReader.SetPosition(Position); FPos := Position; FBufPos := 0; FBufUsed := 0; end; procedure TBufferedReader.Skip(const Count: Integer); var I : Integer; P : Int64; begin if Count < 0 then raise EReader.Create(SSeekError); if Count = 0 then exit; I := FBufUsed - FBufPos; if I >= Count then begin Inc(FBufPos, Count); Inc(FPos, Count); exit; end; P := GetPosition + Count; FReader.SetPosition(P); FPos := P; FBufPos := 0; FBufUsed := 0; end; // Internal function FillBuf // Returns True if buffer was only partially filled function TBufferedReader.FillBuf: Boolean; var P : PByte; L, N : Integer; begin L := FBufferSize - FBufUsed; if L <= 0 then begin Result := False; exit; end; P := FBuffer; Inc(P, FBufPos); N := FReader.Read(P^, L); Inc(FBufUsed, N); Result := N < L; end; function TBufferedReader.Read(var Buffer; const Size: Integer): Integer; var L, M : Integer; P, Q : PByte; R : Boolean; begin if Size <= 0 then begin Result := 0; exit; end; Q := @Buffer; M := Size; R := False; repeat L := FBufUsed - FBufPos; if L > M then L := M; if L > 0 then begin P := FBuffer; Inc(P, FBufPos); MoveMem(P^, Q^, L); Inc(FBufPos, L); Inc(FPos, L); Dec(M, L); if M = 0 then begin Result := Size; exit; end; Inc(Q, L); end; FBufPos := 0; FBufUsed := 0; if R then begin Result := Size - M; exit; end; R := FillBuf; until False; end; function TBufferedReader.EOF: Boolean; begin if FBufUsed > FBufPos then Result := False else Result := FReader.EOF; end; procedure TBufferedReader.InvalidateBuffer; begin FReader.SetPosition(FPos); FBufPos := 0; FBufUsed := 0; end; // Internal function BufferByte // Fills buffer with at least one character, otherwise raises an exception procedure TBufferedReader.BufferByte; var I : Integer; begin I := FBufUsed; if FBufPos < I then exit; if I >= FBufferSize then begin FBufPos := 0; FBufUsed := 0; end; FillBuf; if FBufPos >= FBufUsed then RaiseReadError; end; function TBufferedReader.ReadByte: Byte; var P : PByte; begin BufferByte; P := FBuffer; Inc(P, FBufPos); Result := P^; Inc(FBufPos); Inc(FPos); end; function TBufferedReader.PeekByte: Byte; var P : PByte; begin BufferByte; P := FBuffer; Inc(P, FBufPos); Result := P^; end; function TBufferedReader.PosBuf(const C: ByteCharSet; const LocateNonMatch: Boolean; const MaxOffset: Integer): Integer; var P : PByteChar; L : Integer; begin P := FBuffer; L := FBufPos; Inc(P, L); Result := 0; while (L < FBufUsed) and ((MaxOffset < 0) or (Result <= MaxOffset)) do if (P^ in C) xor LocateNonMatch then exit else begin Inc(P); Inc(L); Inc(Result); end; Result := -1; end; function TBufferedReader.Locate(const C: ByteCharSet; const LocateNonMatch: Boolean; const MaxOffset: Integer): Integer; var I, J, M, K : Integer; P : Int64; R : Boolean; begin P := GetPosition; M := MaxOffset; J := 0; R := False; repeat K := FBufUsed - FBufPos; if K > 0 then begin I := PosBuf(C, LocateNonMatch, M); if I >= 0 then begin SetPosition(P); Result := J + I; exit; end; end; if R then begin SetPosition(P); Result := -1; exit; end; Inc(J, K); Inc(FPos, K); FBufPos := 0; FBufUsed := 0; if M >= 0 then begin Dec(M, K); if M < 0 then begin SetPosition(P); Result := -1; exit; end; end; R := FillBuf; until False; end; { } { TSplitBufferedReader } { } constructor TSplitBufferedReader.Create(const Reader: AReaderEx; const BufferSize: Integer; const ReaderOwner: Boolean); var I : Integer; begin inherited Create(Reader, ReaderOwner); FBufferSize := BufferSize; for I := 0 to 1 do GetMem(FBuffer[I], BufferSize); FPos := Reader.GetPosition; end; destructor TSplitBufferedReader.Destroy; var I : Integer; begin for I := 0 to 1 do if Assigned(FBuffer[I]) then FreeMem(FBuffer[I]); inherited Destroy; end; function TSplitBufferedReader.GetSize: Int64; begin Result := FReader.GetSize; end; function TSplitBufferedReader.GetPosition: Int64; begin Result := FPos; end; // Internal function BufferStart used by SetPosition // Returns the relative offset of the first buffered byte function TSplitBufferedReader.BufferStart: Integer; begin Result := -FBufPos; if FBufNr = 1 then Dec(Result, FBufUsed[0]); end; // Internal function BufferRemaining used by SetPosition // Returns the length of the remaining buffered data function TSplitBufferedReader.BufferRemaining: Integer; begin Result := FBufUsed[FBufNr] - FBufPos; if FBufNr = 0 then Inc(Result, FBufUsed[1]); end; procedure TSplitBufferedReader.SetPosition(const Position: Int64); var D : Int64; begin D := Position - FPos; if D = 0 then exit; if (D >= BufferStart) and (D <= BufferRemaining) then begin Inc(FBufPos, D); if (FBufNr = 1) and (FBufPos < 0) then // Set position from Buf1 to Buf0 begin Inc(FBufPos, FBufUsed[0]); FBufNr := 0; end else if (FBufNr = 0) and (FBufPos > FBufUsed[0]) then // Set position from Buf0 to Buf1 begin Dec(FBufPos, FBufUsed[0]); FBufNr := 1; end; FPos := Position; exit; end; FReader.SetPosition(Position); FPos := Position; FBufNr := 0; FBufPos := 0; FBufUsed[0] := 0; FBufUsed[1] := 0; end; procedure TSplitBufferedReader.InvalidateBuffer; begin FReader.SetPosition(FPos); FBufNr := 0; FBufPos := 0; FBufUsed[0] := 0; FBufUsed[1] := 0; end; // Internal function MoveBuf used by Read // Moves remaining data from Buffer[BufNr]^[BufPos] to Dest // Returns True if complete (Remaining=0) function TSplitBufferedReader.MoveBuf(var Dest: PByte; var Remaining: Integer): Boolean; var P : PByte; L, R, N, O : Integer; begin N := FBufNr; O := FBufPos; L := FBufUsed[N] - O; if L <= 0 then begin Result := False; exit; end; P := FBuffer[N]; Inc(P, O); R := Remaining; if R < L then L := R; MoveMem(P^, Dest^, L); Inc(Dest, L); Inc(FBufPos, L); Dec(R, L); if R <= 0 then Result := True else Result := False; Remaining := R; end; // Internal function FillBuf used by Read // Fill Buffer[BufNr]^[BufPos] with up to BufferSize bytes and moves // the read data to Dest // Returns True if complete (incomplete Read or Remaining=0) function TSplitBufferedReader.FillBuf(var Dest: PByte; var Remaining: Integer): Boolean; var P : PByte; I, L, N : Integer; begin N := FBufNr; I := FBufUsed[N]; L := FBufferSize - I; if L <= 0 then begin Result := False; exit; end; P := FBuffer[N]; Inc(P, I); I := FReader.Read(P^, L); if I > 0 then begin Inc(FBufUsed[N], I); if MoveBuf(Dest, Remaining) then begin Result := True; exit; end; end; Result := I < L; end; function TSplitBufferedReader.Read(var Buffer; const Size: Integer): Integer; var Dest : PByte; Remaining : Integer; begin if Size <= 0 then begin Result := 0; exit; end; Dest := @Buffer; Remaining := Size; repeat if MoveBuf(Dest, Remaining) then begin Result := Size; Inc(FPos, Size); exit; end; if FillBuf(Dest, Remaining) then begin Result := Size - Remaining; Inc(FPos, Result); exit; end; if FBufNr = 0 then FBufNr := 1 else begin Swap(FBuffer[0], FBuffer[1]); FBufUsed[0] := FBufUsed[1]; FBufUsed[1] := 0; end; FBufPos := 0; until False; end; function TSplitBufferedReader.EOF: Boolean; begin if FBufUsed[FBufNr] > FBufPos then Result := False else if FBufNr = 1 then Result := FReader.EOF else if FBufUsed[1] > 0 then Result := False else Result := FReader.EOF; end; { } { TBufferedFileReader } { } constructor TBufferedFileReader.Create(const FileName: String; const BufferSize: Integer); begin inherited Create(TFileReader.Create(FileName), BufferSize, True); end; constructor TBufferedFileReader.Create(const FileHandle: Integer; const HandleOwner: Boolean; const BufferSize: Integer); begin inherited Create(TFileReader.Create(FileHandle, HandleOwner), BufferSize, True); end; { } { TSplitBufferedFileReader } { } constructor TSplitBufferedFileReader.Create(const FileName: String; const BufferSize: Integer); begin inherited Create(TFileReader.Create(FileName), BufferSize, True); end; { } { AWriterEx } { } procedure AWriterEx.RaiseWriteError; begin raise EWriter.Create(SWriteError); end; function AWriterEx.GetPosition: Int64; begin raise EWriter.Create(SNotSupported); end; procedure AWriterEx.SetPosition(const Position: Int64); begin raise EWriter.Create(SSeekError); end; function AWriterEx.GetSize: Int64; begin raise EWriter.Create(SNotSupported); end; procedure AWriterEx.SetSize(const Size: Int64); begin raise EWriter.Create(SNotSupported); end; procedure AWriterEx.Append; begin Position := Size; end; procedure AWriterEx.Truncate; begin Size := Position; end; procedure AWriterEx.Clear; begin Size := 0; end; procedure AWriterEx.WriteBuffer(const Buffer; const Size: Integer); begin if Size <= 0 then exit; if Write(Buffer, Size) <> Size then RaiseWriteError; end; procedure AWriterEx.WriteCharA(const V: AnsiChar); begin Write(V, SizeOf(AnsiChar)); end; procedure AWriterEx.WriteCharW(const V: WideChar); begin Write(V, SizeOf(WideChar)); end; {$IFDEF SupportAnsiString} procedure AWriterEx.WriteStrA(const Buffer: AnsiString); var I : Integer; begin for I := 1 to Length(Buffer) do WriteCharA(Buffer[I]); end; {$ENDIF} procedure AWriterEx.WriteStrB(const Buffer: RawByteString); var I : Integer; begin for I := 1 to Length(Buffer) do WriteCharA(Buffer[I]); end; procedure AWriterEx.WriteStrU(const Buffer: UnicodeString); var I : Integer; begin for I := 1 to Length(Buffer) do WriteCharW(Buffer[I]); end; procedure AWriterEx.SetAsStringB(const S: RawByteString); begin Position := 0; WriteStrB(S); Truncate; end; procedure AWriterEx.SetAsStringU(const S: UnicodeString); begin Position := 0; WriteStrU(S); Truncate; end; procedure AWriterEx.WriteByte(const V: Byte); begin WriteBuffer(V, Sizeof(Byte)); end; procedure AWriterEx.WriteWord(const V: Word); begin WriteBuffer(V, Sizeof(Word)); end; procedure AWriterEx.WriteLongWord(const V: LongWord); begin WriteBuffer(V, Sizeof(LongWord)); end; procedure AWriterEx.WriteLongInt(const V: LongInt); begin WriteBuffer(V, Sizeof(LongInt)); end; procedure AWriterEx.WriteInt64(const V: Int64); begin WriteBuffer(V, Sizeof(Int64)); end; procedure AWriterEx.WriteSingle(const V: Single); begin WriteBuffer(V, Sizeof(Single)); end; procedure AWriterEx.WriteDouble(const V: Double); begin WriteBuffer(V, Sizeof(Double)); end; procedure AWriterEx.WriteFloat(const V: Float); begin WriteBuffer(V, Sizeof(Float)); end; {$IFDEF SupportAnsiString} procedure AWriterEx.WritePackedAnsiString(const V: AnsiString); begin WriteLongInt(Length(V)); WriteStrA(V); end; {$ENDIF} procedure AWriterEx.WritePackedRawByteString(const V: RawByteString); begin WriteLongInt(Length(V)); WriteStrB(V); end; procedure AWriterEx.WritePackedUnicodeString(const V: UnicodeString); begin WriteLongInt(Length(V)); WriteStrU(V); end; procedure AWriterEx.WritePackedString(const V: String); begin {$IFDEF StringIsUnicode} WritePackedUnicodeString(V); {$ELSE} WritePackedRawByteString(V); {$ENDIF} end; {$IFDEF SupportAnsiString} procedure AWriterEx.WritePackedAnsiStringArray(const V: array of AnsiString); var I, L : Integer; begin L := Length(V); WriteLongInt(L); for I := 0 to L - 1 do WritePackedAnsiString(V[I]); end; {$ENDIF} procedure AWriterEx.WritePackedUnicodeStringArray(const V: array of UnicodeString); var I, L : Integer; begin L := Length(V); WriteLongInt(L); for I := 0 to L - 1 do WritePackedUnicodeString(V[I]); end; procedure AWriterEx.WritePackedStringArray(const V: array of String); var I, L : Integer; begin L := Length(V); WriteLongInt(L); for I := 0 to L - 1 do WritePackedString(V[I]); end; procedure AWriterEx.WriteBufLine(const Buffer; const Size: Integer; const NewLineType: TWriterNewLineType); begin WriteBuffer(Buffer, Size); case NewLineType of nlCR : WriteByte(13); nlLF : WriteByte(10); nlCRLF : begin WriteByte(13); WriteByte(10); end; nlLFCR : begin WriteByte(10); WriteByte(13); end; end; end; procedure AWriterEx.WriteLineB(const S: RawByteString; const NewLineType: TWriterNewLineType); begin WriteBufLine(Pointer(S)^, Length(S), NewLineType); end; { } { TFileWriter } { } constructor TFileWriter.Create(const FileName: String; const OpenMode: TFileWriterOpenMode; const Options: TFileWriterOptions; const AccessHint: TFileWriterAccessHint); var CreateFile : Boolean; {$IFDEF MSWIN} F : LongWord; D : LongWord; {$ENDIF} begin inherited Create; FFileName := FileName; case OpenMode of fwomCreate : CreateFile := True; fwomCreateIfNotExist : CreateFile := not FileExists(FileName); {$IFNDEF MSWIN} fwomTruncate : CreateFile := True; {$ENDIF} else CreateFile := False; end; {$IFDEF MSWIN} F := FILE_ATTRIBUTE_NORMAL; case AccessHint of fwahNone : ; fwahRandomAccess : F := F or FILE_FLAG_RANDOM_ACCESS; fwahSequentialAccess : F := F or FILE_FLAG_SEQUENTIAL_SCAN; end; if fwoWriteThrough in Options then F := F or FILE_FLAG_WRITE_THROUGH; if CreateFile then D := CREATE_ALWAYS else D := OPEN_EXISTING; {$IFDEF StringIsUnicode} FHandle := Integer(Windows.CreateFileW(PWideChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, D, F, 0)); {$ELSE} FHandle := Integer(Windows.CreateFileA(PAnsiChar(FileName), GENERIC_READ or GENERIC_WRITE, 0, nil, D, F, 0)); {$ENDIF} {$ELSE} if CreateFile then FHandle := FileCreate(FileName) else FHandle := FileOpen(FileName, fmOpenReadWrite); {$ENDIF} if FHandle = -1 then raise EFileWriter.CreateFmt(SFileOpenError, [GetLastOSErrorMessage]); FHandleOwner := True; FFileCreated := CreateFile; {$IFDEF MSWIN} if OpenMode = fwomTruncate then if not SetEndOfFile(FHandle) then raise EFileWriter.CreateFmt(SFileTruncateError, [GetLastOSErrorMessage]); {$ENDIF} end; constructor TFileWriter.Create(const FileHandle: Integer; const HandleOwner: Boolean); begin inherited Create; FHandle := FileHandle; FHandleOwner := HandleOwner; end; destructor TFileWriter.Destroy; begin if FHandleOwner and (FHandle <> -1) and (FHandle <> 0) then FileClose(FHandle); inherited Destroy; end; function TFileWriter.GetPosition: Int64; begin Result := FileSeek(FHandle, Int64(0), 1); if Result = -1 then raise EFileWriter.CreateFmt(SFileError, [GetLastOSErrorMessage]); end; procedure TFileWriter.SetPosition(const Position: Int64); begin if FileSeek(FHandle, Position, 0) = -1 then raise EFileWriter.CreateFmt(SFileSeekError, [GetLastOSErrorMessage]); end; function TFileWriter.GetSize: Int64; var I : Int64; S : String; begin I := GetPosition; Result := FileSeek(FHandle, Int64(0), 2); if Result = -1 then S := GetLastOSErrorMessage; SetPosition(I); if Result = -1 then raise EFileWriter.CreateFmt(SFileError, [S]); end; procedure TFileWriter.SetSize(const Size: Int64); begin {$IFDEF MSWIN} SetPosition(Size); if not SetEndOfFile(FHandle) then raise EFileWriter.CreateFmt(SFileResizeError, [GetLastOSErrorMessage]); {$ELSE}{$IFDEF UNIX} {$IFDEF FREEPASCAL} SetPosition(Size); if fpftruncate(FHandle, Size) <> 0 then raise EFileWriter.CreateFmt(SFileResizeError, [GetLastOSErrorMessage]); {$ELSE} raise EFileWriter.Create('TruncateFile not implemented'); {$ENDIF} {$ELSE} SetPosition(Size); {$IFDEF ANDROID} raise EFileWriter.Create('TruncateFile not implemented'); {$ELSE} {$IFDEF OSX} raise EFileWriter.Create('TruncateFile not implemented'); {$ELSE} raise EFileWriter.Create('TruncateFile not implemented'); {$ENDIF} {$ENDIF}{$ENDIF} {$ENDIF} end; function TFileWriter.Write(const Buffer; const Size: Integer): Integer; var I : Integer; begin if Size <= 0 then begin Result := 0; exit; end; I := FileWrite(FHandle, Buffer, Size); if I < 0 then raise EFileWriter.CreateFmt(SFileWriteError, [GetLastOSErrorMessage]); Result := I; end; procedure TFileWriter.Flush; begin {$IFDEF MSWIN} if not FlushFileBuffers(FHandle) then raise EFileWriter.CreateFmt(SFileError, [GetLastOSErrorMessage]); {$ENDIF} end; procedure TFileWriter.DeleteFile; begin if FFileName = '' then raise EFileWriter.Create(SInvalidFileName); if (FHandle <> -1) and (FHandle <> 0) then FileClose(FHandle); FHandle := -1; if not SysUtils.DeleteFile(FFileName) then raise EFileWriter.CreateFmt(SFileError, [GetLastOSErrorMessage]); end; procedure WriteStrToFileB(const FileName: String; const S: RawByteString; const OpenMode: TFileWriterOpenMode); var F : TFileWriter; begin F := TFileWriter.Create(FileName, OpenMode); try F.SetAsStringB(S); finally F.Free; end; end; procedure AppendStrToFileB(const FileName: String; const S: RawByteString); var F : TFileWriter; begin F := TFileWriter.Create(FileName, fwomCreateIfNotExist); try F.Append; F.WriteStrB(S); finally F.Free; end; end; procedure WriteUnicodeStrToFile(const FileName: String; const S: UnicodeString; const OpenMode: TFileWriterOpenMode); var F : TFileWriter; begin F := TFileWriter.Create(FileName, OpenMode); try F.SetAsStringU(S); finally F.Free; end; end; procedure AppendUnicodeStrToFile(const FileName: String; const S: UnicodeString); var F : TFileWriter; begin F := TFileWriter.Create(FileName, fwomCreateIfNotExist); try F.Append; F.WriteStrU(S); finally F.Free; end; end; { } { TRawByteStringWriter } { } function TRawByteStringWriter.GetPosition: Int64; begin Result := FPos; end; procedure TRawByteStringWriter.SetPosition(const Position: Int64); begin if (Position < 0) or (Position > High(Integer)) then raise EFileWriter.Create(SInvalidPosition); FPos := Integer(Position); end; function TRawByteStringWriter.GetSize: Int64; begin Result := FDataSize; end; procedure TRawByteStringWriter.SetSize(const Size: Integer); var L : Integer; begin if Size = FDataSize then exit; L := Length(FDataStr); if Size > L then begin // memory allocation strategy if L = 0 then // first allocation is exactly as requested L := Size else if Size < 16 then // if grow to < 16 then allocate 16 L := 16 else // if grow to >= 16 then pre-allocate 1/4 L := Size + (Size shr 2); SetLength(FDataStr, L); end; FDataSize := Size; end; procedure TRawByteStringWriter.SetSize(const Size: Int64); begin if Size > High(Integer) then raise EFileWriter.Create(SInvalidSize); SetSize(Integer(Size)); end; function TRawByteStringWriter.GetAsStringB: RawByteString; var L : Integer; begin L := Length(FDataStr); if L = FDataSize then Result := FDataStr else Result := Copy(FDataStr, 1, FDataSize); end; procedure TRawByteStringWriter.SetAsStringB(const S: RawByteString); begin FDataStr := S; FDataSize := Length(S); FPos := FDataSize; end; function TRawByteStringWriter.Write(const Buffer; const Size: Integer): Integer; var I, J : Integer; P : PByteChar; begin if Size <= 0 then begin Result := 0; exit; end; I := FPos; J := I + Size; if J > FDataSize then SetSize(J); P := Pointer(FDataStr); Inc(P, I); Move(Buffer, P^, Size); Result := Size; FPos := J; end; procedure TRawByteStringWriter.WriteCharW(const V: WideChar); begin if Ord(V) > $FF then raise EWriter.Create(SCharacterSizeError); WriteCharA(AnsIChar(Ord(V))); end; procedure TRawByteStringWriter.WriteStrB(const Buffer: RawByteString); begin Write(Pointer(Buffer)^, Length(Buffer)); end; procedure TRawByteStringWriter.WriteByte(const V: Byte); var I, J : Integer; P : PByteChar; begin I := FPos; J := I + 1; if J > FDataSize then SetSize(J); P := Pointer(FDataStr); Inc(P, I); PByte(P)^ := V; FPos := J; end; { } { TUnicodeStringWriter } { } function TUnicodeStringWriter.GetPosition: Int64; begin Result := FPos; end; procedure TUnicodeStringWriter.SetPosition(const Position: Int64); begin if (Position < 0) or (Position > High(Integer)) then raise EFileWriter.Create(SInvalidPosition); FPos := Integer(Position); end; function TUnicodeStringWriter.GetSize: Int64; begin Result := FDataSize; end; procedure TUnicodeStringWriter.SetSize(const Size: Integer); var L : Integer; begin if Size = FDataSize then exit; L := Length(FDataStr) * Sizeof(WideChar); if Size > L then begin // memory allocation strategy if L = 0 then // first allocation is exactly as request L := Size else if Size < 16 then // if grow to < 16 then allocate 16 L := 16 else L := Size + (Size shr 2); // if grow to > 16 then pre-allocate 1/4 SetLength(FDataStr, (L + 1) div Sizeof(WideChar)); end; FDataSize := Size; end; procedure TUnicodeStringWriter.SetSize(const Size: Int64); begin if Size > High(Integer) then raise EFileWriter.Create(SInvalidSize); SetSize(Integer(Size)); end; function TUnicodeStringWriter.GetAsStringU: UnicodeString; var L : Integer; begin L := Length(FDataStr) * Sizeof(WideChar); if L = FDataSize then Result := FDataStr else Result := Copy(FDataStr, 1, FDataSize div Sizeof(WideChar)); end; procedure TUnicodeStringWriter.SetAsStringU(const S: UnicodeString); begin FDataStr := S; FDataSize := Length(S) * Sizeof(WideChar); FPos := FDataSize; end; function TUnicodeStringWriter.Write(const Buffer; const Size: Integer): Integer; var I, J : Integer; P : PByteChar; begin if Size <= 0 then begin Result := 0; exit; end; I := FPos; J := I + Size; if J > FDataSize then SetSize(J); P := Pointer(FDataStr); Inc(P, I); Move(Buffer, P^, Size); Result := Size; FPos := J; end; procedure TUnicodeStringWriter.WriteCharA(const V: AnsiChar); begin WriteCharW(WideChar(V)); end; procedure TUnicodeStringWriter.WriteCharW(const V: WideChar); begin Write(V, SizeOf(WideChar)); end; procedure TUnicodeStringWriter.WriteStrU(const Buffer: UnicodeString); begin Write(Pointer(Buffer)^, Length(Buffer) * Sizeof(WideChar)); end; procedure TUnicodeStringWriter.WriteByte(const V: Byte); var I, J : Integer; P : PByteChar; begin I := FPos; J := I + 1; if J > FDataSize then SetSize(J); P := Pointer(FDataStr); Inc(P, I); PByte(P)^ := V; FPos := J; end; procedure TUnicodeStringWriter.WriteWord(const V: Word); var I, J : Integer; P : PByteChar; begin I := FPos; J := I + 2; if J > FDataSize then SetSize(J); P := Pointer(FDataStr); Inc(P, I); PWord(P)^ := V; FPos := J; end; { } { TOutputWriter } { } function TOutputWriter.Write(const Buffer; const Size: Integer): Integer; var I : Integer; P : PByte; begin if Size <= 0 then begin Result := 0; exit; end; P := @Buffer; for I := 1 to Size do begin System.Write(Char(P^)); Inc(P); end; Result := Size; end; { } { EStreamOperationAborted } { } constructor EStreamOperationAborted.Create; begin inherited Create('Stream operation aborted'); end; { } { TStreamReaderProxy } { } constructor TStreamReaderProxy.Create(const Stream: AStream); begin inherited Create; Assert(Assigned(Stream)); FStream := Stream; end; function TStreamReaderProxy.GetPosition: Int64; begin Result := FStream.Position; end; procedure TStreamReaderProxy.SetPosition(const Position: Int64); begin FStream.Position := Position; end; function TStreamReaderProxy.GetSize: Int64; begin Result := FStream.Size; end; function TStreamReaderProxy.Read(var Buffer; const Size: Integer): Integer; begin Result := FStream.Read(Buffer, Size) end; function TStreamReaderProxy.EOF: Boolean; begin Result := FStream.EOF; end; { } { TStreamWriterProxy } { } constructor TStreamWriterProxy.Create(const Stream: AStream); begin inherited Create; Assert(Assigned(Stream)); FStream := Stream; end; function TStreamWriterProxy.GetPosition: Int64; begin Result := FStream.Position; end; procedure TStreamWriterProxy.SetPosition(const Position: Int64); begin FStream.Position := Position; end; function TStreamWriterProxy.GetSize: Int64; begin Result := FStream.Size; end; procedure TStreamWriterProxy.SetSize(const Size: Int64); begin FStream.Size := Size; end; function TStreamWriterProxy.Write(const Buffer; const Size: Integer): Integer; begin Result := FStream.Write(Buffer, Size) end; { } { CopyStream } { } const DefaultBlockSize = 2048; function CopyStream(const Source, Destination: AStream; const SourceOffset: Int64; const DestinationOffset: Int64; const BlockSize: Integer; const Count: Int64; const ProgressCallback: TCopyProgressProcedure; const CopyFromBack: Boolean): Int64; var Buf : Pointer; L, I, C : Integer; R, S, D : Int64; A : Boolean; begin if not Assigned(Source) then raise EStream.Create(SInvalidParameter); if not Assigned(Destination) then raise EStream.Create(SInvalidParameter); S := SourceOffset; D := DestinationOffset; if (S < 0) or (D < 0) then raise EStream.Create(SInvalidParameter); if (Source = Destination) and (Count < 0) and (S < D) then raise EStream.Create(SInvalidParameter); A := False; if Assigned(ProgressCallback) then begin ProgressCallback(Source, Destination, 0, A); if A then raise EStreamOperationAborted.Create; end; Result := 0; R := Count; if R = 0 then exit; L := BlockSize; if L <= 0 then L := DefaultBlockSize; if (R > 0) and (R < L) then L := Integer(R); if CopyFromBack then begin if R < 0 then raise EStream.Create(SInvalidParameter); Inc(S, R - L); Inc(D, R - L); end; GetMem(Buf, L); try while not Source.EOF and (R <> 0) do begin C := L; if (R > 0) and (R < C) then C := Integer(R); if CopyFromBack then Source.Position := S; I := Source.Read(Buf^, C); if (I <= 0) and not Source.EOF then raise EStream.Create(SStreamReadError); if CopyFromBack then Destination.Position := D; Destination.WriteBuffer(Buf^, I); Inc(Result, I); if R > 0 then Dec(R, I); if CopyFromBack then begin Dec(S, I); Dec(D, I); end else begin Inc(S, I); Inc(D, I); end; if Assigned(ProgressCallback) then begin ProgressCallback(Source, Destination, Result, A); if A then raise EStreamOperationAborted.Create; end; end; finally FreeMem(Buf); end; end; function CopyStream(const Source: AReaderEx; const Destination: AWriterEx; const BlockSize: Integer; const CopyDataEvent: TCopyDataEvent): Int64; var Buf : Pointer; L, I : Integer; begin if not Assigned(Source) then raise EStream.Create(SInvalidParameter); if not Assigned(Destination) then raise EStream.Create(SInvalidParameter); L := BlockSize; if L <= 0 then L := DefaultBlockSize; Result := 0; GetMem(Buf, L); try while not Source.EOF do begin I := Source.Read(Buf^, L); if (I = 0) and not Source.EOF then Source.RaiseReadError; Destination.WriteBuffer(Buf^, I); if Assigned(CopyDataEvent) then CopyDataEvent(Result, Buf, I); Inc(Result, I); end; finally FreeMem(Buf); end; end; procedure DeleteStreamRange(const Stream: AStream; const Position, Count: Int64; const ProgressCallback: TCopyProgressProcedure); begin if Count <= 0 then exit; if CopyStream(Stream, Stream, Position + Count, Position, 0, Count, ProgressCallback, False) <> Count then raise EStream.Create(SStreamCopyError); end; procedure InsertStreamRange(const Stream: AStream; const Position, Count: Int64; const ProgressCallback: TCopyProgressProcedure); begin if Count <= 0 then exit; if CopyStream(Stream, Stream, Position, Position + Count, 0, Count, ProgressCallback, True) <> Count then raise EStream.Create(SStreamCopyError); end; { } { AStream } { } function AStream.EOF: Boolean; begin Result := Position >= Size; end; procedure AStream.Truncate; begin Size := Position; end; procedure AStream.ReadBuffer(var Buffer; const Size: Integer); begin if Size <= 0 then exit; if Read(Buffer, Size) <> Size then raise EStream.Create(SReadError); end; function AStream.ReadByte: Byte; begin ReadBuffer(Result, 1); end; function AStream.ReadStrB(const Size: Integer): RawByteString; var L : Integer; begin if Size <= 0 then begin Result := ''; exit; end; SetLength(Result, Size); L := Read(Pointer(Result)^, Size); if L <= 0 then begin Result := ''; exit; end; if L < Size then SetLength(Result, L); end; procedure AStream.WriteBuffer(const Buffer; const Size: Integer); begin if Size <= 0 then exit; if Write(Buffer, Size) <> Size then raise EStream.Create(SWriteError); end; procedure AStream.WriteStrB(const S: RawByteString); begin WriteBuffer(Pointer(S)^, Length(S)); end; procedure AStream.WriteStrU(const S: UnicodeString); begin WriteBuffer(Pointer(S)^, Length(S) * SizeOf(WideChar)); end; procedure AStreamCopyCallback(const Source, Destination: AStream; const BytesCopied: Int64; var Abort: Boolean); begin Assert(Assigned(Source) and Assigned(Destination) and not Abort); Source.TriggerCopyProgressEvent(Source, Destination, BytesCopied, Abort); if Abort then exit; Destination.TriggerCopyProgressEvent(Source, Destination, BytesCopied, Abort); end; procedure AStream.TriggerCopyProgressEvent(const Source, Destination: AStream; const BytesCopied: Int64; var Abort: Boolean); begin if Assigned(FOnCopyProgress) then FOnCopyProgress(Source, Destination, BytesCopied, Abort); end; procedure AStream.Assign(const Source: TObject); begin if not Assigned(Source) then raise EStream.Create(SInvalidParameter); if Source is AStream then Size := CopyStream(AStream(Source), self, 0, 0, 0, -1, AStreamCopyCallback, False) else raise EStream.Create(SInvalidParameter); end; function AStream.WriteTo(const Destination: AStream; const BlockSize: Integer; const Count: Int64): Int64; begin Result := CopyStream(self, Destination, Position, Destination.Position, BlockSize, Count, AStreamCopyCallback, False); end; { } { TReaderWriter } { } constructor TReaderWriter.Create(const Reader: AReaderEx; const Writer: AWriterEx; const ReaderOwner: Boolean; const WriterOwner: Boolean); begin inherited Create; FReader := Reader; FReaderOwner := ReaderOwner; FWriter := Writer; FWriterOwner := WriterOwner; end; destructor TReaderWriter.Destroy; begin if FReaderOwner then FReader.Free; FReader := nil; if FWriterOwner then FWriter.Free; FWriter := nil; inherited Destroy; end; procedure TReaderWriter.RaiseNoReaderError; begin raise EReaderWriter.Create(SNoReader); end; procedure TReaderWriter.RaiseNoWriterError; begin raise EReaderWriter.Create(SNoWriter); end; function TReaderWriter.GetReader: AReaderEx; begin Result := FReader; end; function TReaderWriter.GetWriter: AWriterEx; begin Result := FWriter; end; function TReaderWriter.GetPosition: Int64; begin if Assigned(FReader) then Result := FReader.Position else if Assigned(FWriter) then Result := FWriter.Position else Result := 0; end; procedure TReaderWriter.SetPosition(const Position: Int64); begin if Assigned(FReader) then FReader.Position := Position; if Assigned(FWriter) then FWriter.Position := Position; end; function TReaderWriter.GetSize: Int64; begin if Assigned(FWriter) then Result := FWriter.Size else if Assigned(FReader) then Result := FReader.Size else Result := 0; end; procedure TReaderWriter.SetSize(const Size: Int64); begin if not Assigned(FWriter) then RaiseNoWriterError; FWriter.Size := Size; end; function TReaderWriter.Read(var Buffer; const Size: Integer): Integer; begin if not Assigned(FReader) then RaiseNoReaderError; Result := FReader.Read(Buffer, Size); end; function TReaderWriter.Write(const Buffer; const Size: Integer): Integer; begin if not Assigned(FWriter) then RaiseNoWriterError; Result := FWriter.Write(Buffer, Size); end; function TReaderWriter.EOF: Boolean; begin if Assigned(FReader) then Result := FReader.EOF else if Assigned(FWriter) then Result := FWriter.Position >= FWriter.Size else Result := True; end; procedure TReaderWriter.Truncate; begin if not Assigned(FWriter) then RaiseNoWriterError; FWriter.Truncate; end; { } { TFileStream } { } const WriterModes: array[TFileStreamOpenMode] of TFileWriterOpenMode = (fwomOpen, fwomOpen, fwomCreate, fwomCreateIfNotExist, fwomOpen); ReaderAccessHints: array[TFileStreamAccessHint] of TFileReaderAccessHint = (frahNone, frahRandomAccess, frahSequentialAccess); WriterAccessHints: array[TFileStreamAccessHint] of TFileWriterAccessHint = (fwahNone, fwahRandomAccess, fwahSequentialAccess); constructor TFileStream.Create(const FileName: String; const OpenMode: TFileStreamOpenMode; const Options: TFileStreamOptions; const AccessHint: TFileStreamAccessHint); var W : TFileWriter; R : AReaderEx; T : TFileWriterOptions; begin FFileName := FileName; FOpenMode := OpenMode; FOptions := Options; FAccessHint := AccessHint; R := nil; W := nil; T := []; if fsoWriteThrough in Options then Include(T, fwoWriteThrough); case OpenMode of fsomRead : R := TFileReader.Create(FileName, ReaderAccessHints[AccessHint]); fsomCreateOnWrite : try W := TFileWriter.Create(FileName, fwomOpen, T, WriterAccessHints[AccessHint]); except W := nil; end; else W := TFileWriter.Create(FileName, WriterModes[OpenMode], T, WriterAccessHints[AccessHint]); end; if Assigned(W) then try R := TFileReader.Create(W.Handle, False); except W.Free; raise; end; inherited Create(R, W, True, True); end; constructor TFileStream.Create(const FileHandle: Integer; const HandleOwner: Boolean); var W : TFileWriter; R : TFileReader; begin W := TFileWriter.Create(FileHandle, HandleOwner); try R := TFileReader.Create(FileHandle, False); except W.Free; raise; end; inherited Create(R, W, True, True); end; function TFileStream.GetFileHandle: Integer; begin Assert(Assigned(FReader)); Result := TFileReader(FReader).Handle; end; function TFileStream.GetFileCreated: Boolean; begin Result := Assigned(FWriter) and TFileWriter(FWriter).FileCreated; end; procedure TFileStream.DeleteFile; begin if FFileName = '' then raise EFileStream.Create(SInvalidFileName); SysUtils.DeleteFile(FFileName); end; procedure TFileStream.EnsureCreateOnWrite; var T : TFileWriterOptions; begin T := []; if fsoWriteThrough in FOptions then Include(T, fwoWriteThrough); FWriter := TFileWriter.Create(FileName, fwomCreateIfNotExist, T, WriterAccessHints[FAccessHint]); FReader := TFileReader.Create(TFileWriter(FWriter).Handle, False); end; procedure TFileStream.SetPosition(const Position: Int64); begin if (FOpenMode = fsomCreateOnWrite) and not Assigned(FWriter) and (Position > 0) then EnsureCreateOnWrite; if Assigned(FWriter) then FWriter.Position := Position else if Assigned(FReader) then FReader.Position := Position; end; procedure TFileStream.SetSize(const Size: Int64); begin if (FOpenMode = fsomCreateOnWrite) and not Assigned(FWriter) then EnsureCreateOnWrite; inherited SetSize(Size); end; function TFileStream.GetReader: AReaderEx; begin if (FOpenMode = fsomCreateOnWrite) and not Assigned(FWriter) then EnsureCreateOnWrite; Result := FReader; end; function TFileStream.GetWriter: AWriterEx; begin if (FOpenMode = fsomCreateOnWrite) and not Assigned(FWriter) then EnsureCreateOnWrite; Result := FWriter; end; function TFileStream.Write(const Buffer; const Size: Integer): Integer; begin if (FOpenMode = fsomCreateOnWrite) and not Assigned(FWriter) then EnsureCreateOnWrite; Result := inherited Write(Buffer, Size); end; { } { Test cases } { } {$IFDEF STREAMS_TEST} {$ASSERTIONS ON} procedure TestReader(const Reader: AReaderEx; const FreeReader: Boolean); begin try Reader.Position := 0; Assert(not Reader.EOF, 'Reader.EOF'); Assert(Reader.Size = 26, 'Reader.Size'); Assert(Reader.PeekStrB(0) = '', 'Reader.PeekStr'); Assert(Reader.PeekStrB(-1) = '', 'Reader.PeekStr'); Assert(Reader.PeekStrB(2) = '01', 'Reader.PeekStr'); Assert(Char(Reader.PeekByte) = '0', 'Reader.PeekByte'); Assert(Char(Reader.ReadByte) = '0', 'Reader.ReadByte'); Assert(Char(Reader.PeekByte) = '1', 'Reader.PeekByte'); Assert(Char(Reader.ReadByte) = '1', 'Reader.ReadByte'); Assert(Reader.ReadStrB(0) = '', 'Reader.ReadStr'); Assert(Reader.ReadStrB(-1) = '', 'Reader.ReadStr'); Assert(Reader.ReadStrB(1) = '2', 'Reader.ReadStr'); Assert(Reader.MatchChar('3'), 'Reader.MatchChar'); Assert(Reader.MatchStr('3', True), 'Reader.MatchStr'); Assert(Reader.MatchStr('345', True), 'Reader.MatchStr'); Assert(not Reader.MatchStr('35', True), 'Reader.MatchStr'); Assert(not Reader.MatchStr('4', True), 'Reader.MatchStr'); Assert(not Reader.MatchStr('', True), 'Reader.MatchStr'); Assert(Reader.ReadStrB(2) = '34', 'Reader.ReadStr'); Assert(Reader.PeekStrB(3) = '567', 'Reader.PeekStr'); Assert(Reader.Locate('5', False, 0) = 0, 'Reader.Locate'); Assert(Reader.Locate('8', False, -1) = 3, 'Reader.Locate'); Assert(Reader.Locate('8', False, 3) = 3, 'Reader.Locate'); Assert(Reader.Locate('8', False, 2) = -1, 'Reader.Locate'); Assert(Reader.Locate('8', False, 4) = 3, 'Reader.Locate'); Assert(Reader.Locate('0', False, -1) = -1, 'Reader.Locate'); Assert(Reader.Locate(['8'], False, -1) = 3, 'Reader.Locate'); Assert(Reader.Locate(['8'], False, 3) = 3, 'Reader.Locate'); Assert(Reader.Locate(['8'], False, 2) = -1, 'Reader.Locate'); Assert(Reader.Locate(['0'], False, -1) = -1, 'Reader.Locate'); Assert(Reader.LocateStrB('8', -1, True) = 3, 'Reader.LocateStr'); Assert(Reader.LocateStrB('8', 3, True) = 3, 'Reader.LocateStr'); Assert(Reader.LocateStrB('8', 2, True) = -1, 'Reader.LocateStr'); Assert(Reader.LocateStrB('89', -1, True) = 3, 'Reader.LocateStr'); Assert(Reader.LocateStrB('0', -1, True) = -1, 'Reader.LocateStr'); Assert(not Reader.EOF, 'Reader.EOF'); Assert(Reader.Position = 5, 'Reader.Position'); Reader.Position := 7; Reader.SkipByte; Assert(Reader.Position = 8, 'Reader.Position'); Reader.Skip(2); Assert(Reader.Position = 10, 'Reader.Position'); Assert(not Reader.EOF, 'Reader.EOF'); Assert(Reader.MatchStr('abcd', False), 'Reader.MatchStr'); Assert(not Reader.MatchStr('abcd', True), 'Reader.MatchStr'); Assert(Reader.LocateStrB('d', -1, True) = 3, 'Reader.LocateStr'); Assert(Reader.LocateStrB('d', 3, False) = 3, 'Reader.LocateStr'); Assert(Reader.LocateStrB('D', -1, True) = -1, 'Reader.LocateStr'); Assert(Reader.LocateStrB('D', -1, False) = 3, 'Reader.LocateStr'); Assert(Reader.SkipAll('X', False, -1) = 0, 'Reader.SkipAll'); Assert(Reader.SkipAll('A', False, -1) = 1, 'Reader.SkipAll'); Assert(Reader.SkipAll(['b', 'C'], False, -1) = 2, 'Reader.SkipAll'); Assert(Reader.SkipAll(['d'], False, 0) = 0, 'Reader.SkipAll'); Assert(Reader.ExtractAllB(['d', 'E'], False, 1) = 'd', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['*'], True, 1) = 'E', 'Reader.ExtractAll'); Assert(Reader.ReadStrB(2) = '*.', 'Reader.ReadStr'); Assert(Reader.ExtractAllB(['X'], False, 1) = 'X', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['X'], False, -1) = 'XX', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['X', '*'], True, 1) = 'Y', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['X', '*'], True, -1) = 'YYY', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['X'], False, -1) = '', 'Reader.ExtractAll'); Assert(Reader.ExtractAllB(['X'], True, -1) = '*.', 'Reader.ExtractAll'); Assert(Reader.EOF, 'Reader.EOF'); Assert(Reader.Position = 26, 'Reader.Position'); Reader.Position := Reader.Position - 2; Assert(Reader.PeekStrB(3) = '*.', 'Reader.PeekStr'); Assert(Reader.ReadStrB(3) = '*.', 'Reader.ReadStr'); finally if FreeReader then Reader.Free; end; end; procedure TestLineReader(const Reader: AReaderEx; const FreeReader: Boolean); begin try Reader.Position := 0; Assert(not Reader.EOF, 'Reader.EOF'); Assert(Reader.ExtractLineB = '1', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB = '23', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB = '', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB = '4', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB = '5', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB = '6', 'Reader.ExtractLine'); Assert(Reader.EOF, 'Reader.EOF'); Reader.Position := 0; Assert(Reader.ExtractLineB(-1, [eolCRLF, eolEOF]) = '1', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCRLF, eolEOF]) = '23'#13#13'4'#10'5'#10#13'6', 'Reader.ExtractLine'); Assert(Reader.EOF, 'Reader.EOF'); Reader.Position := 0; Assert(Reader.ExtractLineB(-1, [eolCR, eolEOF]) = '1', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolEOF]) = #10'23', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolEOF]) = '', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolEOF]) = '4'#10'5'#10, 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolEOF]) = '6', 'Reader.ExtractLine'); Assert(Reader.EOF, 'Reader.EOF'); Reader.Position := 0; Assert(Reader.ExtractLineB(-1, [eolCR, eolCRLF, eolEOF]) = '1', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolCRLF, eolEOF]) = '23', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolCRLF, eolEOF]) = '', 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolCRLF, eolEOF]) = '4'#10'5'#10, 'Reader.ExtractLine'); Assert(Reader.ExtractLineB(-1, [eolCR, eolCRLF, eolEOF]) = '6', 'Reader.ExtractLine'); Assert(Reader.EOF, 'Reader.EOF'); Reader.Position := 0; Assert(Reader.SkipLine(-1, [eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.SkipLine(-1, [eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.EOF, 'Reader.EOF'); Reader.Position := 0; Assert(Reader.SkipLine(-1, [eolCR, eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.SkipLine(-1, [eolCR, eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.SkipLine(-1, [eolCR, eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.SkipLine(-1, [eolCR, eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.SkipLine(-1, [eolCR, eolCRLF, eolEOF]), 'Reader.SkipLine'); Assert(Reader.EOF, 'Reader.EOF'); finally if FreeReader then Reader.Free; end; end; type TUnsizedStringReader = class(TRawByteStringReader) protected function GetSize: Int64; override; end; function TUnsizedStringReader.GetSize: Int64; begin Result := -1; end; procedure TestUnsizedReader(const Data: RawByteString); var S : TUnsizedStringReader; T : RawByteString; begin S := TUnsizedStringReader.Create(Data); try T := S.GetToEOFB; Assert(T = Data, 'UnsizedReader.GetToEOF'); Assert(S.EOF, 'UnsizedReader.EOF'); finally S.Free; end; end; procedure Test_Reader; var S : TRawByteStringReader; I : Integer; T : RawByteString; B : TFileReader; begin S := TRawByteStringReader.Create('0123456789AbCdE*.XXXYYYY*.'); try TestReader(TReaderProxy.Create(S, False, -1), True); TestReader(S, False); TestReader(TBufferedReader.Create(S, 128, False), True); for I := 1 to 16 do TestReader(TBufferedReader.Create(S, I, False), True); TestReader(TSplitBufferedReader.Create(S, 128, False), True); for I := 1 to 16 do TestReader(TSplitBufferedReader.Create(S, I, False), True); finally S.Free; end; S := TRawByteStringReader.Create('1'#13#10'23'#13#13'4'#10'5'#10#13'6'); try TestLineReader(TReaderProxy.Create(S, False, -1), True); for I := 1 to 32 do TestLineReader(TBufferedReader.Create(S, I, False), True); for I := 1 to 32 do TestLineReader(TSplitBufferedReader.Create(S, I, False), True); TestLineReader(S, False); finally S.Free; end; TestUnsizedReader(''); TestUnsizedReader('A'); TestUnsizedReader('ABC'); T := ''; for I := 1 to 1000 do T := T + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; TestUnsizedReader(T); try WriteStrToFileB('selftestfile', '0123456789AbCdE*.XXXYYYY*.', fwomCreate); B := TFileReader.Create('selftestfile'); TestReader(B, True); WriteStrToFileB('selftestfile', '1'#13#10'23'#13#13'4'#10'5'#10#13'6', fwomCreate); B := TFileReader.Create('selftestfile'); TestLineReader(B, True); finally DeleteFile('selftestfile'); end; end; procedure Test_Writer; var A : TRawByteStringWriter; B : TFileWriter; begin A := TRawByteStringWriter.Create; A.WriteStrB('123'); Assert(A.Size = 3, 'Writer.Size'); Assert(A.GetAsStringB = '123', 'Writer.GetAsString'); A.WriteStrB('ABC'); Assert(A.Size = 6, 'Writer.Size'); Assert(A.GetAsStringB = '123ABC', 'Writer.GetAsString'); A.Free; B := TFileWriter.Create('selftestfile', fwomCreate); try Assert(B.Size = 0, 'Writer.Size'); B.WriteStrB('123'); Assert(B.Size = 3, 'Writer.Size'); B.WriteByte(65); Assert(B.Size = 4, 'Writer.Size'); B.Size := 2; Assert(B.Size = 2, 'Writer.Size'); Assert(B.Position = 2, 'Writer.Position'); finally B.Free; DeleteFile('selftestfile'); end; end; procedure Test_FileStream; var A : TFileStream; S : RawByteString; begin A := TFileStream.Create('selftestfile', fsomCreate); try Assert(A.Size = 0, 'Stream.Size'); Assert(A.Position = 0, 'Stream.Position'); A.WriteStrB('123'); Assert(A.Size = 3, 'Stream.Size'); A.WriteStrB('ABC'); Assert(A.Size = 6, 'Stream.Size'); Assert(A.Position = 6, 'Stream.Position'); A.SetPosition(1); Assert(A.Position = 1, 'Stream.Position'); S := A.ReadStrB(3); Assert(S = '23A', 'Stream.ReadStr'); S := A.ReadStrB(3); Assert(S = 'BC', 'Stream.ReadStr'); A.SetPosition(1); Assert(A.Position = 1, 'Stream.Position'); A.WriteStrB('XY'); Assert(A.Position = 3, 'Stream.Position'); S := A.ReadStrB(3); Assert(S = 'ABC', 'Stream.ReadStr'); A.SetPosition(0); S := A.ReadStrB(3); Assert(S = '1XY', 'Stream.ReadStr'); finally A.Free; DeleteFile('selftestfile'); end; end; procedure Test; begin Test_Reader; Test_Writer; Test_FileStream; end; {$ENDIF} end.