home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Chip 1999 January
/
Chip_1999-01_cd.bin
/
zkuste
/
delphi
/
QDB
/
QDB.ZIP
/
QDB.pas
next >
Wrap
Pascal/Delphi Source File
|
1998-07-29
|
144KB
|
5,468 lines
{*****************************************************************************}
{ }
{ QDB v2.11 Visual Components for Delphi 1, 2, & 3 }
{ }
{ Copyright (c) 1995, 1996, 1997, 1998 Robert R. Marsh, S.J. }
{ & the British Province of the Society of Jesus }
{ }
{ This source code may *not* be redistributed }
{ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ }
{ }
{ If you like QDB and find yourself using it please consider }
{ making a donation to your favorite charity. I would also be }
{ pleased if you would acknowledge QDB in any projects that }
{ make use of it. }
{ }
{ QDB is supplied as is. The author disclaims all warranties, }
{ expressed or implied, including, without limitation, the }
{ warranties of merchantability and of fitness for any purpose. }
{ The author assumes no liability for damages, direct or }
{ consequential, which may result from the use of QDB. }
{ }
{ rrm@sprynet.com }
{ http://home.sprynet.com/sprynet/rrm }
{ }
{*****************************************************************************}
(*
Portions of the code are based on the work of others:
TQDBNavigator is based on TDBNavigator Copyright (c) 1995-1997
Borland International. All Rights Reserved.
The vertical orientation of TQDBNavigator is modeled after
DBVNav97 by Bourmad Mehdi(Mehdi.Bourmad@de.edfgdf.fr).
The Secure Hash Algorithm (SHA-1) used in the password routines is based
on the implementation by Koos Lodewijkx (J.P.Lodewijkx@inter.nl.net).
The grep-style pattern matching routine is based on the code of Gerald Nunn
and comes from GEXperts his excellent suite of Delphi add-ins at
http://www.amano-blick.com/~gnunn/GExperts.htm.
The buffered stream class was based on *someone's* source but the code
has no name attached, I can't remember where I downloaded it, and no
amount of web-searching has turned it up. If you recognize it, do
please let me know, so that I can properly acknowledge the author's
work.
Thanks to Bob Stammers for fixing a problem when TQDBNavigator is
created and destroyed at run-time.
*)
(*
Watch out for compiler warnings after try ... except blocks. The compiler
doesn't know that the various error functions raise exceptions of their
own which prevents the following code from ever being executed uninitialized.
*)
unit QDB;
interface
uses
{$IFDEF WIN32}Windows, {$ELSE}WinTypes, WinProcs, {$ENDIF}
SysUtils, Classes, Messages, Controls, Forms,
ExtCtrls, Buttons, Graphics;
type
string40 = string[40];
string05 = string[5];
const
FileVersion: string05 = '2.11';
AuthorInfo: string40 = 'Robert R. Marsh, SJ -- rrm@sprynet.com';
var
QDBTempFileLocation: string;
type
{ basic types that your application needs to know about }
TKey = string[255]; { QDB key }
TItemIndex = longint; { pointer into index }
TDataIndex = longint; { pointer into item of data }
type
{ other simple types used internally }
TQDBFileName = string;
TFileHandle = integer;
TFilePos = longint; { pointer into disk file }
type
{ event used to signal progress of lengthy process }
TPercentage = 0..100;
TProgressOrigin = (prStart, prFinish, prSave, prPack, prKeyList, prCompress);
TProgressEvent = procedure(Sender: TObject;
Percent: TPercentage;
Kind: TProgressOrigin) of object;
type
{ event used to get confirmation from user }
TConfirmEvent = procedure(Sender: TObject; var OK: boolean) of object;
type
{ event used to give warning to the user }
TWarningEvent = TNotifyEvent;
type
{ event used to elicit password }
TPassword = string[255];
TPasswordEvent = procedure(Sender: TObject; var Password: TPassword) of
object;
type
{ QDB-specific exceptions }
EQDBError = class(Exception);
EQDBListError = class(EQDBError);
EQDBFileError = class(EQDBError);
EQDBIndexError = class(EQDBError);
EQDBInvalidPW = class(EQDBError);
EQDBNoCompress = class(EQDBError);
EQDBBadKey = class(EQDBError);
EQDBOutOfBounds = class(EQDBIndexError);
EQDBNoData = class(EQDBIndexError);
EQDBReadOnly = class(EQDBIndexError);
EQDBNoFile = class(EQDBIndexError);
{ TQDBList }
const
MaxBranchSize = 65532 div SizeOf(pointer);
MaxListSize = MaxBranchSize * MaxBranchSize;
type
PLeafList = ^TLeafList;
TLeafList = array[0..MaxBranchSize - 1] of pointer;
PTopList = ^TTopList;
TTopList = array[0..MaxBranchSize - 1] of PLeafList;
type
TQDBList = class(TObject)
private
FCapacity: longint;
FCount: longint;
FList: PTopList;
LeafMask: longint; { used to find the index into a leaf }
LeafLength: longint; { the length of the Leaf array }
LeafSize: longint; { the memory-size of the Leaf }
TopSize: longint; { the memory-size of the Top array }
Power: longint; { the power of two giving the length }
TopUsed: longint; { the number of active leaves }
procedure AddLeaf;
procedure SetPower(p: longint);
protected
function Get(Index: longint): pointer;
procedure Grow;
procedure Put(Index: longint; Item: pointer);
procedure SetCapacity(NewCapacity: longint);
procedure SetCount(NewCount: longint);
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure Delete(Index: longint);
procedure Error(const ErrMsg: string; Data: longint);
procedure Exchange(Index1, Index2: longint);
procedure Insert(Index: longint; Item: pointer);
property Capacity: longint read FCapacity write SetCapacity;
property Count: longint read FCount write SetCount;
property Items[Index: longint]: pointer read Get write Put;
default;
end;
{ TQDBStringList }
TQDBStringList = class(TPersistent)
private
FCaseSensitive: boolean;
FList: TQDBList;
FSorted: boolean;
procedure SetCaseSensitive(Value: boolean);
procedure SetSorted(Value: boolean);
protected
function Get(Index: longint): string;
function GetCapacity: longint;
function GetCount: longint;
function GetObject(Index: longint): TObject;
procedure Put(Index: longint; const S: string);
procedure PutObject(Index: longint; AObject: TObject);
procedure SetCapacity(NewCapacity: longint);
public
constructor Create;
destructor Destroy; override;
function Add(const S: string): longint;
function AddObject(const S: string; AObject: TObject): longint;
procedure Clear;
procedure Delete(Index: longint);
procedure Error(const ErrMsg: string; Data: longint);
procedure Exchange(Index1, Index2: longint);
function Find(const S: string; var Index: longint): boolean;
procedure Reverse;
property CaseSensitive: boolean read FCaseSensitive write SetCaseSensitive;
property Count: longint read GetCount;
property Sorted: boolean read FSorted write SetSorted;
property Objects[Index: longint]: TObject read GetObject write
PutObject;
property Strings[Index: longint]: string read Get write Put;
default;
end;
{ TIndexList }
type
TIndexList = class(TQDBStringList)
destructor Destroy; override;
procedure EmptyAndClear;
end;
{ TCacheList }
TCacheList = class(TQDBStringList)
private
FAttempts: longint; { number of cache hits and failures }
FCurrentSize: longint; { ... in bytes }
FDisposals: longint; { number of scans for LRU item }
FMaximumSize: longint; { upper limit on size of cache }
FOldest: longint;
FSuccesses: longint; { number of cache hits }
protected
function GetFrequency: integer;
procedure SetFrequency(Value: integer);
procedure SetSize(Value: longint);
public
constructor Create;
destructor Destroy; override;
procedure Fetch(Stream: TStream; Place: longint);
procedure Flush;
procedure MakeSpace;
procedure Remove(Key: TKey);
procedure Statistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio:
longint);
procedure Store(Stream: TStream; Key: TKey);
end;
{ TBFStream }
type
TBFStream = class(TFileStream)
private
Buffer: pchar;
BufLen: longint;
BufferPos: pchar;
BytesRead: longint;
IsDirty: boolean;
public
constructor Create(const FileName: string; Mode: word; BufferSize:
longint);
destructor Destroy; override;
procedure AdjustBuffer;
function GetKey(var k: TKey): boolean;
function GetLongint(var L: longint): boolean;
procedure PutKey(const k: TKey);
procedure PutLongint(const L: longint);
procedure ResetBuffer;
function Seek(Offset: longint; Origin: word): longint; override;
end;
{ TTempBFStream }
type
TTempBFStream = class(TBFStream)
private
FOldFileName: string;
TmpFileName: string;
public
constructor Create(const OldFileName: string);
destructor Destroy; override;
end;
{ TQDB }
type
TMatchProc = function(Key: TKey; Pattern: TKey): boolean of object;
type
TQDBNavigator = class; {forward declaration }
TQDB = class(TComponent)
private
Admin: TIndexList; { in-memory index to administrative items }
Cache: TCacheList; { in-memory item cache }
FAfterCancel: TNotifyEvent;
FAfterDelete: TNotifyEvent;
FAfterEdit: TNotifyEvent;
FAfterInsert: TNotifyEvent;
FAfterPost: TNotifyEvent;
FAdminIndex: TItemIndex; { current position in admin index }
FAggressiveUpdate: boolean; { }
FAuthor: string40; { my name! }
FAutoEdit: boolean;
FBackWild: char; { wildcard stands for any chars at back of key }
FBeforeCancel: TNotifyEvent;
FBeforeDelete: TNotifyEvent;
FBeforeEdit: TNotifyEvent;
FBeforeInsert: TNotifyEvent;
FBeforePost: TNotifyEvent;
FBoF: boolean; { at beginning of file ? }
FCompression: boolean; { compress changes ? }
FCount: TItemIndex; { number of items in Index }
FEditing: boolean;
FEoF: boolean; { at end of file ? }
FFileAge: longint; { age of the QDB file when opened }
FFileName: string; { name of QDB file }
FFilter: TKey;
FForceOverwrite: boolean; { restricts access to certain keys }
FFrontWild: char; { wildcard stands for any chars at front of key }
Filtered: boolean; { is FFilter = '' ? }
FGrepMatch: boolean; { use grep-style match }
FInserting: boolean;
FItemIndex: TItemIndex; { current position in Index }
FKey: TKey; { key of current item }
Matches: TMatchProc; { the matching procedure to use }
FMatchWholeWord: boolean; { in patetrn matching and filtering }
FOnAdded: TNotifyEvent;
FOnChanged: TNotifyEvent;
FOnDeleted: TNotifyEvent;
FOnDemandPassWord: TPasswordEvent;
FOnFileAssigned: TNotifyEvent;
FOnFound: TNotifyEvent;
FOnKilled: TNotifyEvent;
FOnNavigate: TNotifyEvent;
FPassWord: TPassword; { up to 255 chars }
FProgressUpdate: TProgressEvent;
FQDBNavigator: TQDBNavigator;
FReadOnly: boolean; { governs file access }
FReady: boolean; { true iff a file is open and ready for access }
FSaveOnClose: boolean; { if true closing the file saves it , def true }
FExpandedFileNames: boolean; { if true FileName is made absolute , def true }
FUpdating: longint; { count Begin/End Update calls }
FVersion: string05; { QDB version e.g. '1.00' }
index: TIndexList; { in-memory index to file }
IsDirty: boolean; { has the file been changed ? }
MonitorKind: TProgressOrigin;
MonitorInterval: longint; { how often to update progress monitor }
QIXFile: TBFStream; { working index-file stream }
QIXFileName: string; { name of the working index-file }
FBeforeKill: TConfirmEvent;
FBeforeOverWrite: TConfirmEvent;
FWarnNoData: TWarningEvent;
FWarnOutOfBounds: TWarningEvent;
FWarnReadOnly: TWarningEvent;
procedure AdminAddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
procedure AdminChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
function AdminExactMatch(Key: TKey): boolean;
function AdminGetBoolean(Key: TKey): boolean;
function AdminGetInteger(Key: TKey): longint;
procedure AdminGetItem(ItemPtr: pointer);
function AdminGetString(Key: TKey): string;
function AdminItemSize: TDataIndex;
procedure AdminSetBoolean(Key: TKey; b: boolean);
procedure AdminSetInteger(Key: TKey; n: longint);
procedure AdminSetString(Key: TKey; const S: string);
procedure CloseQDB;
procedure CreateQDB;
procedure FileError(ErrCode: integer; SDefault: string);
function GetCacheFrequency: integer;
function GetCacheSize: longint;
function GetFileName: TQDBFileName;
function GetFilteredCount: TItemIndex;
function GetItemSize(Value: TItemIndex): TDataIndex;
function GetKey(Value: TItemIndex): TKey;
function GetKeyCase: boolean;
function GetStr(n: TItemIndex): string;
function GetStrByKey(Key: TKey): string;
function GetThisItemSize: TDataIndex;
function GetThisStr: string;
procedure IndexError(ErrMsg: string);
function ItemIsCompressed(Value: TItemIndex): boolean;
procedure LoadIndex;
procedure MonitorSetup(const Max: longint; const Kind: TProgressOrigin);
procedure MonitorUpdate(const n: longint);
procedure MonitorZero;
procedure OpenQDB;
procedure SaveIndex;
procedure SetCacheFrequency(Value: integer);
procedure SetCacheSize(Value: longint);
procedure SetDummyAuthor(Value: string40);
procedure SetDummyVersion(Value: string05);
procedure SetFilter(Value: TKey);
procedure SetItemIndex(Value: TItemIndex);
procedure SetKeyCase(Value: boolean);
procedure SetReadOnly(Value: boolean);
procedure SetReady(Value: boolean);
procedure SetStr(n: TItemIndex; const Value: string);
procedure SetStrByKey(Key: TKey; const Value: string);
procedure SetThisStr(const Value: string);
procedure Splice;
procedure Split;
protected
QDBFile: TFileStream; { working item-file stream }
QDBFileName: string; { name of the working data-file }
Restructuring: boolean;
procedure AboutToKill(var OK: boolean);
procedure AboutToOverWrite(var OK: boolean);
procedure Added;
procedure CannotChange;
procedure Changed;
procedure Deleted;
procedure DemandPassword;
procedure DoCancel; virtual;
procedure DoDelete; virtual;
procedure DoEdit; virtual;
procedure DoInsert; virtual;
procedure DoPost; virtual;
procedure FileAssigned;
function FileToRecover: string;
procedure ForceOverwrite(Value: boolean);
procedure Found;
function GrepMatches(Key: TKey; Pattern: TKey): boolean;
procedure Killed;
function Live: boolean;
procedure Navigate;
procedure NoData;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure OutOfBounds;
procedure SetFileName(Value: TQDBFileName); virtual;
procedure SetGrepMatch(Value: boolean);
procedure SetLinkToNavigator(Value: TQDBNavigator);
procedure SignalProgress(Percent: TPercentage; Kind: TProgressOrigin);
function SimpleMatches(Key: TKey; Pattern: TKey): boolean;
property AutoEdit: boolean read FAutoEdit write FAutoEdit;
property Editing: boolean read FEditing;
property Inserting: boolean read Finserting;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Add(Stream: TStream; Key: TKey);
procedure AddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
procedure AddStreamItem(Stream: TStream; Key: TKey);
procedure AdminClear(StartOfKey: TKey);
function AdminCount: TItemIndex;
procedure AdminDelete(Key: TKey);
function AdminKeyExists(Key: TKey): boolean;
function AdminKeys(Keys: TStrings; StartOfKey: TKey): longint;
procedure AssignKeyList(Keys: TStrings);
procedure BeginUpdate;
procedure Cancel;
procedure CacheFlush;
procedure CacheStatistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio: longint);
procedure Change(Stream: TStream);
procedure ChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
{// Alex procedure ChangeKey(key: TKey);}
procedure ChangeStreamItem(Stream: TStream);
function CloseMatch(Partialkey: TKey): boolean;
procedure Compress;
procedure Delete;
procedure DeleteItem;
procedure Edit;
procedure EndUpdate;
function ExactMatch(Key: TKey): boolean;
procedure Expand;
procedure FirstItem; virtual;
procedure Get(Stream: TStream);
procedure GetItem(ItemPtr: pointer);
procedure GetStreamItem(Stream: TStream);
procedure Insert;
function KeyExists(Key: TKey): boolean;
procedure Kill;
procedure LastItem; virtual;
procedure NextItem; virtual;
function OrphanToRecover: boolean;
procedure Pack;
function PartialMatch(StartOfKey: TKey): boolean;
procedure PartialMatchInit;
function PatternMatch(Pattern: TKey): boolean;
procedure PatternMatchInit;
procedure Post;
procedure PrepareToAdd(numberofitems: TItemIndex);
procedure PrevItem; virtual;
procedure Recover(NewFileName: string);
procedure Refresh; virtual;
procedure Save;
procedure SaveAs(NewName: string);
procedure SetMatchChars(Front: char; back: char);
procedure UpdateNavigator;
property AdminAsBoolean[Key: TKey]: boolean read AdminGetBoolean write AdminSetBoolean;
property AdminAsInteger[Key: TKey]: longint read AdminGetInteger write AdminSetInteger;
property AdminAsString[Key: TKey]: string read AdminGetString write AdminSetString;
property BoF: boolean read FBoF;
property Count: TItemIndex read FCount;
property EoF: boolean read FEoF;
property FilteredCount: TItemIndex read GetFilteredCount;
property Key: TKey read FKey;
property KeyCaseSensitive: boolean read GetKeyCase write SetKeyCase;
property CurrentItem: string read GetThisStr write SetThisStr;
property ItemIndex: TItemIndex read FItemIndex write SetItemIndex;
property Items[n: TItemIndex]: string read GetStr write SetStr;
property ItemsByKey[Key: TKey]: string read GetStrByKey write SetStrByKey;
default;
property ItemSize: TDataIndex read GetThisItemSize;
property MatchWholeWord: boolean read FMatchWholeWord write FMatchWholeWord;
property Password: TPassword read FPassWord write FPassWord stored false;
property Ready: boolean read FReady;
published
property AboutAuthor: string40 read FAuthor write SetDummyAuthor;
property AboutVersion: string05 read FVersion write SetDummyVersion;
property AfterCancel: TNotifyEvent read FAfterCancel write FAfterCancel;
property AfterDelete: TNotifyEvent read FAfterDelete write FAfterDelete;
property AfterEdit: TNotifyEvent read FAfterEdit write FAfterEdit;
property AfterInsert: TNotifyEvent read FAfterInsert write FAfterInsert;
property AfterPost: TNotifyEvent read FAfterPost write FAfterPost;
property AggressiveUpdate: boolean read FAggressiveUpdate write FAggressiveUpdate;
property BeforeCancel: TNotifyEvent read FBeforeCancel write FBeforeCancel;
property BeforeDelete: TNotifyEvent read FBeforeDelete write FBeforeDelete;
property BeforeEdit: TNotifyEvent read FBeforeEdit write FBeforeEdit;
property BeforeInsert: TNotifyEvent read FBeforeInsert write FBeforeInsert;
property BeforeKill: TConfirmEvent read FBeforeKill write FBeforeKill;
property BeforePost: TNotifyEvent read FBeforePost write FBeforePost;
property BeforeOverWrite: TConfirmEvent read FBeforeOverWrite write FBeforeOverWrite;
property CacheFrequency: integer read GetCacheFrequency write SetCacheFrequency;
property CacheSize: longint read GetCacheSize write SetCacheSize;
property Compression: boolean read FCompression write FCompression;
property FileName: TQDBFileName read GetFileName write SetFileName;
property Filter: TKey read FFilter write SetFilter;
property ProgressUpdate: TProgressEvent read FProgressUpdate write FProgressUpdate;
property ReadOnly: boolean read FReadOnly write SetReadOnly;
property SaveOnClose: boolean read FSaveOnClose write FSaveOnClose;
property ExpandedFileNames: boolean read FExpandedFileNames write FExpandedFileNames;
property UseGrepMatch: boolean read FGrepMatch write SetGrepMatch;
property WarnNoData: TWarningEvent read FWarnNoData write FWarnNoData;
property WarnOutOfBounds: TWarningEvent read FWarnOutOfBounds write FWarnOutOfBounds;
property WarnReadOnly: TWarningEvent read FWarnReadOnly write FWarnReadOnly;
property OnAdded: TNotifyEvent read FOnAdded write FOnAdded;
property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
property OnDeleted: TNotifyEvent read FOnDeleted write FOnDeleted;
property OnDemandPassword: TPasswordEvent read FOnDemandPassWord write FOnDemandPassWord;
property OnFileAssigned: TNotifyEvent read FOnFileAssigned write FOnFileAssigned;
property OnFound: TNotifyEvent read FOnFound write FOnFound;
property OnKilled: TNotifyEvent read FOnKilled write FOnKilled;
property OnNavigate: TNotifyEvent read FOnNavigate write FOnNavigate;
end;
{ TQDBNavigator }
{ This is a modified version of DBNavigator. Code from the VCL library }
{ is copyright Borland. }
{ Copyright (c) 1995-1997 Borland International. All Rights Reserved. }
{type}
TNavButton = class;
TNavGlyph = (ngEnabled, ngDisabled);
TNavOrientation = (noAuto, noHoriz, noVert);
TNavigateBtn = (nbFirst, nbPrev, nbNext, nbLast,
nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh);
TButtonSet = set of TNavigateBtn;
TNavButtonStyle = set of (nsAllowTimer, nsFocusRect);
TNavClickEvent = procedure(Sender: TObject; Button: TNavigateBtn) of object;
TBtnPressEvent = procedure(Sender: TObject; Q: TQDB) of object;
TQDBNavigator = class(TCustomPanel)
private
ButtonHeight: integer;
ButtonWidth: integer;
FBeforeAction: TNavClickEvent;
FFlat: boolean;
FHints: TStrings;
FocusedButton: TNavigateBtn;
FOnCancel: TBtnPressEvent;
FOnDelete: TBtnPressEvent;
FOnEdit: TBtnPressEvent;
FOnFirst: TBtnPressEvent;
FOnInsert: TBtnPressEvent;
FOnLast: TBtnPressEvent;
FOnNavClick: TNavClickEvent;
FOnNext: TBtnPressEvent;
FOnPost: TBtnPressEvent;
FOnPrev: TBtnPressEvent;
FOnRefresh: TBtnPressEvent;
FOrientation: TNavOrientation;
FQDB: TQDB;
FVisibleButtons: TButtonSet;
MinBtnSize: TPoint;
procedure _Click(Sender: TObject);
procedure AdjustSize(var W: integer; var H: integer);
procedure BtnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
function GetEnabled: boolean;
function GetGlyph(Btn: TNavigateBtn): Graphics.TBitmap;
procedure HintsChanged(Sender: TObject);
procedure InitButtons;
procedure InitHints;
procedure SetEnabled(Value: boolean);
procedure SetFlat(Value: boolean);
procedure SetGlyph(Btn: TNavigateBtn; Value: Graphics.TBitmap);
procedure SetHints(Value: TStrings);
procedure SetOrientation(Value: TNavOrientation);
procedure SetVisible(Value: TButtonSet);
procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
procedure WMSize(var Message: TWMSize); message WM_SIZE;
protected
Buttons: array[TNavigateBtn] of TNavButton;
procedure Cancel;
procedure Delete;
procedure Edit;
procedure First;
procedure Insert;
procedure KeyDown(var Key: word; Shift: TShiftState); override;
procedure Last;
procedure Loaded; override;
procedure Next;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Post;
procedure Prev;
procedure QDBStateChanged;
procedure Refresh;
procedure SetQDB(Value: TQDB); virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure BtnClick(Index: TNavigateBtn); virtual;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
property Glyphs[Btn: TNavigateBtn]: Graphics.TBitmap read GetGlyph write SetGlyph;
published
property Align;
property BeforeAction: TNavClickEvent read FBeforeAction write FBeforeAction;
property Ctl3D;
property DragCursor;
property DragMode;
property Enabled read GetEnabled write SetEnabled;
property Flat: boolean read FFlat write SetFlat default false;
property Hints: TStrings read FHints write SetHints;
property Orientation: TNavOrientation read FOrientation write SetOrientation default noAuto;
property ParentCtl3D;
property ParentShowHint;
property QDB: TQDB read FQDB write SetQDB;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;
property VisibleButtons: TButtonSet read FVisibleButtons write SetVisible
default [nbFirst, nbPrev, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh];
property OnCancel: TBtnPressEvent read FOnCancel write FOnCancel;
property OnClick: TNavClickEvent read FOnNavClick write FOnNavClick;
property OnDblClick;
property OnDelete: TBtnPressEvent read FOnDelete write FOnDelete;
property OnDragDrop;
property OnDragOver;
property OnEdit: TBtnPressEvent read FOnEdit write FOnEdit;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnFirst: TBtnPressEvent read FOnFirst write FOnFirst;
property OnInsert: TBtnPressEvent read FOnInsert write FOnInsert;
property OnLast: TBtnPressEvent read FOnLast write FOnLast;
property OnNext: TBtnPressEvent read FOnNext write FOnNext;
property OnPost: TBtnPressEvent read FOnPost write FOnPost;
property OnPrev: TBtnPressEvent read FOnPrev write FOnPrev;
property OnRefresh: TBtnPressEvent read FOnRefresh write FOnRefresh;
property OnResize;
end;
{type}
TNavButton = class(TSpeedButton)
private
FIndex: TNavigateBtn;
FNavStyle: TNavButtonStyle;
FRepeatTimer: TTimer;
procedure TimerExpired(Sender: TObject);
protected
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
procedure Paint; override;
public
destructor Destroy; override;
property Index: TNavigateBtn read FIndex write FIndex;
property NavStyle: TNavButtonStyle read FNavStyle write FNavStyle;
end;
function TempFileName(Prefix: string): string;
procedure RenameOrMoveFile(const SrcFileName, DstFileName: string);
implementation
uses
qdbu;
{$IFDEF WIN32}
{$R QDB.R32}
{$ELSE}
{$R QDB.R16}
{$ENDIF}
{ the codes for the messages in QDB.R16 or QDB.R32}
const
SMissing = 'Could not find the file %s';
SCorrupt = 'The file %s is not a valid QDB file';
SDoorOpen = 'The drive you are trying to access is not ready';
SReadOnly = '%s is marked as read-only';
STooMany = 'No more file handles are available';
SShareError = 'The file %s seems to be in use by another program';
SDiskFull = 'The drive is full';
SUnknownError = 'Unidentified problem - %s';
SIndexAdd = 'Not enough memory to extend the index to %s';
SCannotCopy = 'Unable to copy %s';
SDataAdd = 'Unable to extend the file %s';
SDuplicateKey = 'Duplicate keys are not allowed';
SSortedListError = 'Cannot insert into a sorted list';
SOutOfBounds = 'The list index is out of bounds';
STempFile = 'Could not create a necessary temporary file';
SNoFile = 'Illegal operation - no file assigned';
SNoMemory = 'Insufficient memory to compress or expand';
SBadKey = 'The key ''%s'' does not exist';
SBadPassword = 'The password you have provided is invalid';
SNoData = 'File %s is empty';
{ Flags in the TIndex.Ext field }
type
TFlags = 0..31;
TFlagSet = set of TFlags;
const
IsAdminItem: TFlags = 0; {Admin items}
IsCompressed: TFlags = 1; {Compressed items}
{ ******* Utility routines ******* }
{ Allocates memory for a buffer -- first tries to get the }
{ RequestedSize but if not available keeps halving the size }
{ until a block can be allocated. The actual amount allocated }
{ is returned as Result. }
function GetBuffer(var Buffer: pointer; RequestedSize: longint): longint;
var
AllocatedOK: boolean;
AllocatedSize: longint;
begin
AllocatedSize := 0;
{ make sure request is in range }
if RequestedSize < 1024 then
RequestedSize := 1024;
{$IFNDEF WIN32}
if RequestedSize > (1024 * 63) then
RequestedSize := (1024 * 63);
{$ELSE}
if RequestedSize > (1024 * 512) then
RequestedSize := (1024 * 512);
{$ENDIF}
AllocatedOK := false;
while not AllocatedOK do
begin
try
GetMem(Buffer, RequestedSize);
AllocatedSize := RequestedSize;
AllocatedOK := true;
except
{ keep halving the request until successful }
on EOutOfMemory do
RequestedSize := RequestedSize div 2;
end;
end;
Result := AllocatedSize;
end;
{*******************************************************************
*
* Stream Compression
*
* based on the LZRW1/KH compression algorithm posted by Kurt Haenen
* to SWAG as 'lzrw1' and modified for Delphi by D. Heijl
* (Danny.Heijl@cevi.be)
*
* Haenen states, 'The algoritm is not as good as LZH, but can compete
* with Lempel-Ziff. It's the fastest one I've encountered up to now.'
*
* The procedures below are the ones actually used in QDB
*
* function squashstream(src, dst: tstream): longint;
* compresses the whole of the src stream to the current place in
* dst and returns the number of bytes written to dst
*
* procedure unsquashstream(src, dst: tstream; bytes: longint);
* expands the requested number of bytes from the current place in
* the src stream to dst (which should be empty)
*
* The procedures GetMatch, Squash, and Unsquash do the actual work.
*
******************************************************************************}
{ we want to turn off range checking temporarily }
{$IFOPT R+}
{$DEFINE RON}
{$R-}
{$ENDIF}
{$IFDEF WIN32}
type
int16 = smallint;
{$ELSE}
type
int16 = integer;
{$ENDIF}
const
BufferMaxSize = 32768;
BufferMax = BufferMaxSize - 1;
flag_copied = $80;
flag_compress = $40;
type
BufferIndex = 0..BufferMax + 15;
BufferSize = 0..BufferMaxSize;
BufferArray = array[BufferIndex] of Byte;
Bufferptr = ^BufferArray;
HashTable = array[0..4095] of int16;
HashTabPtr = ^HashTable;
{ turn off overflow testing temporarily }
{$IFOPT Q+}
{$DEFINE QON}
{$Q-}
{$ENDIF}
{check if this string has already been seen in the current 4 KB window }
function GetMatch(Source: Bufferptr; X: BufferIndex; SourceSize: BufferSize;
Hash: HashTabPtr; var size: word; var Pos: BufferIndex): boolean;
var
HashValue: word;
TmpHash: int16;
begin
HashValue := (40543 * ((((Source^[X] shl 4) xor Source^[X + 1]) shl
4) xor
Source^[X + 2]) shr 4) and $0FFF;
Result := false;
TmpHash := Hash^[HashValue];
if (TmpHash <> -1) and (X - TmpHash < 4096) then
begin
Pos := TmpHash;
size := 0;
while ((size < 18) and (Source^[X + size] = Source^[Pos + size])
and (X + size < SourceSize)) do
begin
inc(size);
end;
Result := (size >= 3)
end;
Hash^[HashValue] := X;
end;
{$IFDEF QON}
{$UNDEF QON}
{$Q+}
{$ENDIF}
{ compress a buffer of max. 32 KB }
function Squash(Source, Dest: Bufferptr; SourceSize: BufferSize; Hash:
HashTabPtr): BufferSize;
var
bit, command, size: word;
Key: word;
X, Y, Z, Pos: BufferIndex;
begin
FillChar(Hash^, SizeOf(HashTable), $FF);
Dest^[0] := flag_compress;
X := 0;
Y := 3;
Z := 1;
bit := 0;
command := 0;
while (X < SourceSize) and (Y <= SourceSize) do
begin
if (bit > 15) then
begin
Dest^[Z] := Hi(command);
Dest^[Z + 1] := Lo(command);
Z := Y;
bit := 0;
inc(Y, 2)
end;
size := 1;
while ((Source^[X] = Source^[X + size]) and (size < $FFF)
and (X + size < SourceSize)) do
begin
inc(size);
end;
if (size >= 16) then
begin
Dest^[Y] := 0;
Dest^[Y + 1] := Hi(size - 16);
Dest^[Y + 2] := Lo(size - 16);
Dest^[Y + 3] := Source^[X];
inc(Y, 4);
inc(X, size);
command := (command shl 1) + 1;
end
else
begin { not size >= 16 }
if (GetMatch(Source, X, SourceSize, Hash, size, Pos)) then
begin
Key := ((X - Pos) shl 4) + (size - 3);
Dest^[Y] := Hi(Key);
Dest^[Y + 1] := Lo(Key);
inc(Y, 2);
inc(X, size);
command := (command shl 1) + 1
end
else
begin
Dest^[Y] := Source^[X];
inc(Y);
inc(X);
command := command shl 1
end;
end; { size <= 16 }
inc(bit);
end; { while x < sourcesize ... }
command := command shl (16 - bit);
Dest^[Z] := Hi(command);
Dest^[Z + 1] := Lo(command);
if (Y > SourceSize) then
begin
Move(Source^[0], Dest^[1], SourceSize);
Dest^[0] := flag_copied;
Y := succ(SourceSize)
end;
Result := Y
end;
{ decompress a buffer of max 32 KB }
function Unsquash(Source, Dest: Bufferptr; SourceSize: BufferSize):
BufferSize;
var
X, Y, Pos: BufferIndex;
command, size, k: word;
bit: Byte;
Savey: BufferIndex; { unsafe for-loop variable Y -- dh --}
begin
if (SourceSize <= 1) then
begin { correction of a bug found by Dominique Willems <Domus@compuserve.com>}
Result := 0;
exit;
end;
if (Source^[0] = flag_copied) then
begin
for Y := 1 to pred(SourceSize) do
begin
Dest^[pred(Y)] := Source^[Y];
Savey := Y;
end;
Y := Savey;
end
else
begin
Y := 0;
X := 3;
command := (Source^[1] shl 8) + Source^[2];
bit := 16;
while (X < SourceSize) do
begin
if (bit = 0) then
begin
command := (Source^[X] shl 8) + Source^[X + 1];
bit := 16;
inc(X, 2)
end;
if ((command and $8000) = 0) then
begin
Dest^[Y] := Source^[X];
inc(X);
inc(Y)
end
else
begin { command and $8000 }
Pos := ((Source^[X] shl 4) + (Source^[X + 1] shr 4));
if (Pos = 0) then
begin
size := (Source^[X + 1] shl 8) + Source^[X + 2] + 15;
for k := 0 to size do
begin
Dest^[Y + k] := Source^[X + 3];
end;
inc(X, 4);
inc(Y, size + 1)
end
else
begin { pos = 0 }
size := (Source^[X + 1] and $0F) + 2;
for k := 0 to size do
Dest^[Y + k] := Dest^[Y - Pos + k];
inc(X, 2);
inc(Y, size + 1)
end; { pos = 0 }
end; { command and $8000 }
command := command shl 1;
dec(bit);
end; { while x < sourcesize }
end;
Result := Y;
end; { Unsquash }
function SquashStream(Src, Dst: TStream): longint;
var
inp,
outp: Bufferptr;
ins,
outs: word;
Hash: HashTabPtr;
begin
Result := 0;
Src.Seek(0, 0);
try
GetMem(inp, BufferMaxSize);
except
raise EQDBNoCompress.Create(SNoMemory);
end;
try
try
GetMem(outp, BufferMaxSize);
except
raise EQDBNoCompress.Create(SNoMemory);
end;
try
try
GetMem(Hash, SizeOf(HashTable));
except
raise EQDBNoCompress.Create(SNoMemory);
end;
try
while Src.Position < Src.size do
begin
ins := Src.Read(inp^, BufferMaxSize);
outs := Squash(inp, outp, ins, Hash);
inc(Result, Dst.Write(outs, SizeOf(outs)));
inc(Result, Dst.Write(outp^, outs));
end;
finally
FreeMem(Hash, SizeOf(HashTable));
end;
finally
FreeMem(outp, BufferMaxSize);
end;
finally
FreeMem(inp, BufferMaxSize);
end;
end;
procedure UnSquashStream(Src, Dst: TStream; Bytes: longint);
var
inp,
outp: Bufferptr;
ins,
outs: word;
Tot: longint;
begin
Dst.Seek(0, 0);
Tot := 0;
try
GetMem(inp, BufferMaxSize);
except
raise EQDBNoCompress.Create(SNoMemory);
end;
try
try
GetMem(outp, BufferMaxSize);
except
raise EQDBNoCompress.Create(SNoMemory);
end;
try
while Tot < Bytes do
begin
inc(Tot, Src.Read(ins, SizeOf(ins)));
ins := Src.Read(inp^, ins);
inc(Tot, ins);
outs := Unsquash(inp, outp, ins);
Dst.Write(outp^, outs);
end;
finally
FreeMem(outp, BufferMaxSize);
end;
finally
FreeMem(inp, BufferMaxSize);
end;
Dst.Seek(0, 0);
end;
{ restore the previous range checking state }
{$IFDEF RON}
{$UNDEF RON}
{$R+}
{$ENDIF}
{ TQDBList }
const
PowerMin = 1; { governs the minimum capacity of the list }
{ i.e.. 2^(2*PowerMin) = 4 }
constructor TQDBList.Create;
begin
inherited Create;
FCount := 0;
FList := nil;
TopUsed := 0;
SetPower(PowerMin);
FCapacity := 0;
SetCapacity(0);
end;
destructor TQDBList.Destroy;
begin
while TopUsed > 0 do
begin
FreeMem(FList^[TopUsed - 1], LeafSize);
dec(TopUsed);
end;
if FList <> nil then
begin
FreeMem(FList, TopSize);
FList := nil;
end;
inherited Destroy;
end;
procedure TQDBList.AddLeaf;
var
NewLeaf: PLeafList;
begin
try
GetMem(NewLeaf, LeafSize);
FList^[TopUsed] := NewLeaf;
inc(TopUsed);
except
on EOutOfMemory do
Error(SNoMemory, 0)
else
raise;
end;
end;
procedure TQDBList.Clear;
begin
while TopUsed > 0 do
begin
FreeMem(FList^[TopUsed - 1], LeafSize);
dec(TopUsed);
end;
FCount := 0;
SetCapacity(0);
end;
procedure TQDBList.Delete(Index: longint);
{ messy ... we have to move items from one leaf to the next }
var
i: longint;
amount: longint;
begin
{ how many elements do we have to shift in the first leaf }
amount := LeafLength - 1 - (Index and LeafMask);
{ move the first chunk left }
if amount > 0 then
System.Move(FList^[(Index shr Power)]^[(Index + 1) and LeafMask],
FList^[(Index shr Power)]^[Index and LeafMask], amount * SizeOf(
pointer));
{ then for each leaf on up }
for i := (Index shr Power) to TopUsed - 2 do
begin
{ bring one item down from the end to the front }
FList^[i]^[LeafLength - 1] := FList^[i + 1]^[0];
{ shift the rest left one place }
System.Move(FList^[i + 1]^[1], FList^[i + 1]^[0], LeafSize -
SizeOf(pointer));
end;
dec(FCount);
{ if we've emptied a leaf we can free the space }
if (FCount = 0) or (((FCount - 1) shr Power) < (TopUsed - 1)) then
begin
FreeMem(FList^[TopUsed - 1], LeafSize);
dec(TopUsed);
end;
end;
procedure TQDBList.Error(const ErrMsg: string; Data: longint);
var
StackTop: record
end;
Stack: record
BPorEBP: integer; { 16 bit: BP, 32 bit: EBP }
ReturnAddress: pointer;
end absolute StackTop;
begin
raise EQDBListError.CreateFmt(ErrMsg, [Data])at Stack.ReturnAddress;
end;
procedure TQDBList.Exchange(Index1, Index2: longint);
var
Item: pointer;
begin
Item := FList^[(Index1 shr Power)]^[(Index1 and LeafMask)];
FList^[(Index1 shr Power)]^[(Index1 and LeafMask)] := FList^[(Index2
shr Power)]^[(Index2 and LeafMask)];
FList^[(Index2 shr Power)]^[(Index2 and LeafMask)] := Item;
end;
function TQDBList.Get(Index: longint): pointer;
begin
Result := FList^[(Index shr Power)]^[(Index and LeafMask)];
end;
procedure TQDBList.Grow;
begin
{ SetCapacity will choose a suitable new value -- the list }
{ capacity grows by powers of two }
SetCapacity(FCapacity + 1);
end;
procedure TQDBList.Insert(Index: longint; Item: pointer);
{ messy ... we have to move elements from leaf to leaf }
var
i: longint;
amount: longint;
begin
{ make room if necessary }
if FCount = FCapacity then
Grow;
{ add another leaf if needed }
if (FCount and LeafMask) = 0 then
AddLeaf;
{ for each leaf from the top down to the place of insertion }
for i := TopUsed - 1 downto ((Index shr Power) + 1) do
begin
{ shift everything one place right }
System.Move(FList^[i]^[0], FList^[i]^[1], LeafSize - SizeOf(pointer
));
{ bring one item up from the end of the previous leaf }
FList^[i]^[0] := FList^[i - 1]^[LeafLength - 1];
end;
{ how many elements to shift along }
amount := LeafLength - 1 - (Index and LeafMask);
{ shift right to make room for new item }
System.Move(FList^[Index shr Power]^[(Index and LeafMask)],
FList^[Index shr Power]^[(Index and LeafMask) + 1], amount * SizeOf(
pointer));
{ insert the item itself }
FList^[(Index shr Power)]^[(Index and LeafMask)] := Item;
inc(FCount);
end;
procedure TQDBList.Put(Index: longint; Item: pointer);
begin
FList^[(Index shr Power)]^[(Index and LeafMask)] := Item;
end;
procedure TQDBList.SetCapacity(NewCapacity: longint);
{ a lot of business goes on in here ... }
var
NewPower: longint;
NewSize: longint;
NewList: PTopList;
NewLeaf: PLeafList;
NewTopUsed: longint;
Ratio: longint;
i, j: longint;
function RecommendedPower(NewCapacity: longint): longint;
begin
{ compute the root of s to the nearest greater power of 2 }
Result := PowerMin;
while NewCapacity >= (1 shl (Result shl 1)) do
inc(Result);
end;
begin
{ calculate the parameters of the 'new' qlist }
NewPower := RecommendedPower(NewCapacity);
NewSize := (1 shl NewPower) * SizeOf(pointer);
NewCapacity := (1 shl (NewPower shl 1));
if (NewCapacity < FCount) or (NewCapacity > MaxListSize) then
Error(SOutOfBounds, NewCapacity);
if NewCapacity <> FCapacity then
begin
{ begin to build a new qlist }
try
GetMem(NewList, NewSize);
except
on EOutOfMemory do
Error(SNoMemory, 0)
else
raise
end;
if FCount > 0 then
begin
{ only relevant if the list is not empty }
NewTopUsed := ((FCount - 1) shr NewPower) + 1;
{ how many old leaves fit into a new one }
Ratio := (NewSize div LeafSize);
{ for each old leaf }
for i := 0 to TopUsed - 1 do
begin
{ if a new leaf is needed }
if i mod Ratio = 0 then
begin
try
{ add a new leaf }
GetMem(NewLeaf, NewSize);
except
on EOutOfMemory do
{ get rid of the partly built qlist }
begin
j := i;
dec(j, Ratio);
while j >= 0 do
FreeMem(NewList^[j], NewSize);
FreeMem(NewList, NewSize);
Error(SNoMemory, 0);
end
else
raise;
end;
{ put the leaf into the tree }
NewList^[i div Ratio] := NewLeaf;
end;
{ move the old leaf to its place in the new }
System.Move(FList^[i]^[0], NewList^[i div Ratio]^[(LeafLength *
(i mod Ratio))], LeafSize);
{ get rid of the old leaf }
FreeMem(FList^[i], LeafSize);
end;
TopUsed := NewTopUsed;
end;
{ get rid of the now empty old qlist }
if FList <> nil then
FreeMem(FList, TopSize);
{ assign the new qlist instead }
FList := NewList;
{ adjust the qlist parameters }
SetPower(NewPower);
FCapacity := NewCapacity;
end;
end;
procedure TQDBList.SetCount(NewCount: longint);
var
i: longint;
begin
if (NewCount < 0) or (NewCount > MaxListSize) then
Error(SOutOfBounds, NewCount);
if NewCount > FCapacity then
SetCapacity(NewCount);
{ if we are shrinking the list we blank out the unwanted }
{ items -- if they point to anything there'll be a leak }
if NewCount > FCount then
for i := FCount to NewCount do
FList^[(i shr Power)]^[(i and LeafMask)] := nil;
FCount := NewCount;
end;
procedure TQDBList.SetPower(p: longint);
begin
Power := p;
LeafLength := (1 shl Power);
LeafSize := LeafLength * SizeOf(pointer);
LeafMask := LeafLength - 1;
TopSize := LeafSize;
end;
{ TQDBStringList }
type
PStrItem = ^TStrItem;
TStrItem = record
FString: pchar;
FObject: TObject;
end;
constructor TQDBStringList.Create;
begin
inherited Create;
FList := TQDBList.Create;
end;
destructor TQDBStringList.Destroy;
begin
Clear;
FList.Free;
inherited Destroy;
end;
procedure DisposeStrItem(p: PStrItem);
begin
FreeMem(p^.FString, StrLen(p^.FString) + 1);
FreeMem(p, SizeOf(TStrItem));
end;
function NewStrItem(const AString: string; AObject: TObject): PStrItem;
var
p: PStrItem;
c: pchar;
begin
GetMem(p, SizeOf(TStrItem));
GetMem(c, Length(AString) + 1);
StrPCopy(c, AString);
p^.FObject := AObject;
p^.FString := c;
Result := p;
end;
function TQDBStringList.Add(const S: string): longint;
begin
if not Sorted then
Result := FList.Count
else
if Find(S, Result) then
Error(SDuplicateKey, 0);
FList.Insert(Result, NewStrItem(S, nil));
end;
function TQDBStringList.AddObject(const S: string; AObject: TObject):
longint;
begin
if not Sorted then
Result := FList.Count
else
if Find(S, Result) then
Error(SDuplicateKey, 0);
FList.Insert(Result, NewStrItem(S, AObject));
end;
procedure TQDBStringList.Clear;
var
i: longint;
begin
for i := 1 to FList.Count do
begin
DisposeStrItem(FList[i - 1]);
FList[i - 1] := nil;
end;
FList.Clear;
end;
procedure TQDBStringList.Delete(Index: longint);
begin
DisposeStrItem(FList[Index]);
FList.Delete(Index);
end;
procedure TQDBStringList.Error(const ErrMsg: string; Data: longint);
var
StackTop: record
end;
Stack: record
BPorEBP: integer; { 16 bit: BP, 32 bit: EBP }
ReturnAddress: pointer;
end absolute StackTop;
begin
raise EQDBListError.CreateFmt(ErrMsg, [Data])at Stack.ReturnAddress;
end;
procedure TQDBStringList.Exchange(Index1, Index2: longint);
begin
FList.Exchange(Index1, Index2);
end;
function TQDBStringList.Find(const S: string; var Index: longint): boolean;
var
L, H, i, c: longint;
begin
Result := false;
L := 0;
H := FList.Count - 1;
while L <= H do
begin
i := (L + H) shr 1;
if CaseSensitive then
c := AnsiCompareStr(StrPas(PStrItem(FList[i])^.FString), S)
else
c := AnsiCompareText(StrPas(PStrItem(FList[i])^.FString), S);
if c < 0 then
L := i + 1
else
begin
H := i - 1;
if c = 0 then
begin
Result := true;
L := i;
end;
end;
end;
Index := L;
end;
function TQDBStringList.Get(Index: longint): string;
begin
Result := StrPas(PStrItem(FList[Index])^.FString);
end;
function TQDBStringList.GetCapacity: longint;
begin
Result := FList.Capacity;
end;
function TQDBStringList.GetCount: longint;
begin
Result := FList.Count;
end;
function TQDBStringList.GetObject(Index: longint): TObject;
begin
Result := PStrItem(FList[Index])^.FObject;
end;
procedure TQDBStringList.Put(Index: longint; const S: string);
var
p: PStrItem;
begin
{ get the old str item }
p := FList[Index];
{ create and assign the new str item }
FList[Index] := NewStrItem(S, p^.FObject);
{ get rid of the old one }
DisposeStrItem(p);
end;
procedure TQDBStringList.PutObject(Index: longint; AObject: TObject);
var
p: PStrItem;
begin
p := FList[Index];
FList[Index] := NewStrItem(Strings[Index], AObject);
DisposeStrItem(p);
end;
procedure TQDBStringList.Reverse;
{ the QDB index gets read in in reverse order ... this just reverses that }
{ since it leaves the items in sorted order it is safe to set sorted to true }
var
n, m: longint;
begin
if Sorted then
exit;
n := 1;
m := FList.Count;
while n < m do
begin
FList.Exchange(n - 1, m - 1);
inc(n);
dec(m);
end;
FSorted := true;
end;
procedure TQDBStringList.SetCapacity(NewCapacity: longint);
begin
FList.Capacity := NewCapacity;
end;
procedure TQDBStringList.SetCaseSensitive(Value: boolean);
var
n: longint;
begin
{ if the list is empty it's easy ...}
if Count = 0 then
FCaseSensitive := Value
else
begin
if FCaseSensitive <> Value then
begin
FCaseSensitive := Value;
{ if we are going from sensitive to insensitive we have extra work }
if not FCaseSensitive then
begin
{check for duplicates and delete them }
n := Count - 1;
while n > 0 do
begin
if AnsiCompareText(Get(n - 1), Get(n)) = 0 then
Delete(n);
dec(n);
end;
end;
end;
end;
end;
procedure TQDBStringList.SetSorted(Value: boolean);
begin
if FSorted <> Value then
FSorted := Value;
end;
{ ******* Index and cache records ******* }
type
TIndex = class { each index object points to variable length data... }
Pos: TFilePos; { it's location in the data file }
Len: TDataIndex; { and it's length }
Ext: TFlagSet; { additional flags }
end;
type
TCache = class
FAge: longint;
Stream: TMemoryStream; { the stream holds the data }
public
constructor Create(Data: TStream; Age: longint);
destructor Destroy; override;
end;
{ TCache }
constructor TCache.Create(Data: TStream; Age: longint);
begin
inherited Create;
FAge := Age;
Stream := TMemoryStream.Create;
Stream.LoadFromStream(Data);
Data.Seek(0, 0);
Stream.Seek(0, 0);
end;
destructor TCache.Destroy;
begin
Stream.Free;
inherited Destroy;
end;
{ TIndexList }
destructor TIndexList.Destroy;
begin
EmptyAndClear;
inherited Destroy;
end;
procedure TIndexList.EmptyAndClear;
begin
while Count > 0 do
begin
TIndex(Objects[Count - 1]).Free;
Delete(Count - 1);
end;
end;
{ TCacheList }
constructor TCacheList.Create;
begin
inherited Create;
CaseSensitive := true;
Sorted := true;
FCurrentSize := 0;
FMaximumSize := 64 * 1024; { default cache size 64K }
FSuccesses := 0;
FAttempts := 0;
FDisposals := 0;
FOldest := 0;
end;
destructor TCacheList.Destroy;
begin
Flush;
inherited Destroy;
end;
procedure TCacheList.Fetch(Stream: TStream; Place: longint);
var
CacheRec: TCache;
begin
CacheRec := TCache(Objects[Place]);
CacheRec.Stream.SaveToStream(Stream);
CacheRec.Stream.Seek(0, 0);
{ promote item in age list }
inc(FOldest);
CacheRec.FAge := FOldest;
{ update statistics }
inc(FSuccesses);
inc(FAttempts);
end;
procedure TCacheList.Flush;
{ clear the item cache }
begin
while Count > 0 do
begin
TCache(Objects[Count - 1]).Free;
Delete(Count - 1);
end;
FCurrentSize := 0;
FSuccesses := 0;
FAttempts := 0;
FDisposals := 0;
FOldest := 0;
end;
function TCacheList.GetFrequency: integer;
{ superceded by Statistics }
begin
if FAttempts <> 0 then
Result := Round((100.0 * FSuccesses) / FAttempts)
else
Result := 0;
end;
procedure TCacheList.MakeSpace;
{ remove the oldest item from the cache ... }
var
oldest: longint;
oldestindex: longint;
n: longint;
oldestcache: TCache;
begin
if Count < 1 then
exit;
oldest := maxint;
oldestindex := 0;
for n := 0 to Count - 1 do
begin
oldestcache := TCache(Objects[n]);
if oldestcache.FAge < oldest then
begin
oldest := oldestcache.FAge;
oldestindex := n;
end;
end;
oldestcache := TCache(Objects[oldestindex]);
dec(FCurrentSize, oldestcache.Stream.size);
oldestcache.Free;
Delete(oldestindex);
inc(FDisposals);
end;
procedure TCacheList.Remove(Key: TKey);
{ remove the specified item from the cache }
var
CacheN: longint;
begin
if Find(Key, CacheN) then
begin
dec(FCurrentSize, TCache(Objects[CacheN]).Stream.size);
TCache(Objects[CacheN]).Free;
Delete(CacheN);
end
end;
procedure TCacheList.SetFrequency(Value: integer);
begin
FSuccesses := 0;
FAttempts := 0;
FDisposals := 0;
end;
procedure TCacheList.SetSize(Value: longint);
{ note: changing the cache size empties it }
begin
Flush;
FMaximumSize := Value;
end;
procedure TCacheList.Statistics(var MaxSize, CurSize, CurLen, HitRatio,
DropRatio: longint);
begin
if FAttempts = 0 then
begin
MaxSize := FMaximumSize;
CurSize := 0;
CurLen := 0;
HitRatio := 0;
DropRatio := 0;
end
else
begin
MaxSize := FMaximumSize;
CurSize := FCurrentSize;
CurLen := Count;
HitRatio := (FSuccesses * 100) div FAttempts;
DropRatio := (FDisposals * 100) div FAttempts;
end;
end;
procedure TCacheList.Store(Stream: TStream; Key: TKey);
{ add an item to the cache, making space if needed via MakeSpace }
var
CacheRec: TCache;
begin
{ we don't try to cache items bigger than the limit }
if Stream.size >= FMaximumSize then
exit;
{ make room for the new entry by removing as many old ones as needed }
while FCurrentSize + Stream.size > FMaximumSize do
MakeSpace;
inc(FOldest);
try
CacheRec := TCache.Create(Stream, FOldest);
except
exit;
end;
try
AddObject(Key, CacheRec);
except
CacheRec.Free;
exit;
end;
inc(FCurrentSize, CacheRec.Stream.size);
inc(FAttempts);
end;
{ TBFStream }
constructor TBFStream.Create(const FileName: string; Mode: word; BufferSize:
longint);
{ if BufferSize is -1 we try to get a buffer big enough for the whole file }
begin
inherited Create(FileName, Mode);
if BufferSize = -1 then
begin
BufferSize := inherited Seek(0, 2);
inherited Seek(0, 0);
end;
{need to make sure we have zero byte at the end of the buffer}
BufLen := GetBuffer(pointer(Buffer), BufferSize) - 1;
IsDirty := false;
ResetBuffer;
end;
destructor TBFStream.Destroy;
begin
ResetBuffer;
FreeMem(Buffer, BufLen + 1);
inherited Destroy;
end;
procedure TBFStream.AdjustBuffer;
begin
if IsDirty then
begin
Write(Buffer^, BufferPos - Buffer);
IsDirty := false;
end;
FillChar(Buffer^, BufLen + 1, #0);
inherited Seek(BufferPos - Buffer - BytesRead, 1);
end;
function TBFStream.GetKey(var k: TKey): boolean;
begin
Result := false;
if (BufferPos - Buffer + StrLen(BufferPos) + 1 > BytesRead) then
begin
if (BytesRead < BufLen) then
exit
else
begin
AdjustBuffer;
BufferPos := Buffer;
BytesRead := Read(Buffer^, BufLen);
Result := GetKey(k);
end;
end
else
begin
k := StrPas(BufferPos);
inc(BufferPos, Length(k) + 1);
Result := true;
end;
end;
function TBFStream.GetLongint(var L: longint): boolean;
begin
Result := false;
if (BufferPos - Buffer + SizeOf(L) > BytesRead) then
begin
if (BytesRead < BufLen) then
exit
else
begin
AdjustBuffer;
BufferPos := Buffer;
BytesRead := Read(Buffer^, BufLen);
Result := GetLongint(L);
end;
end
else
begin
Move(BufferPos^, L, SizeOf(L));
inc(BufferPos, SizeOf(L));
Result := true;
end;
end;
procedure TBFStream.PutKey(const k: TKey);
begin
if (BufferPos - Buffer + Length(k) + 1 > BufLen) then
begin
ResetBuffer;
BufferPos := Buffer;
end;
StrPCopy(BufferPos, k);
inc(BufferPos, Length(k) + 1);
IsDirty := true;
end;
procedure TBFStream.PutLongint(const L: longint);
begin
if (BufferPos - Buffer + SizeOf(L) > BufLen) then
begin
ResetBuffer;
BufferPos := Buffer;
end;
Move(L, BufferPos^, SizeOf(L));
inc(BufferPos, SizeOf(L));
IsDirty := true;
end;
procedure TBFStream.ResetBuffer;
begin
if IsDirty then
begin
Write(Buffer^, BufferPos - Buffer);
IsDirty := false;
end;
FillChar(Buffer^, BufLen + 1, #0);
BufferPos := Buffer + BufLen;
BytesRead := BufLen;
end;
function TBFStream.Seek(Offset: longint; Origin: word): longint;
begin
ResetBuffer;
Result := inherited Seek(Offset, Origin);
end;
{ ******* Utility routine ******* }
function TempLocationValid: boolean;
var
tfl: string;
L: integer;
begin
tfl := QDBTempFileLocation;
Result := false;
if tfl <> '' then
begin
tfl := ExpandFileName(tfl);
L := Length(tfl);
if tfl[L] = '\' then
begin
if tfl[L - 1] <> ':' then
Delete(QDBTempFileLocation, Length(QDBTempFileLocation), 1);
tfl := tfl + 'nul';
end
else
tfl := tfl + '\nul';
Result := FileExists(tfl);
end;
end;
{$IFNDEF WIN32}
const
MAX_PATH = 255;
{$ENDIF}
function TempFileName(Prefix: string): string;
{ returns a unique name for a temp file }
const
TmpStrLen = MAX_PATH;
var
TmpName: pchar;
TmpPath: pchar;
{$IFNDEF WIN32}
Dummy: pchar;
{$ENDIF}
begin
Result := '';
GetMem(TmpName, TmpStrLen);
try
FillChar(TmpName^, TmpStrLen, #0);
{$IFDEF WIN32}
GetMem(TmpPath, TmpStrLen);
try
FillChar(TmpPath^, TmpStrLen, #0);
if TempLocationValid then
StrPCopy(TmpPath, ExpandFileName(QDBTempFileLocation))
else
GetTempPath(TmpStrLen, TmpPath);
GetTempFileName(TmpPath, pchar(Prefix), 0, TmpName);
Result := TmpName;
finally
FreeMem(TmpPath, TmpStrLen);
end;
{$ELSE}
GetMem(Dummy, Length(Prefix) + 1);
try
GetTempFileName(#0, StrPCopy(Dummy, Prefix), 0, TmpName);
Result := StrPas(TmpName);
finally
FreeMem(Dummy, Length(Prefix) + 1);
end;
{$ENDIF}
finally
FreeMem(TmpName, TmpStrLen);
end;
end;
procedure RenameOrMoveFile(const SrcFileName, DstFileName: string);
{ if src and dst are on the same drive rename will work }
{ if not we have to pysically move the file }
var
FSrc: TFileStream;
FDst: TFileStream;
begin
{first get rid of the dst file }
SysUtils.DeleteFile(DstFileName);
{ if a rename doesn't work we have to copy }
if not RenameFile(SrcFileName, DstFileName) then
begin
FDst := TFileStream.Create(DstFileName, fmCreate);
try
FSrc := TFileStream.Create(SrcFileName, fmOpenRead);
try
FDst.CopyFrom(FSrc, FSrc.size);
finally
FSrc.Free;
end;
finally
FDst.Free;
end;
SysUtils.DeleteFile(SrcFileName);
end;
end;
{ TTempBFStream }
const
TmpFilePrefix = 'QDT';
constructor TTempBFStream.Create(const OldFileName: string);
begin
TmpFileName := TempFileName(TmpFilePrefix);
if TmpFileName = '' then
raise EQDBIndexError.CreateFmt(STempFile, [FOldFileName]);
inherited Create(TmpFileName, fmCreate or fmShareExclusive, 32 * 1024);
FOldFileName := OldFileName;
end;
destructor TTempBFStream.Destroy;
begin
inherited Destroy;
if TmpFileName <> '' then
RenameOrMoveFile(TmpFileName, FOldFileName);
TmpFileName := '';
FOldFileName := '';
end;
{ GREP code ... a cut-down version of code graciously supplied by
Gerald Nunn ... from his GExperts Delphi add-in
(http://www.amano-blick.com/~gnunn/GExperts.htm). Any problems
with this code are mine rather than his. }
const
opCHAR = 1;
opBOL = 2;
opEOL = 3;
opANY = 4;
opCLASS = 5;
opNCLASS = 6;
opSTAR = 7;
opBOW = 8; {opPLUS = 8;}
opEOW = 9; {opMINUS = 9;}
opALPHA = 10;
opDIGIT = 11;
opNALPHA = 12;
opPUNCT = 13;
opRANGE = 14;
opENDPAT = 15;
function LoCase(ch: char): char;
begin
if (ch >= 'A') and (ch <= 'Z') then
inc(ch, 32);
Result := ch;
end;
function GrepMatch(const S, Pattern: string; CaseSensitive, WholeWord: boolean
): boolean;
var
L: integer;
FixBOL: boolean; { beginning of line }
FixBOW: boolean; { beginning of word }
FString: pchar;
FStrLen: integer;
FPattern: pchar;
PatternOK: boolean;
procedure CompilePattern(Source: string);
var
lp: integer; {Last Pattern Pointer}
c: integer; {Current Character}
procedure Store(ch: char);
begin
if not CaseSensitive then
FPattern[lp] := LoCase(ch)
else
FPattern[lp] := ch;
inc(lp);
end;
procedure cclass;
var
cstart: integer;
begin
cstart := c;
inc(c);
if Source[c] = '^' then
Store(char(opNCLASS))
else
Store(char(opCLASS));
while (c <= Length(Source)) and (Source[c] <> ']') do
begin
if (Source[c] = '\') and (c < Length(Source)) and (Source[c + 1
] = '\') then
begin
Store(Source[c + 2]);
inc(c, 3);
end
else
if (Source[c] = '-') and (c - cstart > 1) and (Source[c + 1]
<>
']') and (c < Length(Source)) then
begin
dec(lp, 2);
Store(char(opRANGE));
Store(Source[c - 1]);
Store(Source[c + 1]);
inc(c, 2);
end
else
begin
Store(Source[c]);
inc(c);
end;
end;
if (Source[c] <> ']') or (c > Length(Source)) then
begin
PatternOK := false;
exit;
end;
inc(c);
end;
begin
try
c := 1;
lp := 0;
while c <= Length(Source) do
begin
case Source[c] of
'^': { beginning of line }
begin
if c = 1 then
FixBOL := true
else
begin
PatternOK := false;
exit;
end;
inc(c);
end;
'%': { beginning of word }
begin
if c = 1 then
FixBOW := true
else
begin
PatternOK := false;
exit;
end;
inc(c);
end;
'$': { end of line }
begin
if c <> Length(Source) then
begin
PatternOK := false;
exit;
end;
Store(char(opEOL));
inc(c);
end;
'&': { end of word }
begin
if c <> Length(Source) then
begin
PatternOK := false;
exit;
end;
Store(char(opEOW));
inc(c);
end;
'.':
begin
Store(char(opANY));
inc(c);
end;
'[':
cclass;
':':
begin
if c < Length(Source) then
begin
case UpCase(Source[c + 1]) of
'A':
Store(char(opALPHA));
'D':
Store(char(opDIGIT));
'N':
Store(char(opNALPHA));
' ':
Store(char(opPUNCT));
else
begin
Store(char(opENDPAT));
PatternOK := false;
exit;
end;
end;
inc(c, 2);
end;
end;
'\':
begin
if c < Length(Source) then
if Source[c + 1] = '\' then
begin
Store(char(opCHAR));
Store(Source[c + 2]);
inc(c, 3);
end
else
begin
Store(char(opCHAR));
Store(Source[c]);
inc(c);
end
else
begin
Store(char(opCHAR));
Store(Source[c]);
inc(c);
end;
end;
else
begin
Store(char(opCHAR));
Store(Source[c]);
inc(c);
end;
end;
end;
finally
Store(char(opENDPAT));
Store(#0);
end;
end;
function PatternMatch: boolean;
var
L, p: integer; {line and pattern pointers}
op: integer; {Pattern operation}
LinePos: integer;
function IsFound: boolean;
var
S, E: integer;
begin
Result := false;
if WholeWord then
begin
S := LinePos - 2;
E := L;
if (S > 0) then
if (LoCase(FString[S]) >= 'a') and (LoCase(FString[S]) <=
'z') then
exit;
if (FString[E] <> #0) then
if (LoCase(FString[E]) >= 'a') and (LoCase(FString[E]) <=
'z') then
exit;
end;
if FixBOL and (LinePos <> 1) then
exit;
if (FixBOW) and not ((LinePos = 1) or (FString[LinePos - 2] = ' ')
or (FString[LinePos - 2] <= #64)) then
exit;
Result := true;
end;
begin
Result := false;
if not PatternOK then
exit;
if FString[0] = #0 then
exit;
if integer(FPattern[0]) = opENDPAT then
exit;
if not CaseSensitive then
StrLower(FString);
LinePos := 0;
{Don't bother pattern matching if first search is opCHAR, just go to first match directly}
{Results in about a 5% to 10% speed increase}
if (integer(FPattern[0]) = opCHAR) and not CaseSensitive then
while (FPattern[1] <> FString[LinePos]) and (FString[LinePos] <>
#0) do
inc(LinePos);
while FString[LinePos] <> #0 do
begin
L := LinePos;
p := 0;
op := integer(FPattern[p]);
while (op <> opENDPAT) do
begin
case op of
opCHAR:
begin
if not (FString[L] = FPattern[p + 1]) then
Break;
inc(p, 2);
end;
opEOL:
begin
if L = FStrLen then
begin
inc(LinePos);
Result := IsFound;
end;
exit;
end;
opEOW:
begin
if (L = FStrLen) or (FString[L] = ' ') or (FString[L] <
#64) then
begin
inc(LinePos);
Result := IsFound;
end;
exit;
end;
opANY:
begin
if (FString[L] = #13) or (FString[L] = #10) or (FString[L
] = #0) then
Break;
inc(p);
end;
opCLASS:
begin
inc(p);
{Compare letters to find a match}
while (FPattern[p] > #15) and (FPattern[p] <> FString[L]) do
inc(p);
{Was a match found?}
if (FPattern[p] <= #15) then
Break;
{move FPattern pointer to next opcode}
while (FPattern[p] > #15) do
inc(p);
end;
opNCLASS:
begin
inc(p);
{Compare letters to find a match}
while (FPattern[p] > #15) and (FPattern[p] <> FString[L]) do
inc(p);
if (FPattern[p] > #15) then
Break;
end;
opALPHA:
begin
if (LoCase(FString[L]) < 'a') or (LoCase(FString[L]) >
'z') then
Break;
inc(p);
end;
opDIGIT:
begin
if (FString[L] < '0') or (FString[L] > '9') then
Break;
inc(p);
end;
opNALPHA:
begin
if (LoCase(FString[L]) > 'a') or (LoCase(FString[L]) <
'z') then
Break;
inc(p);
end;
opPUNCT:
begin
if (FString[L] = ' ') or (FString[L] > #64) then
Break;
inc(p);
end;
opRANGE:
begin
if (FString[L] < FPattern[p + 1]) or (FString[L] >
FPattern[p + 2]) then
Break;
inc(p, 3);
end;
else
inc(p);
end; {End Case}
op := integer(FPattern[p]);
inc(L);
end; {End While op<>opENDPAT}
inc(LinePos);
if op = opENDPAT then
Result := IsFound;
end; {While FString[LinePos]<>#0}
end;
begin
L := Length(S) + 1;
FString := StrAlloc(L);
FillChar(FString^, L, #0);
FString := StrPCopy(FString, S);
FStrLen := StrLen(FString);
FPattern := StrAlloc(512);
FixBOL := false;
FixBOW := false;
PatternOK := true;
CompilePattern(Pattern);
if PatternOK then
Result := PatternMatch
else
Result := true;
StrDispose(FString);
StrDispose(FPattern);
end;
{ TQDB }
constructor TQDB.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
try
Index := TIndexList.Create;
Index.Sorted := true;
Cache := TCacheList.Create;
Admin := TIndexList.Create;
Admin.Sorted := true;
SetReady(false);
FItemIndex := 0;
FAdminIndex := 0;
FKey := '';
FCount := Index.Count;
FReadOnly := false;
FFrontWild := '<';
FBackWild := '>';
FVersion := FileVersion;
FAuthor := AuthorInfo;
FUpdating := 0;
FSaveOnClose := true;
FExpandedFileNames := true;
UseGrepMatch := false;
Restructuring := false;
except
Index.Free;
Cache.Free;
Admin.Free;
raise;
end;
end;
destructor TQDB.Destroy;
begin
if Live and (FFileName <> '') then
CloseQDB;
Index.Free;
Cache.Free;
Admin.Free;
FItemIndex := 0;
inherited Destroy;
end;
procedure TQDB.AboutToKill(var OK: boolean);
begin
if Assigned(FBeforeKill) then
FBeforeKill(Self, OK);
end;
procedure TQDB.AboutToOverWrite(var OK: boolean);
begin
if Assigned(FBeforeOverWrite) then
FBeforeOverWrite(Self, OK);
end;
procedure TQDB.Add(Stream: TStream; Key: TKey);
{ add an item to the file -- this is now the method of choice }
var
IndRec: TIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
Stream.Seek(0, 0);
if ReadOnly then
CannotChange
else
begin
try
IndRec := TIndex.Create;
except
IndexError(SIndexAdd);
end;
IndRec.Pos := QDBFile.Seek(0, 2);
if IndRec.Pos < 0 then
FileError(-1, SCorrupt);
try
FItemIndex := Index.AddObject(Key, IndRec);
except
IndRec.Free;
IndexError(SDuplicateKey);
end;
FKey := GetKey(FItemIndex);
Cache.Store(Stream, Index.Strings[FItemIndex]);
if Compression then
begin
TIndex(Index.Objects[FItemIndex]).Len := SquashStream(Stream, QDBFile
);
TIndex(Index.Objects[FItemIndex]).Ext := TIndex(Index.Objects[
FItemIndex]).Ext + [IsCompressed];
end
else
begin
TIndex(Index.Objects[FItemIndex]).Len := QDBFile.CopyFrom(Stream,
Stream.size);
TIndex(Index.Objects[FItemIndex]).Ext := TIndex(Index.Objects[
FItemIndex]).Ext - [IsCompressed];
end;
FCount := Index.Count;
IsDirty := true;
Added;
if (FUpdating = 0) then
Navigate;
end;
Stream.Seek(0, 0);
end;
procedure TQDB.Added;
begin
if Assigned(FOnAdded) then
FOnAdded(Self);
end;
procedure TQDB.AddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
{ add an item to the file -- if you can, use Add instead }
var
TmpStream: TMemoryStream;
begin
TmpStream := TMemoryStream.Create;
try
TmpStream.Write(ItemPtr^, ItemLen);
Add(TmpStream, Key);
finally
TmpStream.Free;
end;
end;
procedure TQDB.AddStreamItem(Stream: TStream; Key: TKey);
{ add an item to the file -- provided for compatibility }
{ -- use Add instead }
begin
Add(Stream, Key);
end;
procedure TQDB.AdminAddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey
);
var
IndRec: TIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
CannotChange
else
begin
try
IndRec := TIndex.Create;
except
IndexError(SIndexAdd);
end;
IndRec.Pos := QDBFile.Seek(0, 2);
if IndRec.Pos < 0 then
FileError(-1, SCorrupt);
IndRec.Len := ItemLen;
IndRec.Ext := IndRec.Ext + [IsAdminItem];
try
FAdminIndex := Admin.AddObject(Key, IndRec);
except
IndRec.Free;
IndexError(SDuplicateKey);
end;
if QDBFile.Write(ItemPtr^, ItemLen) <> ItemLen then
IndexError(SDataAdd);
IsDirty := true;
end;
end;
procedure TQDB.AdminChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
var
ThisKey: TKey;
IndRec: TIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if Admin.Count < 1 then
NoData
else
if ReadOnly then
CannotChange
else
begin
ThisKey := Admin.Strings[FAdminIndex];
if ItemLen <= AdminItemSize then
begin {just write on top of the old}
with TIndex(Admin.Objects[FAdminIndex]) do
begin
QDBFile.Seek(Pos, 0);
Len := ItemLen;
end;
end
else
begin
TIndex(Admin.Objects[FAdminIndex]).Free;
Admin.Delete(FAdminIndex);
try
IndRec := TIndex.Create;
except
IndexError(SIndexAdd);
end;
IndRec.Pos := QDBFile.Seek(0, 2);
if IndRec.Pos < 0 then
FileError(-1, SDataAdd);
IndRec.Len := ItemLen;
IndRec.Ext := IndRec.Ext + [IsAdminItem];
try
FAdminIndex := Admin.AddObject(ThisKey, IndRec);
except
IndRec.Free;
raise
end;
end;
if QDBFile.Write(ItemPtr^, ItemLen) <> ItemLen then
FileError(-1, SDataAdd);
IsDirty := true;
end;
end;
procedure TQDB.AdminClear(StartOfKey: TKey);
var
TmpList: TStringList;
i: integer;
begin
if StartOfKey = '' then
begin
Admin.EmptyAndClear;
FAdminIndex := 0;
end
else
begin
TmpList := TStringList.Create;
try
AdminKeys(TmpList, StartOfKey);
for i := 0 to TmpList.Count - 1 do
AdminDelete(TmpList[i]);
finally
TmpList.Free;
end;
end;
end;
function TQDB.AdminCount: TItemIndex;
{ nb not a property like TQDB.Count }
begin
Result := Admin.Count;
end;
procedure TQDB.AdminDelete(Key: TKey);
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
CannotChange
else
if not AdminExactMatch(Key) then
raise EQDBBadKey.Create(SBadKey)
else
begin
TIndex(Admin.Objects[FAdminIndex]).Free;
Admin.Delete(FAdminIndex);
if FAdminIndex > 0 then
FAdminIndex := FAdminIndex - 1;
IsDirty := true;
end;
end;
function TQDB.AdminExactMatch(Key: TKey): boolean;
{ generally you will know what items are stored -- use }
{ this function to locate the key you want }
var
n: TItemIndex;
begin
Result := Admin.Find(Key, n);
if Result then
FAdminIndex := n;
end;
function TQDB.AdminGetBoolean(Key: TKey): boolean;
var
Buffer: pointer;
BufLen: longint;
begin
Result := false;
if AdminExactMatch(Key) then
begin
BufLen := AdminItemSize;
if BufLen = SizeOf(boolean) then
begin
GetMem(Buffer, BufLen);
try
AdminGetItem(Buffer);
Result := Byte(Buffer^) = 1;
finally
FreeMem(Buffer, BufLen);
end;
end;
end
else
raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
end;
function TQDB.AdminGetInteger(Key: TKey): longint;
var
Buffer: pointer;
BufLen: longint;
begin
Result := 0;
if AdminExactMatch(Key) then
begin
BufLen := AdminItemSize;
if BufLen = SizeOf(longint) then
begin
GetMem(Buffer, BufLen);
try
AdminGetItem(Buffer);
Result := longint(Buffer^);
finally
FreeMem(Buffer, BufLen);
end;
end;
end
else
raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
end;
procedure TQDB.AdminGetItem(ItemPtr: pointer);
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if Admin.Count > 0 then
begin
try
QDBFile.Seek(TIndex(Admin.Objects[FAdminIndex]).Pos, 0);
QDBFile.Read(ItemPtr^, TIndex(Admin.Objects[FAdminIndex]).Len);
except
IndexError(SCorrupt);
end;
end
else
NoData;
end;
function TQDB.AdminGetString(Key: TKey): string;
var
Buffer: pointer;
BufLen: longint;
begin
Result := '';
if AdminExactMatch(Key) then
begin
BufLen := AdminItemSize;
{$IFNDEF WIN32}
if BufLen > 255 then
BufLen := 255;
{$ENDIF}
if BufLen > 0 then
begin
GetMem(Buffer, BufLen);
try
AdminGetItem(Buffer);
Result := StrPas(pchar(Buffer));
finally
FreeMem(Buffer, BufLen);
end;
end;
end
else
raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
end;
function TQDB.AdminItemSize: TDataIndex;
begin
if Admin.Count < 1 then
begin
Result := 0;
NoData;
end
else
Result := TIndex(Admin.Objects[FAdminIndex]).Len;
end;
function TQDB.AdminKeyExists(Key: TKey): boolean;
begin
Result := AdminExactMatch(Key);
end;
function TQDB.AdminKeys(Keys: TStrings; StartOfKey: TKey): longint;
{ makes a list of all the Admin keys }
var
ThisItem: TItemIndex;
ThisKey: TKey;
Len: longint;
begin
TStringList(Keys).Clear;
for ThisItem := 1 to Admin.Count do
begin
ThisKey := Admin.Strings[ThisItem - 1];
Len := Length(StartOfKey);
if Copy(ThisKey, 1, Len) = StartOfKey then
Keys.Add(ThisKey);
end;
Result := Keys.Count;
end;
procedure TQDB.AdminSetBoolean(Key: TKey; b: boolean);
begin
if AdminExactMatch(Key) then
AdminChangeItem(@b, SizeOf(b))
else
AdminAddItem(@b, SizeOf(b), Key);
end;
procedure TQDB.AdminSetInteger(Key: TKey; n: longint);
begin
if AdminExactMatch(Key) then
AdminChangeItem(@n, SizeOf(n))
else
AdminAddItem(@n, SizeOf(n), Key);
end;
procedure TQDB.AdminSetString(Key: TKey; const S: string);
var
p: pchar;
begin
p := StrAlloc(Length(S) + 1);
try
StrPCopy(p, S);
if AdminExactMatch(Key) then
AdminChangeItem(p, Length(S) + 1)
else
AdminAddItem(p, Length(S) + 1, Key);
finally
StrDispose(p);
end;
end;
procedure TQDB.AssignKeyList(Keys: TStrings);
{ Copies the in-memory list of keys to the Keys parameter. }
{ Items that are filtered out are not included. }
var
ThisItem: TItemIndex;
ThisKey: TKey;
WasSorted: boolean;
begin
TStringList(Keys).Clear;
{ TStrings has no sort method but TStringList does }
if Keys is TStringList then
begin
WasSorted := TStringList(Keys).Sorted;
TStringList(Keys).Sorted := false;
end;
MonitorSetup(Index.Count, prKeyList);
for ThisItem := 1 to Index.Count do
begin
MonitorUpdate(ThisItem);
if Filtered then
begin
ThisKey := GetKey(ThisItem - 1);
if Matches(ThisKey, FFilter) then
TStringList(Keys).Add(ThisKey);
end
else
TStringList(Keys).Add(GetKey(ThisItem - 1));
end;
if Keys is TStringList then
TStringList(Keys).Sorted := WasSorted;
MonitorZero;
end;
procedure TQDB.BeginUpdate;
begin
inc(FUpdating);
end;
procedure TQDB.CacheFlush;
begin
Cache.Flush;
end;
procedure TQDB.CacheStatistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio
: longint);
begin
Cache.Statistics(MaxSize, CurSize, CurLen, HitRatio, DropRatio);
end;
procedure TQDB.DoCancel;
begin
if not AutoEdit then
FEditing := false;
FInserting := false;
UpdateNavigator;
end;
procedure TQDB.Cancel;
begin
if Assigned(FBeforeCancel) then
FBeforeCancel(Self);
DoCancel;
if Assigned(FAfterCancel) then
FAfterCancel(Self);
end;
procedure TQDB.CannotChange;
{ If a warning handler has not been assigned an exception is raised }
{ To silence exceptions assign at least an empty handler }
begin
if Assigned(FWarnReadOnly) then
FWarnReadOnly(Self)
else
raise EQDBReadOnly.CreateFmt(SReadOnly, [FFileName]);
end;
procedure TQDB.Change(Stream: TStream);
{ Change the contents of the current stream item }
{ This is the change method of choice -- use it }
var
ThisKey: TKey;
IndRec: TIndex;
TmpStream: TMemoryStream;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if Index.Count < 1 then
NoData
else
if ReadOnly then
CannotChange
else
begin
Stream.Seek(0, 0);
Cache.Remove(Index.Strings[FItemIndex]);
ThisKey := GetKey(FItemIndex);
TmpStream := TMemoryStream.Create;
if Compression then
SquashStream(Stream, TmpStream)
else
TmpStream.LoadFromStream(Stream);
TmpStream.Seek(0, 0);
if TmpStream.size <= TIndex(Index.Objects[FItemIndex]).Len then
begin {just write on top of the old}
with TIndex(Index.Objects[FItemIndex]) do
begin
QDBFile.Seek(Pos, 0);
Len := TmpStream.size;
if Compression then
Ext := Ext + [IsCompressed]
else
Ext := Ext - [IsCompressed];
end;
end
else
begin
TIndex(Index.Objects[FItemIndex]).Free;
Index.Delete(FItemIndex);
try
IndRec := TIndex.Create;
except
IndexError(SIndexAdd);
end;
IndRec.Pos := QDBFile.Seek(0, 2);
if IndRec.Pos < 0 then
FileError(-1, SDataAdd);
IndRec.Len := TmpStream.size;
if Compression then
IndRec.Ext := IndRec.Ext + [IsCompressed]
else
IndRec.Ext := IndRec.Ext - [IsCompressed];
try
FItemIndex := Index.AddObject(ThisKey, IndRec);
except
IndRec.Free;
raise
end;
end;
QDBFile.CopyFrom(TmpStream, TmpStream.size);
TmpStream.Free;
Cache.Store(Stream, Index.Strings[FItemIndex]);
FCount := Index.Count;
IsDirty := true;
Stream.Seek(0, 0);
Changed;
end;
end;
procedure TQDB.Changed;
begin
if Assigned(FOnChanged) then
FOnChanged(Self);
end;
procedure TQDB.ChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
{ Change the contents of the current item }
{ If you can, use the Change method instead }
var
TmpStream: TMemoryStream;
begin
TmpStream := TMemoryStream.Create;
try
TmpStream.Write(ItemPtr^, ItemLen);
Change(TmpStream);
finally
TmpStream.Free;
end;
end;
(*// Alex
procedure TQDB.ChangeKey(key: TKey);
var
IndRec: TIndex;
begin
IndRec := TIndex.Create;
IndRec.pos := TIndex(Index.Objects[FItemIndex]).pos;
IndRec.len := TIndex(Index.Objects[FItemIndex]).len;
IndRec.ext := TIndex(Index.Objects[FItemIndex]).ext;
Index.beginupdate;
TIndex(Index.Objects[FItemIndex]).Free;
Index.Delete(FItemIndex);
FItemindex := Index.addobject(key, IndRec);
Index.endupdate;
end;
*)
procedure TQDB.ChangeStreamItem(Stream: TStream);
{ provided for backwards compatibility -- use Change instead }
begin
Change(Stream);
end;
function TQDB.CloseMatch(Partialkey: TKey): boolean;
{ looking for a near match -- stops where a full match would be }
var
n: TItemIndex;
IsFound: boolean;
begin
Result := Index.Find(Partialkey, n);
IsFound := Result;
if (not Result) and (n >= 0) and (n < Index.Count) then
begin
if KeyCaseSensitive then
IsFound := (Copy(Index[n], 1, Length(Partialkey)) = Partialkey)
else
IsFound := (LowerCase(Copy(Index[n], 1, Length(Partialkey))) = LowerCase(Partialkey));
end;
if IsFound then
begin
ItemIndex := n;
Found;
end;
end;
procedure TQDB.CloseQDB;
{ close up a QDB file }
begin
SaveIndex;
FItemIndex := 0;
FKey := '';
FCount := 0;
IsDirty := false;
SetReady(false);
Password := '';
end;
procedure TQDB.Compress;
{ compresses every item in the file -- not quick! }
var
m: TMemoryStream;
n: TItemIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
begin
CannotChange;
exit;
end;
Compression := true;
BeginUpdate;
MonitorSetup(Index.Count, prCompress);
for n := 1 to Index.Count do
begin
MonitorUpdate(n);
if not ItemIsCompressed(n - 1) then
begin
m := TMemoryStream.Create;
try
FItemIndex := n - 1;
Get(m);
Change(m);
finally
m.Free;
end;
end;
end;
MonitorZero;
Pack;
EndUpdate;
end;
procedure TQDB.CreateQDB;
{ makes a new empty QDB file on disk ... }
const
Sig1: array[0..3] of char = ('Q', 'D', 'B', #0);
Sig2: array[0..3] of char = ('Q', 'I', 'X', #0);
var
f: TFileHandle;
sz1,
sz2: longint;
zero: Byte;
bigzero: longint;
begin
zero := 0; { to null-terminate the index block }
bigzero := 0; { number of items in the index = 0 }
f := FileCreate(FileName);
if f < 0 then
FileError(f, '');
try { protect file f }
FileWrite(f, Sig1, SizeOf(Sig1));
sz1 := 0;
FileWrite(f, sz1, SizeOf(sz1));
FileWrite(f, Sig2, SizeOf(Sig2));
sz2 := SizeOf(bigzero) + SizeOf(zero);
FileWrite(f, sz2, SizeOf(sz2));
FileWrite(f, bigzero, SizeOf(bigzero));
FileWrite(f, zero, SizeOf(zero));
finally
FileClose(f);
end;
end;
procedure TQDB.DoDelete;
{ delete an item from the index -- need to Pack to get it }
{ out of the item file }
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
CannotChange
else
if Index.Count < 1 then
NoData
else
begin
if not AutoEdit then
FEditing := false;
FInserting := false;
Cache.Remove(Index.Strings[FItemIndex]);
TIndex(Index.Objects[FItemIndex]).Free;
Index.Delete(FItemIndex);
if FItemIndex > 0 then
ItemIndex := FItemIndex - 1
else
FKey := '';
FCount := Index.Count;
IsDirty := true;
Deleted;
UpdateNavigator;
end;
end;
procedure TQDB.Delete;
begin
if Assigned(FBeforeDelete) then
FBeforeDelete(Self);
DoDelete;
if Assigned(FAfterDelete) then
FAfterDelete(Self);
end;
procedure TQDB.Deleted;
begin
if Assigned(FOnDeleted) then
FOnDeleted(Self);
end;
procedure TQDB.DeleteItem;
{ provided for backward compatibility -- use Delete }
begin
Delete;
end;
procedure TQDB.DemandPassword;
var
NewPassword: TPassword;
begin
NewPassword := '';
if Assigned(FOnDemandPassWord) then
FOnDemandPassWord(Self, NewPassword);
Password := NewPassword;
end;
procedure TQDB.DoEdit;
begin
FEditing := true;
UpdateNavigator;
end;
procedure TQDB.Edit;
begin
if Assigned(FBeforeEdit) then
FBeforeEdit(Self);
DoEdit;
if Assigned(FAfterEdit) then
FAfterEdit(Self);
end;
procedure TQDB.EndUpdate;
begin
dec(FUpdating);
if (FUpdating = 0) then
Navigate;
end;
function TQDB.ExactMatch(Key: TKey): boolean;
{ simple stringlist find }
var
n: TItemIndex;
begin
Result := Index.Find(Key, n);
if Result then
begin
ItemIndex := n;
Found;
end;
end;
procedure TQDB.Expand;
{ decompresses every item in the file -- not quick! }
var
m: TMemoryStream;
n: TItemIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
begin
CannotChange;
exit;
end;
Compression := false;
BeginUpdate;
MonitorSetup(Index.Count, prCompress);
for n := 1 to Index.Count do
begin
MonitorUpdate(n);
if ItemIsCompressed(n - 1) then
begin
m := TMemoryStream.Create;
try
FItemIndex := n - 1;
Get(m);
Change(m);
finally
m.Free;
end;
end;
end;
MonitorZero;
Pack;
EndUpdate;
end;
procedure TQDB.FileAssigned;
begin
if Assigned(FOnFileAssigned) then
FOnFileAssigned(Self);
end;
procedure TQDB.FileError(ErrCode: integer; SDefault: string);
{ report errors concerning file resources }
var
SErr: string;
begin
case ErrCode of
- 1:
SErr := SDefault;
- 2:
SErr := SMissing;
- 3:
SErr := SDoorOpen;
- 4:
SErr := STooMany;
- 5:
SErr := SShareError;
- 101:
SErr := SDiskFull;
else
raise EQDBFileError.CreateFmt(SUnknownError, [IntToStr(ErrCode)]);
end;
raise EQDBFileError.CreateFmt(SErr, [FFileName]);
end;
function TQDB.FileToRecover: string;
{ returns the name of an orphaned working file }
var
TmpPath: pchar;
PlaceToLook: string;
f: TSearchRec;
OldQDBFileName: string;
Handle: integer;
begin
Result := '';
GetMem(TmpPath, MAX_PATH);
try
FillChar(TmpPath^, MAX_PATH, #0);
{$IFDEF WIN32}
GetTempPath(MAX_PATH, TmpPath);
PlaceToLook := TmpPath;
{$ELSE}
GetTempFileName(#0, 'RRM', 0, TmpPath);
PlaceToLook := ExtractFilePath(StrPas(TmpPath));
{$ENDIF}
finally
FreeMem(TmpPath, MAX_PATH);
end;
if FindFirst(PlaceToLook + 'QDB*.tmp', 0, f) = 0 then
begin
OldQDBFileName := PlaceToLook + f.Name;
SysUtils.FindClose(f);
{ check if in use at the moment or if genuine orphan ... }
Handle := FileOpen(OldQDBFileName, fmShareExclusive);
if Handle < 0 then
exit;
FileClose(Handle);
Result := OldQDBFileName;
end;
end;
procedure TQDB.FirstItem;
var
This: TItemIndex;
begin
FBoF := true;
FEoF := false;
if Filtered then
begin
{ the filter slows things down ... }
This := 0;
while (This < Index.Count) and not Matches(GetKey(This), FFilter) do
inc(This);
ItemIndex := This;
end
else
ItemIndex := 0;
{ if there is one, tell the navigator we've moved }
UpdateNavigator;
end;
procedure TQDB.ForceOverwrite(Value: boolean);
begin
FForceOverwrite := Value;
end;
procedure TQDB.Found;
begin
if Assigned(FOnFound) then
FOnFound(Self);
end;
procedure TQDB.Get(Stream: TStream);
{ retrieve an item from the file as a stream }
var
CacheN: longint;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if Index.Count > 0 then
begin
try
Stream.Seek(0, 0);
{ first check if in cache }
if Cache.Find(Index.Strings[FItemIndex], CacheN) then
begin
Cache.Fetch(Stream, CacheN);
end
else
begin
QDBFile.Seek(TIndex(Index.Objects[FItemIndex]).Pos, 0);
Stream.Seek(0, 0);
if ItemIsCompressed(FItemIndex) then
UnSquashStream(QDBFile, Stream, TIndex(Index.Objects[FItemIndex]).Len)
else
Stream.CopyFrom(QDBFile, TIndex(Index.Objects[FItemIndex]).Len);
Stream.Seek(0, 0);
Cache.Store(Stream, Index.Strings[FItemIndex]);
end;
Stream.Seek(0, 0);
except
IndexError(SCorrupt);
end;
end
else
NoData;
end;
function TQDB.GetCacheFrequency: integer;
{ gets the percentage of accesses that hit the cache }
begin
Result := Cache.GetFrequency;
end;
function TQDB.GetCacheSize: longint;
begin
Result := Cache.FMaximumSize;
end;
function TQDB.GetFileName: TQDBFileName;
begin
Result := FFileName;
end;
function TQDB.GetFilteredCount: TItemIndex;
{ how many keys match the current filter? }
var
This: TItemIndex;
i: TItemIndex;
begin
if Filtered then
begin
{ not quick ... }
i := 0;
for This := 1 to Index.Count do
if Matches(GetKey(This - 1), FFilter) then
inc(i);
Result := i;
end
else
Result := FCount;
end;
procedure TQDB.GetItem(ItemPtr: pointer);
{ retrieve the current item -- try to use Get instead }
var
TmpStream: TMemoryStream;
begin
TmpStream := TMemoryStream.Create;
try
GetStreamItem(TmpStream);
TmpStream.Read(ItemPtr^, TmpStream.size);
finally
TmpStream.Free;
end;
end;
function TQDB.GetItemSize(Value: TItemIndex): TDataIndex;
{ returns the number of bytes an item occupies on the disk }
begin
Result := TIndex(Index.Objects[Value]).Len;
end;
function TQDB.GetKey(Value: TItemIndex): TKey;
{ fetches the key for the given item }
begin
if Index.Count < 1 then
begin
Result := '';
NoData;
end
else
Result := Index.Strings[Value];
end;
function TQDB.GetKeyCase: boolean;
{ is the index case-sensitive ? }
begin
Result := Index.CaseSensitive;
end;
function TQDB.GetStr(n: TItemIndex): string;
{ gets item n as a string }
begin
Result := '';
ItemIndex := n;
Result := GetThisStr;
end;
function TQDB.GetStrByKey(Key: TKey): string;
{ if key exists gets item as string }
begin
if ExactMatch(Key) then
Result := GetThisStr
else
raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
end;
procedure TQDB.GetStreamItem(Stream: TStream);
{ provided for compatibility -- use Get }
begin
Get(Stream);
end;
function TQDB.GetThisItemSize: TDataIndex;
{ returns the number of bytes the current item occupies }
{ in memory -- may have to uncompress an item to tell }
var
m: TMemoryStream;
n: TDataIndex;
begin
if Index.Count < 1 then
begin
Result := 0;
NoData;
end
else
if ItemIsCompressed(FItemIndex) then
begin
{ look in Cache }
if Cache.Find(Key, n) then
begin
Result := TCache(Cache.Objects[n]).Stream.size;
end
else
begin
m := TMemoryStream.Create;
try
Get(m);
Result := m.size;
finally
m.Free;
end;
end;
end
else
Result := GetItemSize(FItemIndex);
end;
function TQDB.GetThisStr: string;
{ gets the current item as a string -- truncates D1 }
{ strings if needed }
var
m: TMemoryStream;
size: longint;
begin
m := TMemoryStream.Create;
try
Get(m);
size := m.size;
{$IFNDEF WIN32}
if size > 255 then
size := 255;
Result[0] := chr(size);
{$ELSE}
SetLength(Result, size);
{$ENDIF}
m.Read(Result[1], size);
finally
m.Free;
end;
end;
function TQDB.GrepMatches(Key: TKey; Pattern: TKey): boolean;
begin
Result := GrepMatch(Key, Pattern, KeyCaseSensitive, FMatchWholeWord);
end;
procedure TQDB.IndexError(ErrMsg: string);
{ reports errors to do with the Index }
begin
raise EQDBIndexError.CreateFmt(ErrMsg, [FFileName]);
end;
procedure TQDB.DoInsert;
begin
FInserting := true;
UpdateNavigator;
end;
procedure TQDB.Insert;
begin
if Assigned(FBeforeInsert) then
FBeforeInsert(Self);
DoInsert;
if Assigned(FAfterInsert) then
FAfterInsert(Self);
end;
function TQDB.ItemIsCompressed(Value: TItemIndex): boolean;
{ is the item compressed? }
begin
Result := IsCompressed in TIndex(Index.Objects[Value]).Ext;
end;
function TQDB.KeyExists(Key: TKey): boolean;
var
n: TItemIndex;
begin
Result := Index.Find(Key, n);
end;
procedure TQDB.Kill;
{ Erases a QDB file after asking confirmation }
var
OK: boolean;
FileToDel: string;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
OK := false;
AboutToKill(OK);
if not OK then
exit;
if ReadOnly then
begin
CannotChange;
exit;
end;
if FFileName <> '' then
begin
FileToDel := FileName;
FileName := '';
SysUtils.DeleteFile(FileToDel);
Killed;
end;
end;
procedure TQDB.Killed;
begin
if Assigned(FOnKilled) then
FOnKilled(Self);
end;
procedure TQDB.LastItem;
var
This: TItemIndex;
begin
FBoF := false;
FEoF := true;
if Filtered then
begin
This := Index.Count - 1;
while (This >= 0) and not Matches(GetKey(This), FFilter) do
dec(This);
ItemIndex := This;
end
else
ItemIndex := Index.Count - 1;
{ if there is one, tell the navigator we've moved }
UpdateNavigator;
end;
function TQDB.Live: boolean;
begin
Result := Restructuring or not (csDesigning in ComponentState);
end;
procedure TQDB.LoadIndex;
{ Loading and saving the index are the most complex tasks in the unit }
{ Some words about the working file formats is in order: }
{ The item file (QDBFile) consists of contiguous, variable-length, }
{ blocks of data. Where one ends and the next begins is known only }
{ to the index file (QIXFile). LoadIndex reads this data into }
{ its Index list. The index file format is as follows: }
{ }
{ 4 bytes -- longint(n) = number of items in the file }
{ n variable length blocks of the following structure }
{ null terminated string data = the key to an item }
{ 4 bytes -- longint(n1) = the place in the item file }
{ 4 bytes -- longint(n2) = the size of the item }
{ 4 bytes -- longint(n3) = binary attribute flags }
{ }
var
Key: TKey;
Rec: TIndex;
NumItems: TItemIndex;
n: TItemIndex;
begin
try { except any error }
Split;
QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or
fmShareExclusive);
QIXFile := TBFStream.Create(QIXFileName, fmOpenReadWrite or
fmShareExclusive, -1);
try { protect file QIXFile }
QIXFile.GetLongint(NumItems);
MonitorSetup(NumItems, prStart);
Index.SetCapacity(NumItems);
Index.Sorted := false; { quicker to sort later than add to a sorted list }
for n := 1 to NumItems do
begin
QIXFile.GetKey(Key);
try
Rec := TIndex.Create;
except
IndexError(SIndexAdd);
end;
QIXFile.GetLongint(Rec.Pos);
QIXFile.GetLongint(Rec.Len);
QIXFile.GetLongint(longint(Rec.Ext));
try
if IsAdminItem in Rec.Ext then
FAdminIndex := Admin.AddObject(Key, Rec)
else
begin
FItemIndex := Index.AddObject(Key, Rec);
end;
except
Rec.Free;
Index.EmptyAndClear;
Index.Sorted := true;
Admin.EmptyAndClear;
IndexError(SIndexAdd);
end;
MonitorUpdate(n);
end;
FItemIndex := 0;
FAdminIndex := 0;
FCount := Index.Count;
IsDirty := false;
Index.Reverse;
MonitorZero;
if FCount > 0 then
begin
FKey := GetKey(FItemIndex);
end
else
begin
FKey := '';
end;
finally
QIXFile.Destroy;
end;
except
on EOutOfMemory do
IndexError(SCorrupt);
on ERangeError do
IndexError(SCorrupt);
on EQDBListError do
IndexError(SCorrupt);
else
raise;
end;
end;
procedure TQDB.MonitorSetup(const Max: longint; const Kind: TProgressOrigin
);
{ start monitoring the progress of a lengthy process }
begin
MonitorInterval := (Max div 10) + 1;
MonitorKind := Kind;
end;
procedure TQDB.MonitorUpdate(const n: longint);
{ update the progress monitor }
begin
if n mod MonitorInterval = 0 then
begin
Application.ProcessMessages;
SignalProgress((10 * n) div MonitorInterval, MonitorKind);
end;
end;
procedure TQDB.MonitorZero;
begin
SignalProgress(0, MonitorKind);
end;
procedure TQDB.Navigate;
{ whenever the ItemIndex is changed }
begin
if Assigned(FOnNavigate) then
FOnNavigate(Self);
end;
procedure TQDB.NextItem;
var
This: TItemIndex;
begin
FEoF := false;
FBoF := false;
This := FItemIndex;
if Filtered then
begin
while (This + 1 < Index.Count) and not Matches(GetKey(This + 1), FFilter
) do
begin
inc(This);
end;
end;
inc(This);
if This >= Index.Count then
begin
FBoF := false;
FEoF := true;
end
else
begin
ItemIndex := This;
end;
{ if there is one tell the navigator we've moved }
UpdateNavigator;
end;
procedure TQDB.NoData;
{ If a warning handler has not been assigned an exception is raised }
{ To silence exceptions assign at least an empty handler }
begin
if Assigned(FWarnNoData) then
FWarnNoData(Self)
else
raise EQDBNoData.CreateFmt(SNoData, [FileName]);
end;
procedure TQDB.Notification(AComponent: TComponent; Operation: TOperation);
{ if the link to a navigator is broken we must respond }
begin
inherited Notification(AComponent, Operation);
if (FQDBNavigator <> nil) and
(AComponent = FQDBNavigator) and
(Operation = opRemove) then
FQDBNavigator := nil;
end;
procedure TQDB.OpenQDB;
{ open a QDB file with the current filename }
begin
try
if not FileExists(FFileName) then
CreateQDB;
LoadIndex;
SetReady(true);
if AdminKeyExists('QDBCaseSensitive') then
Index.CaseSensitive := AdminAsBoolean['QDBCaseSensitive'];
except
raise
end;
end;
function TQDB.OrphanToRecover: boolean;
{ is there at least one orphaned file... }
begin
Result := (FileToRecover <> '');
end;
procedure TQDB.OutOfBounds;
{ If a warning handler has not been assigned an exception is raised }
{ To silence exceptions assign at least an empty handler }
begin
if Assigned(FWarnOutOfBounds) then
FWarnOutOfBounds(Self)
else
raise EQDBOutOfBounds.CreateFmt(SOutOfBounds, [FFileName]);
end;
procedure TQDB.Pack;
{ Re-organizes the working item file into index order }
{ eliminating any unreferenced items. }
var
TmpFile: TFileStream;
DatBuf: pchar;
QLen: TDataIndex;
QPos: TFilePos;
TmpFileName: string;
This, Init: TItemIndex;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly then
begin
CannotChange;
exit;
end;
Init := FItemIndex;
TmpFileName := TempFileName('QDB');
if TmpFileName = '' then
IndexError(STempFile);
TmpFile := TFileStream.Create(TmpFileName, fmCreate);
try { protect file tmpfile }
MonitorSetup(Index.Count, prPack);
for This := 1 to Admin.Count do
begin
QLen := TIndex(Admin.Objects[This - 1]).Len;
QPos := TIndex(Admin.Objects[This - 1]).Pos;
GetMem(DatBuf, QLen);
try { protect memory DatBuf }
try { catch file errors }
TIndex(Admin.Objects[This - 1]).Pos := TmpFile.Seek(0, 2);
QDBFile.Seek(QPos, 0);
QDBFile.Read(DatBuf^, QLen);
except
IndexError(SCorrupt);
end;
TmpFile.Write(DatBuf^, QLen);
finally
FreeMem(DatBuf, QLen);
end;
end;
for This := 1 to Index.Count do
begin
MonitorUpdate(This);
QLen := TIndex(Index.Objects[This - 1]).Len;
QPos := TIndex(Index.Objects[This - 1]).Pos;
GetMem(DatBuf, QLen);
try { protect memory DatBuf }
try { catch file errors }
TIndex(Index.Objects[This - 1]).Pos := TmpFile.Seek(0, 2);
QDBFile.Seek(QPos, 0);
QDBFile.Read(DatBuf^, QLen);
except
IndexError(SCorrupt);
end;
TmpFile.Write(DatBuf^, QLen);
finally
FreeMem(DatBuf, QLen);
end;
end;
finally
TmpFile.Free;
end;
QDBFile.Free;
RenameOrMoveFile(TmpFileName, QDBFileName);
QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive);
if Init > 0 then
ItemIndex := Init;
MonitorZero;
end;
function TQDB.PartialMatch(StartOfKey: TKey): boolean;
{ finds the next key which begins with the right chars -- if }
{ you want to include the first item call PartialMatchInit first }
var
n: TItemIndex;
k: TKey;
T: TItemIndex;
begin
Result := false;
if not KeyCaseSensitive then
StartOfKey := LowerCase(StartOfKey);
begin
T := FItemIndex;
if KeyCaseSensitive then
begin
for n := T + 2 to Count do
begin
k := Index.Strings[n - 1];
if Copy(k, 1, Length(StartOfKey)) = StartOfKey then
begin
Result := true;
Break;
end;
end;
end
else
begin
for n := T + 2 to Count do
begin
k := Index.Strings[n - 1];
if LowerCase(Copy(k, 1, Length(StartOfKey))) = StartOfKey then
begin
Result := true;
Break;
end;
end;
end;
if Result and (T <> n - 1) then
begin
ItemIndex := n - 1;
Found;
end;
end;
end;
procedure TQDB.PartialMatchInit;
begin
FItemIndex := -1;
end;
function TQDB.PatternMatch(Pattern: TKey): boolean;
{ brute force search for a pattern -- not quick! }
var
n: TItemIndex;
k: TKey;
T: TItemIndex;
begin
T := FItemIndex;
Result := false;
for n := T + 1 to Count do
begin
k := GetKey(n - 1);
if Matches(k, Pattern) then
begin
Result := true;
Break;
end;
end;
if Result and (T <> n - 1) then
begin
ItemIndex := n - 1;
Found;
end;
end;
procedure TQDB.PatternMatchInit;
begin
FItemIndex := -1;
end;
procedure TQDB.DoPost;
begin
if not AutoEdit then
FEditing := false;
FInserting := false;
UpdateNavigator;
end;
procedure TQDB.Post;
begin
if Assigned(FBeforePost) then
FBeforePost(Self);
DoPost;
if Assigned(FAfterPost) then
FAfterPost(Self);
end;
procedure TQDB.PrepareToAdd(numberofitems: longint);
{ Usually the memory allocated for the index grows whenever needed, }
{ which can be time-consuming with all the moving of memory blocks, }
{ etc. Instead PrepareToAdd allocates all the memory required in }
{ go which is much quicker and reduces memory fragmentation. }
begin
Index.SetCapacity(Index.Count + numberofitems);
end;
procedure TQDB.PrevItem;
var
This: TItemIndex;
begin
FBoF := false;
FEoF := false;
This := FItemIndex;
if Filtered then
begin
while (This - 1 >= 0) and not Matches(GetKey(This - 1), FFilter) do
begin
dec(This);
end;
end;
dec(This);
if This < 0 then
begin
FBoF := true;
FEoF := false;
end
else
begin
ItemIndex := This;
end;
{ if there is one, tell the navigator we've moved }
UpdateNavigator;
end;
procedure TQDB.Recover(NewFileName: string);
{ Checks to see if there are temp working files still around }
{ that are not in use. }
var
Remnant: string;
begin
if FFileName <> '' then
exit; { only use if nothing open }
Remnant := FileToRecover;
if Remnant = '' then
exit; { and there is an orphan }
{ prepare to splice the working files together }
QDBFileName := Remnant;
QIXFileName := ExtractFilePath(Remnant) + 'QIX' + Copy(ExtractFileName(
Remnant), 4, MAX_PATH);
FFileAge := 0;
FFileName := NewFileName;
Splice;
FFileName := '';
SysUtils.DeleteFile(QDBFileName);
SysUtils.DeleteFile(QIXFileName);
{ then open it up again }
FileName := NewFileName;
end;
procedure TQDB.Refresh;
begin
end;
procedure TQDB.Save;
{ Save commits the in-memory index to the working file }
var
T: TIndex;
i, n: longint;
NumItems: longint;
TmpFile: TTempBFStream;
begin
if not FReady then
raise EQDBNoFile.Create(SNoFile);
if ReadOnly or not IsDirty then
exit;
MonitorSetup(Index.Count, prSave);
TmpFile := TTempBFStream.Create(QIXFileName);
try { protect file tmpfile }
NumItems := Index.Count + Admin.Count;
TmpFile.PutLongint(NumItems);
i := Admin.Count;
while i > 0 do
begin
TmpFile.PutKey(Admin.Strings[i - 1]);
T := TIndex(Admin.Objects[i - 1]);
TmpFile.PutLongint(T.Pos);
TmpFile.PutLongint(T.Len);
TmpFile.PutLongint(longint(T.Ext));
dec(i);
end;
i := Index.Count;
n := 0;
while i > 0 do
begin
MonitorUpdate(n);
inc(n);
TmpFile.PutKey(Index.Strings[i - 1]);
T := TIndex(Index.Objects[i - 1]);
TmpFile.PutLongint(T.Pos);
TmpFile.PutLongint(T.Len);
TmpFile.PutLongint(longint(T.Ext));
dec(i);
end;
finally
TmpFile.Destroy;
end;
QDBFile.Free;
IsDirty := false;
QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive
);
MonitorZero;
end;
procedure TQDB.SaveAs(NewName: string);
{ SaveAs first commits the in-memory index to the working file }
{ before writing a copy of index and items to a new QDB file }
begin
Save;
QDBFile.Free;
FFileName := ExpandFileName(NewName);
if ExtractFileExt(FFileName) = '' then
FFileName := ChangeFileExt(FFileName, '.QDB');
FFileAge := 0;
Splice;
FFileAge := FileAge(FFileName);
QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive
);
IsDirty := false;
end;
procedure TQDB.SaveIndex;
{ Saving the index is just as messy as loading it ... }
var
T: TIndex;
n: TItemIndex;
NumItems: TItemIndex;
TmpFile: TTempBFStream;
begin
MonitorSetup(Index.Count, prFinish);
if ReadOnly or not SaveOnClose then
begin
while Admin.Count > 0 do
begin
TIndex(Admin.Objects[Admin.Count - 1]).Free;
Admin.Delete(Admin.Count - 1);
end;
n := 0;
while Index.Count > 0 do
begin
MonitorUpdate(n);
inc(n);
TIndex(Index.Objects[Index.Count - 1]).Free;
Index.Delete(Index.Count - 1);
end;
end
else
begin
TmpFile := TTempBFStream.Create(QIXFileName);
try { protect file tmpfile }
NumItems := Index.Count + Admin.Count;
TmpFile.PutLongint(NumItems);
while Admin.Count > 0 do
begin
TmpFile.PutKey(Admin.Strings[Admin.Count - 1]);
T := TIndex(Admin.Objects[Admin.Count - 1]);
TmpFile.PutLongint(T.Pos);
TmpFile.PutLongint(T.Len);
TmpFile.PutLongint(longint(T.Ext));
T.Free;
Admin.Delete(Admin.Count - 1);
end;
n := 0;
while Index.Count > 0 do
begin
MonitorUpdate(n);
inc(n);
TmpFile.PutKey(Index.Strings[Index.Count - 1]);
T := TIndex(Index.Objects[Index.Count - 1]);
TmpFile.PutLongint(T.Pos);
TmpFile.PutLongint(T.Len);
TmpFile.PutLongint(longint(T.Ext));
T.Free;
Index.Delete(Index.Count - 1);
end;
finally
TmpFile.Destroy;
end;
end;
Cache.Flush;
QDBFile.Free;
IsDirty := false;
Splice;
SysUtils.DeleteFile(QDBFileName);
SysUtils.DeleteFile(QIXFileName);
MonitorZero;
end;
procedure TQDB.SetCacheFrequency(Value: integer);
{ resets cache-hit counting -- notice that whatever the }
{ value the result is the same. }
begin
Cache.SetFrequency(Value);
end;
procedure TQDB.SetCacheSize(Value: longint);
{ sets the upper limit on the cache's size -- flushing }
{ the cache in the process }
begin
Cache.SetSize(Value);
end;
procedure TQDB.SetDummyAuthor(Value: string40);
begin
{ does nothing but make a read-only property visible in Object Inspector }
end;
procedure TQDB.SetDummyVersion(Value: string05);
begin
{ does nothing but make a read-only property visible in Object Inspector }
end;
procedure TQDB.SetFileName(Value: string);
{ setting the FileName property loads and unloads QDB files }
begin
try
if FExpandedFileNames and (Value <> '') then
Value := ExpandFileName(Value);
if ExpandFileName(FFileName) <> ExpandFileName(Value) then
begin
if Live and (FFileName <> '') then
CloseQDB;
if Value = '' then
FFileName := ''
else
begin
FFileName := Value;
if ExtractFileExt(FFileName) = '' then
FFileName := ChangeFileExt(FFileName, '.QDB');
end;
if Live and (FFileName <> '') then
OpenQDB;
FileAssigned; { trigger event }
end;
except
FFileName := ''; { if anything goes wrong ... }
raise;
end;
end;
procedure TQDB.SetFilter(Value: TKey);
{ sets a filter to restrict navigation }
begin
Filtered := (Value <> ''); { we use this flag a lot elsewhere }
FFilter := Value;
end;
procedure TQDB.SetGrepMatch(Value: boolean);
begin
if Value then
Matches := GrepMatches
else
Matches := SimpleMatches;
FGrepMatch := Value;
end;
procedure TQDB.SetItemIndex(Value: TItemIndex);
{ does all the work of moving about the index }
begin
if Index.Count = 0 then
NoData { trigger event if empty }
else
if (Value < 0) or (Value > Index.Count - 1) then
OutOfBounds { trigger event if illegal move }
else
begin
FItemIndex := Value; { new index position }
FKey := GetKey(FItemIndex); { updated properties }
if (FUpdating = 0) then
Navigate; { trigger event when index pos changes }
end
end;
procedure TQDB.SetKeyCase(Value: boolean);
{ sets the case-sensitivity of the index }
begin
{ if value is false the index is checked and any duplicates purged }
Index.CaseSensitive := Value;
{the case-sensitivity is stored in the QDB file }
AdminAsBoolean['QDBCaseSensitive'] := Value;
end;
procedure TQDB.SetLinkToNavigator(Value: TQDBNavigator);
{ note which navigator (if any) is using this QDB }
begin
FQDBNavigator := Value;
UpdateNavigator;
end;
procedure TQDB.SetMatchChars(Front: char; back: char);
{ defines the wild card chars for the simple pattern matching }
begin
if Front <> #0 then
FFrontWild := Front;
if back <> #0 then
FBackWild := back;
end;
procedure TQDB.SetReadOnly(Value: boolean);
{ sets the ReadOnly state of the QDB and notifies the navigator }
begin
if Value <> FReadOnly then
begin
FReadOnly := Value;
UpdateNavigator;
end;
end;
procedure TQDB.SetReady(Value: boolean);
{ sets the Ready state of the QDB and notifies the navigator }
begin
if Value <> FReady then
begin
FReady := Value;
UpdateNavigator;
end;
end;
procedure TQDB.SetStr(n: TItemIndex; const Value: string);
{ sets the item n as a string }
begin
ItemIndex := n;
SetThisStr(Value);
end;
procedure TQDB.SetStrByKey(Key: TKey; const Value: string);
{ look up key and add or change item as string }
var
m: TMemoryStream;
begin
if ExactMatch(Key) then
SetThisStr(Value)
else
begin
m := TMemoryStream.Create;
try
m.Write(Value[1], Length(Value));
m.Seek(0, 0);
Add(m, Key);
finally
m.Free;
end;
end;
end;
procedure TQDB.SetThisStr(const Value: string);
{ sets -- adds or changes -- current item as string }
var
m: TMemoryStream;
begin
m := TMemoryStream.Create;
try
m.Write(Value[1], Length(Value));
m.Seek(0, 0);
Change(m);
finally
m.Free;
end;
end;
procedure TQDB.SignalProgress(Percent: TPercentage; Kind: TProgressOrigin);
begin
if Assigned(FProgressUpdate) then
FProgressUpdate(Self, Percent, Kind);
end;
function TQDB.SimpleMatches(Key: TKey; Pattern: TKey): boolean;
{ used by the filtering system -- if you wanted a more }
{ sophisticated match you would override this function. }
var
IsMatch: boolean;
LeftPos,
RightPos,
PatternPosF,
PatternPosL,
LenP,
LastPos: Byte;
begin
if not KeyCaseSensitive then
begin
Key := UpperCase(Key);
Pattern := UpperCase(Pattern);
end;
LenP := Length(Pattern);
LeftPos := Pos(FFrontWild, Pattern);
if LeftPos = 1 then
begin
dec(LenP);
Pattern := Copy(Pattern, 2, LenP);
end;
RightPos := Pos(FBackWild, Pattern);
if (RightPos = LenP) and (LenP <> 0) then
begin
dec(LenP);
Pattern := Copy(Pattern, 1, LenP);
end
else
begin
if LenP = 0 then { Clester Keaton }
begin
Result := true;
exit;
end;
end;
if LenP = 0 then
begin
PatternPosF := 1;
PatternPosL := 1;
end
else
begin
PatternPosF := Pos(Pattern, Key);
PatternPosL := PatternPosF;
if PatternPosL <> 0 then
begin
LastPos := PatternPosL;
while LastPos <> 0 do
begin
LastPos := Pos(Pattern, Copy(Key, PatternPosL + 1, Length(Key)));
PatternPosL := PatternPosL + LastPos;
end;
end;
end;
IsMatch := PatternPosF <> 0;
if IsMatch and (LeftPos = 0) and (PatternPosF <> 1) then
begin
IsMatch := false;
end;
if IsMatch and (RightPos = 0) and (PatternPosL <> (Length(Key) - LenP + 1
)) then
begin
IsMatch := false;
end;
Result := IsMatch;
end;
procedure TQDB.Splice;
{ Splices the two working files back into a single QDB file, }
{ takingcare not to overwrite the original if it has been used }
{ by another program. }
const
Sig1: array[0..3] of char = ('Q', 'D', 'B', #0);
Sig2: array[0..3] of char = ('Q', 'I', 'X', #0);
Sig3: array[0..3] of char = ('Q', 'P', 'W', #0);
var
n: longint;
TmpFileName: string;
f, f1, f2: TFileHandle;
Buffer: pointer;
sz1, sz2: longint;
BytesRead: TFilePos;
BufLen: longint;
CanOverWrite: boolean;
PassHash: THash;
WriteHash: THash;
Encrypt: boolean;
begin
if ReadOnly then
exit;
{ check if the file has been used since we opened it -- }
{ if FFileAge = 0 it means we are doing a SaveAs... }
if (FFileAge <> 0) and (FileAge(FFileName) <> FFileAge) then
begin
{ it has so we need to ask if we can save our stuff over it }
CanOverWrite := FForceOverwrite; { be conservative }
AboutToOverWrite(CanOverWrite);
if not CanOverWrite then
repeat
{ since we can't overwrite the original we have }
{ to look for a unique derivative of the filename }
FFileName := ExtractFilePath(FFileName) + '1.' + ExtractFileName(
FFileName);
until not FileExists(FileName);
{ then we can go ahead as normal }
end;
Encrypt := false;
TmpFileName := TempFileName('QDD');
f := FileCreate(TmpFileName);
if f < 0 then
FileError(f, '');
try { protect file f }
FileWrite(f, Sig1, SizeOf(Sig1));
if Password <> '' then
begin
FileWrite(f, Sig3, SizeOf(Sig3));
WriteHash := Hash(Password);
PassHash := Hash(WriteHash);
FileWrite(f, PassHash, SizeOf(THash));
Encrypt := true;
end;
f1 := FileOpen(QDBFileName, fmOpenRead or fmShareExclusive);
if f1 < 0 then
FileError(f1, '');
try { protect file f1 }
sz1 := FileSeek(f1, 0, 2);
FileWrite(f, sz1, SizeOf(sz1));
FileSeek(f1, 0, 0);
BufLen := GetBuffer(Buffer, sz1);
try { protect memory buffer }
for n := 1 to (sz1 div BufLen) do
begin
BytesRead := FileRead(f1, Buffer^, BufLen);
if Encrypt then
Shroud(Buffer^, BytesRead, WriteHash);
FileWrite(f, Buffer^, BytesRead);
end;
BytesRead := FileRead(f1, Buffer^, sz1 mod BufLen);
if Encrypt then
Shroud(Buffer^, BytesRead, WriteHash);
FileWrite(f, Buffer^, BytesRead);
finally
FreeMem(Buffer, BufLen);
end;
finally
FileClose(f1);
end;
FileWrite(f, Sig2, SizeOf(Sig2));
f2 := FileOpen(QIXFileName, fmOpenRead or fmShareExclusive);
if f2 < 0 then
FileError(f2, '');
try { protect file f2 }
sz2 := FileSeek(f2, 0, 2);
FileWrite(f, sz2, SizeOf(sz2));
FileSeek(f2, 0, 0);
BufLen := GetBuffer(Buffer, sz2);
try { protect memory buffer }
for n := 1 to (sz2 div BufLen) do
begin
BytesRead := FileRead(f2, Buffer^, BufLen);
if Encrypt then
Shroud(Buffer^, BytesRead, WriteHash);
FileWrite(f, Buffer^, BytesRead);
end;
BytesRead := FileRead(f2, Buffer^, sz2 mod BufLen);
if Encrypt then
Shroud(Buffer^, BytesRead, WriteHash);
FileWrite(f, Buffer^, BytesRead);
finally
FreeMem(Buffer, BufLen);
end;
finally
FileClose(f2);
end;
finally
FileClose(f);
RenameOrMoveFile(TmpFileName, FFileName);
FFileAge := FileAge(FFileName);
end;
end;
procedure TQDB.Split;
{ This seems like a good place to document the QDB file format ... }
{ }
{ 4 bytes -- 'Q','D','B',#0 }
{(24 bytes -- optional password block present if file is encrypted }
{ 'Q','P','W',#0 indicates encryption }
{ 20 bytes of encrypted password ) }
{ 4 bytes -- longint(n1) = size of data block }
{ n1 bytes of data }
{ 4 bytes -- 'Q','I','X',#0 }
{ 4 bytes -- longint(n2) = size of index block }
{ Split takes a QDB file and, testing it for integrity, }
{ splits it into two working files, one the item data, }
{ the other the index data. These are the files that the }
{ QDB component uses internally. The original QDB file }
{ doesn't get reconstituted until the Splice method. }
var
n: longint;
f, f1, f2: TFileHandle;
Buffer: pointer;
Sig: array[0..3] of char;
sz1, sz2: longint;
BytesRead: TFilePos;
BufLen: longint;
PassHash: THash;
ReadHash: THash;
ReadHash2: THash;
Decrypt: boolean;
begin
Decrypt := false;
{ we get the age of the file when it was opened }
FFileAge := FileAge(FFileName);
if (faReadOnly and FileGetAttr(FFileName)) <> 0 then
ReadOnly := true;
f := FileOpen(FFileName, fmOpenRead);
if f < 0 then
FileError(f, '');
try { protect file f }
FileRead(f, Sig, SizeOf(Sig));
if StrPas(Sig) <> 'QDB' then
FileError(-1, SCorrupt);
BytesRead := FileRead(f, Sig, SizeOf(Sig));
if StrPas(Sig) = 'QPW' then
begin
{ process password }
FileRead(f, PassHash, SizeOf(THash));
if Password = '' then
DemandPassword;
ReadHash := Hash(Password);
ReadHash2 := Hash(ReadHash);
if Hash(ReadHash) = PassHash then
begin
{ we have a match }
Decrypt := true;
end
else
begin
{ file demands a password and we can't deliver }
raise EQDBInvalidPW.Create(SBadPassword);
end;
end
else
begin
{ rewind and continue }
FileSeek(f, -BytesRead, 1)
end;
FileRead(f, sz1, SizeOf(sz1));
BufLen := GetBuffer(Buffer, sz1);
try { protect memory buffer }
QDBFileName := TempFileName('QDB');
f1 := FileCreate(QDBFileName);
if f1 < 0 then
FileError(f1, '');
try { protect file f1 }
for n := 1 to (sz1 div BufLen) do
begin
BytesRead := FileRead(f, Buffer^, BufLen);
if Decrypt then
UnShroud(Buffer^, BytesRead, ReadHash);
FileWrite(f1, Buffer^, BytesRead);
end;
BytesRead := FileRead(f, Buffer^, sz1 mod BufLen);
if Decrypt then
UnShroud(Buffer^, BytesRead, ReadHash);
FileWrite(f1, Buffer^, BytesRead);
finally
FileClose(f1);
end;
finally
FreeMem(Buffer, BufLen);
end;
FileRead(f, Sig, SizeOf(Sig));
if StrPas(Sig) <> 'QIX' then
FileError(-1, SCorrupt);
FileRead(f, sz2, SizeOf(sz2));
BufLen := GetBuffer(Buffer, sz2);
try { protect memory buffer }
{ use same 'random' name as for QDBFileName }
QIXFileName := ExtractFilePath(QDBFileName) + 'QIX' + Copy(
ExtractFileName(QDBFileName), 4, MAX_PATH);
f2 := FileCreate(QIXFileName);
if f2 < 0 then
FileError(f2, '');
try { protect file f2 }
for n := 1 to (sz2 div BufLen) do
begin
BytesRead := FileRead(f, Buffer^, BufLen);
if Decrypt then
UnShroud(Buffer^, BytesRead, ReadHash);
FileWrite(f2, Buffer^, BytesRead);
end;
BytesRead := FileRead(f, Buffer^, sz2 mod BufLen);
if Decrypt then
UnShroud(Buffer^, BytesRead, ReadHash);
FileWrite(f2, Buffer^, BytesRead);
finally
FileClose(f2);
end;
finally
FreeMem(Buffer, BufLen);
end;
finally
FileClose(f);
end;
end;
procedure TQDB.UpdateNavigator;
{ recalcs BoF and EoF and then prompts the navigator (if there is }
{ one assigned) to update its buttons }
var
This: TItemIndex;
begin
if Filtered then
begin
if AggressiveUpdate then
begin
This := FItemIndex - 1;
while (This >= 0) and not Matches(GetKey(This), FFilter) do
begin
dec(This);
end;
if This < 0 then
begin
FBoF := true;
end;
This := FItemIndex + 1;
while (This < FCount) and not Matches(GetKey(This), FFilter) do
begin
inc(This);
end;
if This >= FCount then
begin
FEoF := true;
end;
end;
end
else
begin
FBoF := (FCount > 0) and (FItemIndex = 0);
FEoF := (FCount > 0) and (FItemIndex + 1 = FCount);
end;
if Assigned(FQDBNavigator) then
begin
FQDBNavigator.QDBStateChanged;
end;
end;
{ Basically -- the TQDBNavigator component from DBCtrls but with }
{ all the BDE stuff torn out and replaced with QDB stuff instead. }
{ Portions of this code are Copyright Borland. }
{ Copyright (c) 1995-1997 Borland International. All Rights Reserved. }
{ TQDBNavigator }
const
InitRepeatPause = 400; { pause before repeat timer (ms) }
RepeatPause = 100; { pause before hint window displays (ms)}
SpaceSize = 5; { size of space between special buttons }
const
SFirstRecord = 119;
SPrevRecord = 120;
SNextRecord = 121;
SLastRecord = 122;
SInsertRecord = 123;
SDeleteRecord = 124;
SEditRecord = 125;
SPostEdit = 126;
SCancelEdit = 127;
SRefreshRecord = 128;
BtnTypeName: array[TNavigateBtn] of pchar = ('FIRST', 'PREV', 'NEXT',
'LAST', 'INSERT', 'DELETE', 'EDIT', 'POST', 'CANCEL', 'REFRESH');
BtnHintId: array[TNavigateBtn] of word = (SFirstRecord, SPrevRecord,
SNextRecord, SLastRecord, SInsertRecord, SDeleteRecord, SEditRecord,
SPostEdit, SCancelEdit, SRefreshRecord);
constructor TQDBNavigator.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle - [csAcceptsControls, csSetCaption] + [csOpaque
];
{$IFDEF VER100}
if not NewStyleControls then
ControlStyle := ControlStyle + [csFramed];
{$ELSE}
ControlStyle := ControlStyle + [csFramed];
{$ENDIF}
FVisibleButtons := [nbFirst, nbPrev, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost,
nbCancel, nbRefresh];
FHints := TStringList.Create;
TStringList(FHints).OnChange := HintsChanged;
InitButtons;
BevelOuter := bvNone;
BevelInner := bvNone;
Width := 241;
Height := 25;
ButtonHeight := 0;
ButtonWidth := 0;
FocusedButton := nbFirst;
end;
destructor TQDBNavigator.Destroy;
var
i: TNavigateBtn;
begin
if FQDB <> nil then {BS}
FQDB.SetLinkToNavigator(nil); {BS}
FHints.Free;
for i := Low(Buttons) to High(Buttons) do
Buttons[i].Free;
inherited Destroy;
end;
procedure TQDBNavigator._Click(Sender: TObject);
begin
BtnClick(TNavButton(Sender).Index);
end;
procedure TQDBNavigator.AdjustSize(var W: integer; var H: integer);
var
Count: integer;
MinW: integer;
MinH: integer;
i: TNavigateBtn;
Space, Temp, Remain: integer;
X: integer;
Y: integer;
begin
if (csLoading in ComponentState) then
exit;
if Buttons[nbFirst] = nil then
exit;
Count := 0;
for i := Low(Buttons) to High(Buttons) do
begin
if Buttons[i].Visible then
begin
inc(Count);
end;
end;
if Count = 0 then
inc(Count);
{horizontal}
if ((FOrientation = noAuto) and (W >= H)) or (FOrientation = noHoriz) then
begin
MinW := Count * (MinBtnSize.X);
if W < MinW then
W := MinW;
if H < MinBtnSize.Y then
H := MinBtnSize.Y;
ButtonWidth := ((W) div Count);
Temp := Count * (ButtonWidth);
if Align = alNone then
W := Temp;
X := 0;
Remain := W - Temp;
Temp := Count div 2;
for i := Low(Buttons) to High(Buttons) do
begin
if Buttons[i].Visible then
begin
Space := 0;
if Remain <> 0 then
begin
dec(Temp, Remain);
if Temp < 0 then
begin
inc(Temp, Count);
Space := 1;
end;
end;
Buttons[i].SetBounds(X, 0, ButtonWidth + Space, Height);
inc(X, ButtonWidth + Space);
end
else
Buttons[i].SetBounds(Width, 0, ButtonWidth, Height);
end;
end {vertical: ((FDirection=dirAuto) and (W < H)) or (FDirection=dirVertical)}
else
begin
MinH := Count * (MinBtnSize.Y);
if H < MinH then
H := MinH;
if W < MinBtnSize.X then
W := MinBtnSize.X;
ButtonHeight := (H div Count);
Temp := Count * (ButtonHeight);
if Align = alNone then
H := Temp;
Y := 0;
Remain := H - Temp;
Temp := Count div 2;
for i := Low(Buttons) to High(Buttons) do
begin
if Buttons[i].Visible then
begin
Space := 0;
if Remain <> 0 then
begin
dec(Temp, Remain);
if Temp < 0 then
begin
inc(Temp, Count);
Space := 1;
end;
end;
Buttons[i].SetBounds(0, Y, Width, ButtonHeight + Space);
inc(Y, ButtonHeight + Space);
end
else
Buttons[i].SetBounds(0, Height, ButtonHeight, Width);
end;
end;
end;
procedure TQDBNavigator.BtnClick(Index: TNavigateBtn);
begin
if (FQDB <> nil) then
begin
if not (csDesigning in ComponentState) and Assigned(FBeforeAction) then
FBeforeAction(Self, Index);
case Index of
nbPrev:
Prev;
nbNext:
Next;
nbFirst:
First;
nbLast:
Last;
nbInsert:
Insert;
nbEdit:
Edit;
nbCancel:
Cancel;
nbPost:
Post;
nbRefresh:
Refresh;
nbDelete:
Delete;
end;
end;
if not (csDesigning in ComponentState) and Assigned(FOnNavClick) then
FOnNavClick(Self, Index);
end;
procedure TQDBNavigator.BtnMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: integer);
var
OldFocus: TNavigateBtn;
begin
OldFocus := FocusedButton;
FocusedButton := TNavButton(Sender).Index;
if TabStop and (GetFocus <> Handle) and CanFocus then
begin
SetFocus;
if (GetFocus <> Handle) then
exit;
end
else
if TabStop and (GetFocus = Handle) and (OldFocus <> FocusedButton) then
begin
Buttons[OldFocus].Invalidate;
Buttons[FocusedButton].Invalidate;
end;
end;
procedure TQDBNavigator.Cancel;
begin
if Assigned(FQDB) then
if Assigned(FOnCancel) then
FOnCancel(Self, FQDB)
else
FQDB.Cancel;
end;
procedure TQDBNavigator.Delete;
begin
if Assigned(FQDB) then
if Assigned(FOnDelete) then
FOnDelete(Self, FQDB)
else
FQDB.Delete;
end;
procedure TQDBNavigator.Edit;
begin
if Assigned(FQDB) then
if Assigned(FOnEdit) then
FOnEdit(Self, FQDB)
else
FQDB.Edit;
end;
procedure TQDBNavigator.First;
begin
if Assigned(FQDB) then
if Assigned(FOnFirst) then
FOnFirst(Self, FQDB)
else
FQDB.FirstItem;
end;
function TQDBNavigator.GetEnabled: boolean;
begin
Result := inherited Enabled;
end;
function TQDBNavigator.GetGlyph(Btn: TNavigateBtn): Graphics.TBitmap;
begin
Result := Buttons[Btn].Glyph;
end;
procedure TQDBNavigator.HintsChanged(Sender: TObject);
begin
InitHints;
end;
procedure TQDBNavigator.InitButtons;
var
i: TNavigateBtn;
Btn: TNavButton;
X: integer;
ResName: array[0..40] of char;
begin
MinBtnSize := Point(20, 18);
X := 0;
for i := Low(Buttons) to High(Buttons) do
begin
Btn := TNavButton.Create(Self);
{$IFDEF VER100}
Btn.Flat := Flat;
{$ENDIF}
Btn.Index := i;
Btn.Visible := i in FVisibleButtons;
Btn.Enabled := true;
Btn.SetBounds(X, 0, MinBtnSize.X, MinBtnSize.Y);
StrFmt(ResName, 'qdb_%s', [BtnTypeName[i]]);
Btn.Glyph.Handle := LoadBitmap(HInstance, ResName);
Btn.NumGlyphs := 2;
Btn.Enabled := false;
Btn.Enabled := true;
Btn.OnClick := _Click;
Btn.OnMouseDown := BtnMouseDown;
Btn.Parent := Self;
Buttons[i] := Btn;
X := X + MinBtnSize.X;
end;
InitHints;
Buttons[nbPrev].NavStyle := Buttons[nbPrev].NavStyle + [nsAllowTimer];
Buttons[nbNext].NavStyle := Buttons[nbNext].NavStyle + [nsAllowTimer];
end;
procedure TQDBNavigator.InitHints;
var
i: integer;
j: TNavigateBtn;
begin
for j := Low(Buttons) to High(Buttons) do
Buttons[j].Hint := LoadStr(BtnHintId[j]);
j := Low(Buttons);
for i := 0 to (FHints.Count - 1) do
begin
if FHints.Strings[i] <> '' then
Buttons[j].Hint := FHints.Strings[i];
if j = High(Buttons) then
exit;
inc(j);
end;
end;
procedure TQDBNavigator.Insert;
begin
if Assigned(FQDB) then
if Assigned(FOnInsert) then
FOnInsert(Self, FQDB)
else
FQDB.Insert;
end;
procedure TQDBNavigator.KeyDown(var Key: word; Shift: TShiftState);
var
NewFocus: TNavigateBtn;
OldFocus: TNavigateBtn;
begin
OldFocus := FocusedButton;
case Key of
VK_RIGHT:
begin
NewFocus := FocusedButton;
repeat
if NewFocus < High(Buttons) then
NewFocus := succ(NewFocus);
until (NewFocus = High(Buttons)) or (Buttons[NewFocus].Visible);
if NewFocus <> FocusedButton then
begin
FocusedButton := NewFocus;
Buttons[OldFocus].Invalidate;
Buttons[FocusedButton].Invalidate;
end;
end;
VK_LEFT:
begin
NewFocus := FocusedButton;
repeat
if NewFocus > Low(Buttons) then
NewFocus := pred(NewFocus);
until (NewFocus = Low(Buttons)) or (Buttons[NewFocus].Visible);
if NewFocus <> FocusedButton then
begin
FocusedButton := NewFocus;
Buttons[OldFocus].Invalidate;
Buttons[FocusedButton].Invalidate;
end;
end;
VK_SPACE:
begin
if Buttons[FocusedButton].Enabled then
Buttons[FocusedButton].Click;
end;
end;
end;
procedure TQDBNavigator.Last;
begin
if Assigned(FQDB) then
if Assigned(FOnLast) then
FOnLast(Self, FQDB)
else
FQDB.LastItem;
end;
procedure TQDBNavigator.Loaded;
var
W, H: integer;
begin
inherited Loaded;
W := Width;
H := Height;
AdjustSize(W, H);
if (W <> Width) or (H <> Height) then
inherited SetBounds(Left, Top, W, H);
InitHints;
end;
procedure TQDBNavigator.Next;
begin
if Assigned(FQDB) then
if Assigned(FOnNext) then
FOnNext(Self, FQDB)
else
FQDB.NextItem;
end;
procedure TQDBNavigator.Notification(AComponent: TComponent; Operation:
TOperation);
{ if the link to a data file is broken we must respond }
begin
inherited Notification(AComponent, Operation);
if (FQDB <> nil) and
(AComponent = FQDB) and
(Operation = opRemove) then
FQDB := nil;
end;
procedure TQDBNavigator.Post;
begin
if Assigned(FQDB) then
if Assigned(FOnPost) then
FOnPost(Self, FQDB)
else
FQDB.Post;
end;
procedure TQDBNavigator.Prev;
begin
if Assigned(FQDB) then
if Assigned(FOnPrev) then
FOnPrev(Self, FQDB)
else
FQDB.PrevItem;
end;
procedure TQDBNavigator.QDBStateChanged;
{ update the buttons to reflect the state of the QDB }
var
Btn: TNavigateBtn;
begin
if not Assigned(FQDB) then
exit;
with FQDB do
if not Ready then
for Btn := Low(Buttons) to High(Buttons) do
Buttons[Btn].Enabled := false
else
begin
Buttons[nbFirst].Enabled := not BoF;
Buttons[nbPrev].Enabled := not BoF;
Buttons[nbNext].Enabled := not EoF;
Buttons[nbLast].Enabled := not EoF;
Buttons[nbInsert].Enabled := not (ReadOnly or FInserting);
Buttons[nbDelete].Enabled := not (ReadOnly or (Count < 1));
Buttons[nbEdit].Enabled := not (ReadOnly or FEditing or FInserting or (Count < 1));
Buttons[nbPost].Enabled := FEditing or FInserting;
Buttons[nbCancel].Enabled := FEditing or FInserting;
Buttons[nbRefresh].Enabled := true;
end;
end;
procedure TQDBNavigator.Refresh;
begin
if Assigned(FQDB) then
if Assigned(FOnRefresh) then
FOnRefresh(Self, FQDB)
else
FQDB.Refresh;
end;
procedure TQDBNavigator.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
var
W, H: integer;
begin
W := AWidth;
H := AHeight;
if not HandleAllocated then
AdjustSize(W, H);
inherited SetBounds(ALeft, ATop, W, H);
end;
procedure TQDBNavigator.SetEnabled(Value: boolean);
{ en/disable the buttons as well }
var
Btn: TNavigateBtn;
begin
if Value and not (csDesigning in ComponentState) then
QDBStateChanged
else
for Btn := Low(Buttons) to High(Buttons) do
Buttons[Btn].Enabled := Value;
inherited Enabled := Value;
end;
procedure TQDBNavigator.SetFlat(Value: boolean);
var
i: TNavigateBtn;
begin
{$IFDEF VER100}
if FFlat <> Value then
begin
FFlat := Value;
for i := Low(Buttons) to High(Buttons) do
Buttons[i].Flat := Value;
end;
{$ELSE}
FFlat := false;
{$ENDIF}
end;
procedure TQDBNavigator.SetGlyph(Btn: TNavigateBtn; Value: Graphics.TBitmap
);
begin
Buttons[Btn].Glyph := Value;
end;
procedure TQDBNavigator.SetHints(Value: TStrings);
begin
FHints.Assign(Value);
InitHints;
end;
procedure TQDBNavigator.SetOrientation(Value: TNavOrientation);
var
W, H: integer;
begin
W := Width;
H := Height;
if ((((FOrientation = noAuto) and (W >= H)) or (FOrientation = noHoriz
)) and (Value = noVert)) or
((((FOrientation = noAuto) and (W < H)) or (FOrientation = noVert)
) and (Value = noHoriz)) then
begin
W := Height;
H := Width;
end;
FOrientation := Value;
AdjustSize(W, H);
if (W <> Width) or (H <> Height) then
inherited SetBounds(Left, Top, W, H);
Invalidate;
end;
procedure TQDBNavigator.SetQDB(Value: TQDB);
begin
if Value <> FQDB then
begin
if FQDB <> nil then {BS}
FQDB.SetLinkToNavigator(nil); {BS}
FQDB := Value;
if FQDB <> nil then {BS}
FQDB.SetLinkToNavigator(TQDBNavigator(Self));
end;
end;
procedure TQDBNavigator.SetVisible(Value: TButtonSet);
var
i: TNavigateBtn;
W, H: integer;
begin
W := Width;
H := Height;
FVisibleButtons := Value;
for i := Low(Buttons) to High(Buttons) do
Buttons[i].Visible := i in FVisibleButtons;
AdjustSize(W, H);
if (W <> Width) or (H <> Height) then
inherited SetBounds(Left, Top, W, H);
Invalidate;
end;
procedure TQDBNavigator.WMGetDlgCode(var Message: TWMGetDlgCode);
begin
Message.Result := DLGC_WANTARROWS;
end;
procedure TQDBNavigator.WMKillFocus(var Message: TWMKillFocus);
begin
Buttons[FocusedButton].Invalidate;
end;
procedure TQDBNavigator.WMSetFocus(var Message: TWMSetFocus);
begin
Buttons[FocusedButton].Invalidate;
end;
procedure TQDBNavigator.WMSize(var Message: TWMSize);
var
W, H: integer;
begin
inherited;
{ check for minimum size }
W := Width;
H := Height;
AdjustSize(W, H);
if (W <> Width) or (H <> Height) then
inherited SetBounds(Left, Top, W, H);
Message.Result := 0;
end;
{ TNavButton }
destructor TNavButton.Destroy;
begin
if FRepeatTimer <> nil then
FRepeatTimer.Free;
inherited Destroy;
end;
procedure TNavButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: integer);
begin
inherited MouseDown(Button, Shift, X, Y);
if nsAllowTimer in FNavStyle then
begin
if FRepeatTimer = nil then
FRepeatTimer := TTimer.Create(Self);
FRepeatTimer.OnTimer := TimerExpired;
FRepeatTimer.Interval := InitRepeatPause;
FRepeatTimer.Enabled := true;
end;
end;
procedure TNavButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
X, Y: integer);
begin
inherited MouseUp(Button, Shift, X, Y);
if FRepeatTimer <> nil then
FRepeatTimer.Enabled := false;
end;
procedure TNavButton.Paint;
var
R: TRect;
begin
inherited Paint;
if (GetFocus = Parent.Handle) and
(FIndex = TQDBNavigator(Parent).FocusedButton) then
begin
R := Bounds(0, 0, Width, Height);
InflateRect(R, -3, -3);
if FState = bsDown then
OffsetRect(R, 1, 1);
DrawFocusRect(Canvas.Handle, R);
end;
end;
procedure TNavButton.TimerExpired(Sender: TObject);
begin
FRepeatTimer.Interval := RepeatPause;
if (FState = bsDown) and MouseCapture then
begin
try
Click;
except
FRepeatTimer.Enabled := false;
raise;
end;
end;
end;
initialization
QDBTempFileLocation := '';
end.