uses Objects, Drivers, Views, Menus, Dialogs, App, Layout, OODB; { layout and OODB are at the end !!} const DBFileName = 'dbdemo.dat'; MaxLen = 25; CollLimit = $7F; CollDelta = 4; InvPID = 1; cmInfo = 100; cmOpen = 101; cmShut = 102; cmStat = 103; cmCreate = 105; cmGet = 106; cmDelete = 107; cmCommit = 108; cmAbort = 109; type NameString = String [MaxLen]; ModDialData = record NameData : NameString end; TInvCard = record Name : NameString; ID : Word end; PInvCard = ^TInvCard; { ----- TCatCollection ----- } TCatCollection = object (TSortedCollection) procedure FreeItem (Item: Pointer); virtual; function GetItem (var S: TStream): Pointer; virtual; procedure PutItem (var S: TStream; Item: Pointer); virtual; function Compare (Key1, Key2 : Pointer): Integer; virtual; end; PCatCollection = ^TCatCollection; { ----- TDemoApplication class ----- } TDemoApplication = object (TApplication) DB : PBase; DBFile : PDosStream; constructor Init; destructor Done; virtual; procedure InitMenuBar; virtual; procedure InitStatusLine; virtual; procedure HandleEvent (var Event: TEvent); virtual; procedure Idle; virtual; function NameDialog (Title: TTitleStr): PDialog; virtual; procedure About; virtual; procedure OpenDB; virtual; procedure ShutDB; virtual; procedure StatInfo; virtual; procedure CreateMod; virtual; procedure GetMod; virtual; procedure DeleteMod; virtual; procedure Commit; virtual; procedure Rollback; virtual; end; PDemoApplication = ^TDemoApplication; { -- Implementation of TCatCollection -- } procedure TCatCollection.FreeItem (Item: Pointer); begin Dispose (Item) end; { FreeItem } function TCatCollection.GetItem (var S: TStream): Pointer; var Item : PInvCard; begin New (Item); with S do with Item^ do begin Read (Name, SizeOf(Name)); Read (ID, SizeOf(ID)) end; GetItem := Item end; { GetItem } procedure TCatCollection.PutItem (var S: TStream; Item: Pointer); begin with S do with TInvCard(Item^) do begin Write (Name, SizeOf(Name)); Write (ID, SizeOf(ID)) end end; { PutItem } function TCatCollection.Compare (Key1, Key2 : Pointer): Integer; var N1, N2 : NameString; begin N1 := TInvCard(Key1^).Name; N2 := TInvCard(Key2^).Name; if N1 > N2 then Compare := 1 else if N1 < N2 then Compare := -1 else Compare := 0 end; { Compare } { -- End of TCatCollection implementation -- } { ----- TDemoApplication implementation ----- } { ----- Init ----- } constructor TDemoApplication.Init; begin TApplication.Init; DB := nil end; { ----- Done ----- } destructor TDemoApplication.Done; begin if DB <> nil then begin Dispose (DB, Done); Dispose (DBFile, Done) end; TApplication.Done end; { ----- InitMenuBar ----- } procedure TDemoApplication.InitMenuBar; var MenuRect: TRect; begin GetExtent (MenuRect); MenuRect.B.Y := MenuRect.A.Y + 1; MenuBar := New (PMenuBar, Init (MenuRect, NewMenu ( NewItem ( '~I~nfo', '', kbNoKey, cmInfo, hcNoContext, NewSubMenu ( '~D~atabase', hcNoContext, NewMenu ( NewItem ( '~O~pen', 'F3', kbF3, cmOpen, hcNoContext, NewItem ( '~S~hut', 'F4', kbF4, cmShut, hcNoContext, NewItem ( 'S~t~atistics', '', kbNoKey, cmStat, hcNoContext, NewLine ( NewItem ( '~E~xit', 'Alt-X', kbAltX, cmQuit, hcNoContext, nil )))))), NewSubMenu ( '~M~odules', hcNoContext, NewMenu ( NewItem ( '~C~reate', 'F5', kbF5, cmCreate, hcNoContext, NewItem ( '~G~et', 'F6', kbF6, cmGet, hcNoContext, NewItem ( '~D~elete', '', kbNoKey, cmDelete, hcNoContext, nil )))), NewSubMenu ( '~T~ransaction', hcNoContext, NewMenu ( NewItem ( '~C~ommit', '', kbNoKey, cmCommit, hcNoContext, NewItem ( '~R~ollback', '', kbNoKey, cmAbort, hcNoContext, nil ))), nil ))))))) end; { ----- InitStatusLine ----- } procedure TDemoApplication.InitStatusLine; var StatusRect: TRect; begin GetExtent (StatusRect); StatusRect.A.Y := StatusRect.B.Y - 1; StatusLine := New (PStatusLine, Init (StatusRect, NewStatusDef (0, $FFFF, NewStatusKey ('~Alt-X~ - Exit', kbAltX, cmQuit, NewStatusKey ('~F3~ - Open database', kbF3, cmOpen, NewStatusKey ('~F10~ - Menu', kbF10, cmMenu, nil ))), nil ))) end; { ----- HandleEvent ----- } procedure TDemoApplication.HandleEvent (var Event: TEvent); begin TApplication.HandleEvent (Event); with Event do if What = evCommand then begin case Command of cmInfo : About; cmOpen : OpenDB; cmShut : ShutDB; cmStat : StatInfo; cmCreate : CreateMod; cmGet : GetMod; cmDelete : DeleteMod; cmCommit : Commit; cmAbort : Rollback; else Exit end; ClearEvent (Event) end end; { ----- Idle ----- } procedure TDemoApplication.Idle; begin TApplication.Idle; if DB <> nil then DB^.IdlePack end; { ----- NameDialog ----- } function TDemoApplication.NameDialog (Title: TTitleStr): PDialog; var X, Y : Word; R : TRect; Dial : PDialog; Bruce : PView; begin if DB = nil then begin HandleError ( ^C'Open database at first !' ); NameDialog := nil; Exit end; Randomize; X := 2 + Random (50); Y := 2 + Random (12); R.Assign (X,Y,X+28,Y+9); New (Dial, Init (R, Title)); with Dial^ do begin R.Assign (2,6,12,8); Insert (New (PButton, Init (R, '~O~k', cmOK, bfDefault))); R.Assign (14,6,24,8); Insert (New (PButton, Init (R, '~C~ancel', cmCancel, bfNormal))); R.Assign (3,3,25,4); Bruce := New (PInputLine, Init (R, MaxLen)); Insert (Bruce); R.Assign (2,2,20,3); Insert (New (PLabel, Init (R, 'Module name:', Bruce))) end; NameDialog := Dial end; { ----- About ----- } procedure TDemoApplication.About; var R: TRect; begin R.Assign (15,3,65,16); Inform ( R, ^C'This program is intended to demonstrate'^M + ^C'some features of OODBMS'^M + ^C'(object-oriented database management system).'^M + ^C'OODBMS as well as this demo'^M + ^C'is developed independently by Shmatikov V.'^M^M + ^C'Spring 1992', nil ) end; { ----- OpenDB ----- } procedure TDemoApplication.OpenDB; var Dial : PDialog; C : Word; DBIsNew : Boolean; Invent : PCatCollection; begin DBIsNew := False; if DB = nil then begin if Confirm ( ^C'You are to open database.'^M + ^C'Choose Ok to proceed ...' ) = cmCancel then Exit; New (DBFile, Init (DBFileName, stOpen)); if DBFile^.Status <> stOk then begin Dispose (DBFile, Done); New (DBFile, Init (DBFileName, stCreate)); DBIsNew := True; end; New (DB, Init (DBFile)); if DBIsNew then begin New (Invent, Init (CollLimit, CollDelta)); DB^.Put (InvPID, Invent); Inc (DB^.PIDCurrent); Dispose (Invent, Done) end; DB^.Commit end else HandleError ( ^C'Database is in use already !' ) end; { ----- ShutDB ----- } procedure TDemoApplication.ShutDB; var Dial : PDialog; C : Word; begin if DB <> nil then begin if Confirm ( ^C'You are about to close database'^M + ^C'Choose Ok to do it !' ) = cmCancel then Exit; Dispose (DB, Done); DB := nil; Dispose (DBFile, Done); DBFile := nil end else HandleError ( ^C'No database is in use now !' ) end; { ----- StatInfo ----- } procedure TDemoApplication.StatInfo; type InfoRec = record FileName : PString; NumObj, SizeObj, NumHoles, SizeHoles, SizeAnc, TotalSize : Longint end; var R : TRect; DataRec : InfoRec; i : Integer; begin if DB = nil then begin HandleError ( ^C'Open database at first !' ); Exit end; with DB^ do with DataRec do begin FileName^ := DBFileName; NumObj := 0; SizeObj := 0; For i := 2 to DBIndex^.Count - 1 do if (IndRec(DBIndex^.At(i)^).Base = i) and (IndRec(DBIndex^.At(1)^).Base <> i) then begin Inc (NumObj); SizeObj := SizeObj + IndRec(DBIndex^.At(i)^).Size end; NumHoles := HolesIndex^.Count; SizeHoles := 0; For i := 0 to NumHoles-1 do SizeHoles := SizeHoles + IndRec(HolesIndex^.At(i)^).Size; SizeAnc := DBFile^.GetSize - SizeObj - SizeHoles; TotalSize := DBFile^.GetSize end; R.Assign (10,2,70,15); Inform ( R, 'Database file "%s" is in use'^M^M + ' - %d user object(s) hold(s) %d byte(s) in file'^M + ' - %d hole(s) hold(s) %d byte(s) in file'^M + ' - Ancillary information holds %d byte(s)'^M + ' - Total size of database is %d byte(s)', @DataRec ) end; { ----- CreateMod ----- } procedure TDemoApplication.CreateMod; var NewDial : PDialog; C : Word; DialData : ModDialData; Card : PInvCard; Invent : PCatCollection; PID : Word; begin NewDial := NameDialog ('New module'); if NewDial = nil then Exit; C := DeskTop^.ExecView (NewDial); if C <> CmCancel then begin NewDial^.GetData (DialData); if DialData.NameData <> '' then begin Invent := PCatCollection (DB^.Get (InvPID)); New (Card); PID := DB^.Create; Card^.Name := DialData.NameData; Card^.ID := PID; Invent^.Insert (Card); DB^.Put (PID, NewDial); DB^.Destroy (InvPID); DB^.Put (InvPID, Invent); Dispose (Invent, Done) end end; Dispose (NewDial, Done) end; { ----- GetMod ----- } procedure TDemoApplication.GetMod; var Dial, DialFromDB : PDialog; C : Word; DialData : ModDialData; Card : PInvCard; Invent : PCatCollection; Ind : Integer; begin Dial := NameDialog ('Get'); if Dial = nil then Exit; C := DeskTop^.ExecView (Dial); if C <> CmCancel then begin Dial^.GetData (DialData); New (Card); Card^.Name := DialData.NameData; Invent := PCatCollection (DB^.Get (InvPID)); if Invent^.Search (Card, Ind) then begin DialFromDB := PDialog (DB^.Get (TInvCard(Invent^.At(Ind)^).ID)); C := ExecView (DialFromDB); Dispose (DialFromDB, Done) end; Dispose (Invent, Done) end; Dispose (Dial, Done) end; { ----- DeleteMod ----- } procedure TDemoApplication.DeleteMod; var Dial : PDialog; C : Word; DialData : ModDialData; Card : PInvCard; Invent : PCatCollection; Ind : Integer; begin Dial := NameDialog ('Delete'); if Dial = nil then Exit; C := DeskTop^.ExecView (Dial); if C <> CmCancel then begin Dial^.GetData (DialData); New (Card); Card^.Name := DialData.NameData; Invent := PCatCollection (DB^.Get (InvPID)); if Invent^.Search (Card, Ind) then begin DB^.Destroy (TInvCard(Invent^.At(Ind)^).ID); Invent^.AtDelete (Ind); DB^.Destroy (InvPID); DB^.Put (InvPID, Invent) end; Dispose (Invent, Done) end; Dispose (Dial, Done) end; { ----- Commit ----- } procedure TDemoApplication.Commit; var Dial : PDialog; C : Word; begin if DB <> nil then begin if Confirm ( ^C'All changes you''ve made since last Commit '^M + ^C'will be placed into the database forever !' ) = cmCancel then Exit; DB^.Commit end else HandleError ( ^C'No database is in use now !' ) end; { ----- Rollback ----- } procedure TDemoApplication.Rollback; var Dial : PDialog; C : Word; begin if DB <> nil then begin if Confirm ( ^C'You are restoring database to its old state.'^M + ^C'Changes since last Commit will be lost !' ) = cmCancel then Exit; DB^.Abort; end else HandleError ( ^C'No database is in use now !' ) end; procedure RegisterAll; const RCatCollection: TStreamRec = ( ObjType : 10001; VMTLink : Ofs(TypeOf(TCatCollection)^); Load : @TCatCollection.Load; Store : @TCatCollection.Store ); begin RegisterObjects; RegisterViews; RegisterDialogs; RegisterType (RCatCollection) end; { ----- Program body ----- } var DA : TDemoApplication; begin RegisterAll; DA.Init; DA.Run; DA.Done end. { --------------------- LAYOUT.PAS ---------------------- } { CUT } unit Layout; interface uses Objects, MsgBox; procedure HandleError ( Mess: String ); procedure Inform ( R: TRect; Mess: String; Params: Pointer ); function Confirm ( Mess: String ): Word; implementation procedure HandleError ( Mess: String ); var C: Word; begin C := MessageBox ( Mess, nil, mfError + mfOKButton ) end; procedure Inform ( R: TRect; Mess: String; Params: Pointer ); var C: Word; begin C := MessageBoxRect ( R, Mess, Params, mfInformation + mfOKButton ) end; function Confirm ( Mess: String ): Word; var R: TRect; begin R.Assign (10,4,60,12); Confirm := MessageBoxRect ( R, Mess, nil, mfConfirmation + mfOKCancel ) end; end. { --------------------- OODB.PAS ---------------------- } { CUT } unit OODB; interface uses Objects; const PIDLimit: Word = $7FFF; Delta = 4; Hallmark = 9999; IndexPointerLocation = 4; StorageStart = 8; type { Record type for object registration } IndRec = record ID : Word; StartPos, Size : Longint; Base : Integer end; PIndRec = ^IndRec; { Stream for object size evaluation } TNullStream = object (TStream) SizeCounter : Longint; constructor Init; procedure ResetCounter; virtual; procedure Write (var Buf; Count: Word); virtual; function SizeInStream: Longint; virtual; end; PNullStream = ^TNullStream; { Stream - database main storage } DBStream = TStream; PDBStream = ^DBStream; { Collection for indexes } TIndexCollection = object (TCollection) procedure FreeItem (Item: Pointer); virtual; function GetItem (var S: TStream): Pointer; virtual; procedure PutItem (var S: TStream; Item: Pointer); virtual; end; PIndexCollection = ^TIndexCollection; { --- TBASE - the main class --- } TBase = object (TObject) BaseStream : PDBStream; { Main storage pointer } DBIndex, { Database index } HolesIndex : PIndexCollection; { Holes index } PIDCurrent : Word; { Unique identifier } NS : PNullStream; { For object size evaluation } DoneFlag : Boolean; { True if OODB is being disposed } function BytesInStream (P: PObject): Longint ; virtual; procedure IndexSort (Cat: PIndexCollection; StOrd: Boolean); virtual; function IndexFound (Cat: PIndexCollection; LookFor: Longint; var Pos: Integer; PIDSorted: Boolean): Boolean; virtual; function HoleFound (S: Longint; var Pos: Longint): Boolean; virtual; procedure Abort; virtual; procedure Commit; virtual; constructor Init (AStream: PDBStream); destructor Done; virtual; function Create: Word; virtual; procedure Put (PID: Word; P: PObject); virtual; function Get (PID: Word): PObject; virtual; procedure Destroy (PID: Word); virtual; function ObjSize (PID: Word): Longint; virtual; function Count: Integer; virtual; procedure IdlePack; virtual; end; { -- TBase -- } PBase = ^TBase; implementation { -- Implementation of TNullStream -- } constructor TNullStream.Init; begin TStream.Init; ResetCounter end; procedure TNullStream.ResetCounter; begin SizeCounter := 0 end; procedure TNullStream.Write (var Buf; Count: Word); { Overrides TStream.Write method } begin SizeCounter := SizeCounter + Count end; function TNullStream.SizeInStream: Longint; begin SizeInStream := SizeCounter end; { -- End of TNullStream implementation -- } { -- Implementation of TIndexCollection -- } procedure TIndexCollection.FreeItem (Item: Pointer); begin Dispose (Item) end; { FreeItem } function TIndexCollection.GetItem (var S: TStream): Pointer; var Item : PIndRec; begin New (Item); with S do with Item^ do begin Read (ID, SizeOf(ID)); Read (StartPos, SizeOf(StartPos)); Read (Size, SizeOf(Size)); Read (Base, SizeOf(Base)) end; GetItem := Item end; { GetItem } procedure TIndexCollection.PutItem (var S: TStream; Item: Pointer); begin with S do with IndRec(Item^) do begin Write (ID, SizeOf(ID)); Write (StartPos, SizeOf(StartPos)); Write (Size, SizeOf(Size)); Write (Base, SizeOf(Base)) end end; { PutItem } { -- End of TIndexCollection implementation -- } { -- TBASE IMPLEMENTATION -- } { ----- BytesInStream ------------------------------------------ } function TBase.BytesInStream (P: PObject): Longint ; { Determines the number of bytes required to put an object into the stream } begin with NS^ do begin ResetCounter; Put (P); BytesInStream := SizeInStream end end; { ----- IndexSort ---------------------------------------------- } procedure TBase.IndexSort (Cat: PIndexCollection; StOrd: Boolean); { Bubble-sorts any index (DBIndex or HolesIndex) according either to StartPos'es in a stream (StOrd = True) or to PID's (StOrd = False) } var i, j, k : Integer; Min : Longint; Aux : PIndRec; begin with Cat^ do for i := 0 to Count-2 do begin if StOrd then begin Min := IndRec(At(i)^).StartPos; k := i; for j := i+1 to Count-1 do if IndRec(At(j)^).StartPos < Min then begin k := j; Min := IndRec(At(k)^).StartPos end end else begin Min := IndRec(At(i)^).ID; k := i; for j := i+1 to Count-1 do if IndRec(At(j)^).ID < Min then begin k := j; Min := IndRec(At(k)^).ID end end; Aux := At (i); AtPut (i,At(k)); AtPut (k,Aux) { Bubble is up } end { for } end; { IndexSort } { ----- IndexFound --------------------------------------------- } function TBase.IndexFound (Cat: PIndexCollection; LookFor: Longint; var Pos: Integer; PIDSorted: Boolean) : Boolean; { Looks for LookFor in Cat^ index (binary search) and returns True if hits it. Position for LookFor (Pos) is located by all means } var m, j : Integer; Value : Longint; { Value that is found } begin IndexFound := False; with Cat^ do begin Pos := 0; j := Count-1; if j < Pos then Exit; while j > Pos do begin m := ( Pos + j ) div 2; if ( PIDSorted and (IndRec(At(m)^).ID >= LookFor) ) or ( not PIDSorted and (IndRec(At(m)^).StartPos >= LookFor) ) then j := m else Pos := m + 1 end; { while } if PIDSorted then Value := IndRec(At(Pos)^).ID else Value := IndRec(At(Pos)^).StartPos; if Value < LookFor then Pos := Pos + 1 else if Value = LookFor then IndexFound := True end { with } end; { IndexFound } { ----- HoleFound ---------------------------------------------- } function TBase.HoleFound (S: Longint; var Pos: Longint): Boolean; { Looks for a hole in a storage stream. Linear search, FIRST-FIT } var Found : Boolean; i : Integer; begin with HolesIndex^ do begin Found := False; i := 0; while not (Found or (i > Count-1)) do begin with IndRec(At(i)^) do if Size >= S then begin Found := True; Pos := StartPos; Size := Size - S; if Size = 0 then AtDelete(i) end; { if } i := i + 1 end { while } end; { with } HoleFound := Found end; { HoleFound } { ----- Abort ---------------------------------------------- } procedure TBase.Abort; { Cancels transaction. Restores old DBIndex and HolesIndex } var HoleStart, { Start of probable hole } Diff, { Length of probable hole } IndLoc : Longint; { Old DBIndex location in stream } i : Integer; NewRec : PIndRec; { Hole registration card } begin Dispose (DBIndex, Done); { Destroying old indexes } Dispose (HolesIndex, Done); with BaseStream^ do begin Seek (IndexPointerLocation); Read (IndLoc,4); Seek (IndLoc); DBIndex := PIndexCollection (Get) end; New (HolesIndex, Init(PIDLimit,Delta)); with DBIndex^ do begin HoleStart := StorageStart; for i := 0 to Count-1 do begin Diff := IndRec(At(i)^).StartPos - HoleStart; if Diff > 0 then begin New (NewRec); with NewRec^ do begin StartPos := HoleStart; Size := Diff end; HolesIndex^.Insert(NewRec) end; { if } HoleStart := IndRec(At(i)^).StartPos + IndRec(At(i)^).Size end; { for } BaseStream^.Seek (HoleStart); BaseStream^.Truncate end; { with } IndexSort (DBIndex, False); IndexSort (HolesIndex, True); PIDCurrent := IndRec(DBIndex^.At(DBIndex^.Count-1)^).ID + 1 end; { Abort } { ----- Commit ---------------------------------------------- } procedure TBase.Commit; { Acknowledges transaction by putting DBIndex into the stream } var S, { Size of DBIndex } IndLoc : Longint; { Index location in stream } i, BasePos : Integer; { Auxiliary variables } begin with DBIndex^ do begin for i := 0 to Count-1 do begin BasePos := IndRec(At(i)^).Base; if (BasePos <> -1) and (BasePos <> i) then begin IndRec(At(i)^).Size := IndRec(At(BasePos)^).Size; IndRec(At(i)^).StartPos := IndRec(At(BasePos)^).StartPos; IndRec(At(i)^).Base := i; IndRec(At(BasePos)^).Base := -1 end end; { for } i := 0; while ( i < Count ) do if IndRec(At(i)^).Base = -1 then AtDelete (i) else i := i + 1; for i := 0 to Count-1 do IndRec(At(i)^).Base := i end; { with } S := BytesInStream (DBIndex); if not HoleFound (S, IndLoc) then IndLoc := BaseStream^.GetSize; with IndRec(DBIndex^.At(0)^) do begin ID := 0; StartPos := IndLoc; Size := S; Base := 0 end; IndexSort (DBIndex, True); with BaseStream^ do begin Seek (IndLoc); Put (DBIndex); Seek (IndexPointerLocation); Write (IndLoc,4) end; if not DoneFlag then Abort end; { Commit } { ----- Init ---------------------------------------------- } constructor TBase.Init (AStream: PDBStream); { Opens an existing database stream or creates a new one } var Descr : Longint; { Stream descriptor } IndexCard : PIndRec; { DBIndex registration card } begin TObject.Init; BaseStream := AStream; New (NS, Init); New (DBIndex, Init(PIDLimit,Delta)); New (HolesIndex, Init(PIDLimit,Delta)); DoneFlag := False; with BaseStream^ do begin Descr := 0; Seek (0); if GetSize > 3 then Read (Descr,4); if Descr = Hallmark then Abort else begin Descr := Hallmark; Seek (0); Truncate; Write (Descr,4); Seek (IndexPointerLocation); Write (Descr,4); New (IndexCard); With IndexCard^ do begin ID := 0; StartPos := StorageStart; Size := 0; Base := 0 end; DBIndex^.AtInsert (0,IndexCard); Commit end end { with } end; { Init } { ----- Done ---------------------------------------------- } destructor TBase.Done; { Done is done ! } begin DoneFlag := True; Commit; Dispose (NS, Done); Dispose (DBIndex, Done); Dispose (HolesIndex, Done) end; { Done } { ----- Create ---------------------------------------------- } function TBase.Create : Word; { Generates unique identifier } begin if PIDCurrent < PIDLimit then begin Create := PIDCurrent; PIDCurrent := PIDCurrent + 1 end else Create := 0 end; { Create } { ----- Destroy ---------------------------------------------- } procedure TBase.Destroy (PID: Word); { Marks object registration card in DBIndex as destroyed (Base = -1). If object's base has existed in a stream, it becomes a hole. Object doesn't vanish from a stream until transaction is over (Commit or Done). } var Pos, { Number of object's card in DBIndex } HolePos, { Number of a potential hole } BasePos : Integer; BaseStart, BaseSize : Longint; { Charasteristics of object's base } NewRec : PIndRec; { New hole } i : Integer; begin with DBIndex^ do begin if not IndexFound (DBIndex, PID, Pos, True) then Exit; BasePos := IndRec(At(Pos)^).Base; IndRec(At(Pos)^).Base := -1; if (BasePos = -1) or (BasePos = Pos) then Exit; if IndexFound (HolesIndex, IndRec(At(BasePos)^).StartPos, HolePos, False) then Halt (1); BaseStart := IndRec(At(BasePos)^).StartPos; BaseSize := IndRec(At(BasePos)^).Size; if HolePos < HolesIndex^.Count then if BaseStart + BasePos = IndRec(HolesIndex^.At(HolePos)^).StartPos then begin IndRec(HolesIndex^.At(HolePos)^).StartPos := BaseStart; IndRec(HolesIndex^.At(HolePos)^).Size := IndRec(HolesIndex^.At(HolePos)^).Size + BaseSize; Exit end; if BaseStart + BaseSize < BaseStream^.GetSize then begin New (NewRec); NewRec^.StartPos := BaseStart; NewRec^.Size := BaseSize; HolesIndex^.AtInsert (HolePos, NewRec) end else begin BaseStream^.Seek (BaseStart); BaseStream^.Truncate end; AtDelete (BasePos); for i := BasePos to Count-1 do if IndRec(At(i)^).Base <> -1 then IndRec(At(i)^).Base := IndRec(At(i)^).Base-1 end { with } end; { Destroy } { ----- Put ---------------------------------------------- } procedure TBase.Put (PID: Word; P: PObject); { Puts an object into the database } var StreamPos, S : Longint; { Location and size of an object } Pos, { Number of object registration card } BasePos : Integer; { Number of object's base card } NewRec : PIndRec; { Object registration card } begin if PID >= PIDLimit then Exit; with DBIndex^ do if IndexFound (DBIndex, PID, Pos, True) then begin BasePos := IndRec(At(Pos)^).Base; if BasePos <> Pos then begin if BasePos <> -1 then Exit; PID := Create; if IndexFound (DBIndex, PID, BasePos, True ) then Halt (1); IndRec(At(Pos)^).Base := BasePos; Pos := BasePos end { if } end; { if } S := BytesInStream (P); if not HoleFound (S, StreamPos) then StreamPos := BaseStream^.GetSize; New (NewRec); with NewRec^ do begin ID := PID; StartPos := StreamPos; Size := S; Base := Pos end; DBIndex^.AtInsert (Pos, NewRec); with BaseStream^ do begin Seek (StreamPos); Put (P) end end; { Put } { ----- Get ---------------------------------------------- } function TBase.Get (PID: Word): PObject; { Gets an object from the database } var Pos, { Number of object registration card } BasePos : Integer; { Number of object's base card } begin Get := Nil; if IndexFound (DBIndex, PID, Pos, True) then begin BasePos := IndRec(DBIndex^.At(Pos)^).Base; if BasePos <> -1 then begin BaseStream^.Seek (IndRec(DBIndex^.At(BasePos)^).StartPos); Get := BaseStream^.Get end { if } end { if } end; { Get } { ----- ObjSize ---------------------------------------------- } function TBase.ObjSize (PID: Word): Longint; { Returns the size of an object } var Pos, { Number of object registration card } BasePos : Integer; { Number of object's base card } begin ObjSize := 0; if IndexFound (DBIndex, PID, Pos, True) then begin BasePos := IndRec(DBIndex^.At(Pos)^).Base; if BasePos <> -1 then ObjSize := IndRec(DBIndex^.At(BasePos)^).Size end { if } end; { ObjSize } { ----- Count ---------------------------------------------- } function TBase.Count: Integer; { Returns the number of objects in the database } begin Count := DBIndex^.Count end; { Count } { ----- IdlePack ---------------------------------------------- } procedure TBase.IdlePack; { Makes a single step of database packing. Method (just now) - simple sequential relocation. Before object is relocated, old index is gotten from the stream and then put back with proper amendments. } var P : PObject; { Relocated object } OldLoc, { Old location of relocated object } NewLoc, { New location of relocated object } IndLoc : Longint; { Location of old DBIndex } OldIndex : PIndexCollection; { Old DBIndex } Pos : Integer; { Posititon of relocated object in the index } begin with HolesIndex^ do with BaseStream^ do begin if Count = 0 then Exit; OldLoc := IndRec(At(0)^).StartPos + IndRec(At(0)^).Size; NewLoc := IndRec(At(0)^).StartPos; Seek (OldLoc); P := Get; if P = Nil then begin Reset; Seek (NewLoc); Truncate; AtDelete (0); Exit end; Seek (IndexPointerLocation); Read (IndLoc,4); Seek (IndLoc); OldIndex := PIndexCollection (Get); if IndexFound (OldIndex, OldLoc, Pos, False) then begin IndRec(OldIndex^.At(Pos)^).StartPos := NewLoc; if not IndexFound (DBIndex, IndRec(OldIndex^.At(Pos)^).ID, Pos, True) then Halt (1) end else begin Pos := 0; while (IndRec(DBIndex^.At(Pos)^).StartPos <> OldLoc) do Pos := Pos + 1 end; IndRec(DBIndex^.At(Pos)^).StartPos := NewLoc; if OldLoc = IndLoc then IndLoc := NewLoc; Seek (NewLoc); Put (P); Seek (IndexPointerLocation); Write (IndLoc,4); Seek (IndLoc); Put (OldIndex); Dispose (P,Done); Dispose (OldIndex, Done); IndRec(At(0)^).StartPos := NewLoc + IndRec(DBIndex^.At(Pos)^).Size; if Count > 1 then if ( IndRec(At(0)^).StartPos + IndRec(At(0)^).Size = IndRec(At(1)^).StartPos ) then begin IndRec(At(0)^).Size := IndRec(At(0)^).Size + IndRec(At(1)^).Size; AtDelete (1) end end { With } end; { IdlePack } { -- End of TBase implementation -- } const RIndexCollection: TStreamRec = ( ObjType : 10000; VMTLink : Ofs(TypeOf(TIndexCollection)^); Load : @TIndexCollection.Load; Store : @TIndexCollection.Store ); begin { Unit body } RegisterType (RIndexCollection) end.