<> <> <> DIRECTORY Atom, Basics, BTree, BTreeVM, Commander, CommandTool, FS, FSBackdoor, IO, Log, LupineRuntime, PrincOps, PrincOpsUtils, ProcessProps, RefTab, RefText, Rope, ThNet, UserProfile; WPBTreeImpl: PROGRAM IMPORTS Atom, Basics, BTree, BTreeVM, Commander, CommandTool, FS, FSBackdoor, IO, Log, LupineRuntime, RefTab, PrincOpsUtils, ProcessProps, RefText, Rope, ThNet, UserProfile EXPORTS ThNet SHARES Rope = BEGIN OPEN IO; ROPE: TYPE = Rope.ROPE; Attrs: TYPE = ThNet.Attrs; WPListing: TYPE = ThNet.WPListing; <> wpRepPrim: BTree.RepresentationPrimitives = [ compare: Compare, compareEntries: CompareEntries, entrySize: EntrySize, entryFromRecord: EntryFromRecord, newRecordFromEntry: NewRecordFromEntry]; Key: TYPE = ROPE; Record: TYPE = REF WPEntryRecord; Entry: TYPE = LONG POINTER TO WPEntryRecord; WPEntryRecord: TYPE = RECORD [ recordSize: EntSize, linkIndex: INT_-1, -- If non-negative, word index of an additional record in another file. recordData: SEQUENCE COMPUTED EntSize OF INTEGER ]; WPState: TYPE = ThNet.WPState; WPStateRecord: TYPE = ThNet.WPStateRecord; EntSize: TYPE = [0..LAST[BTree.PageSize]-BTree.reservedWordsPerPage+1]; lastEnt: EntSize = LAST[EntSize]-1; Compare: PROCEDURE [key: BTree.Key, entry: Entry] RETURNS [BTree.Comparison] = { recordKey: Key=RopeFromWPEntryRecord[entry,0].rope; RETURN [CompareRopes[NARROW[key, Key], recordKey, FALSE]]; }; CompareEntries: PROCEDURE [entry1, entry2: Entry] RETURNS [BTree.Comparison] = { e1: Key=RopeFromWPEntryRecord[entry1,0].rope; e2: Key=RopeFromWPEntryRecord[entry2,0].rope; RETURN [CompareRopes[e1, e2, FALSE]]; }; EntrySize: PROCEDURE [entry: Entry] RETURNS [words: BTree.EntSize] = { RETURN [SIZE[WPEntryRecord[entry.recordSize]]] }; EntryFromRecord: PROCEDURE [record: BTree.Record] RETURNS [entry: Entry] = { RETURN [LOOPHOLE[NARROW[record, Record]]] }; NewRecordFromEntry: PROCEDURE [entry: Entry] RETURNS [record: Record] = BEGIN record _ NEW[WPEntryRecord[entry.recordSize]]; PrincOpsUtils.LongCopy[to: LOOPHOLE[record], from: entry, nwords: SIZE[WPEntryRecord[entry.recordSize]]]; END; CompareRopes: PROC[s1: ROPE, s2: ROPE, substringEqual: BOOL] RETURNS[res: BTree.Comparison] = { ss1: INT=Rope.Size[s1]; r2: ROPE_IF substringEqual THEN Rope.Substr[s2, 0, ss1] ELSE s2; RETURN [Rope.Compare[s1, r2, FALSE]]; }; wpAttrs: RefTab.Ref; wpExtInt: RefTab.Ref; <> InitWhitePagesDatabase: PUBLIC SAFE PROC[ fileName: ROPE, accessOptions: FS.AccessOptions _ $read, howToComplain: Log.WhereToReport_$System] RETURNS[wpState: WPState_NIL] = TRUSTED { buffer: REF TEXT = RefText.ObtainScratch[100]; treeName: ROPE=fileName.Concat[".Tree"]; dataName: ROPE=fileName.Concat[".TreeData"]; feepTreeName: ROPE=fileName.Concat[".FTree"]; treeFile, dataFile, feepFile: FS.OpenFile; keyRecord, feepKeyRecord: Record; IF wpAttrs=NIL THEN { wpAttrs _ RefTab.Create[]; FOR attr: Attrs IN Attrs DO IF keysForAttributes[attr] # $unassigned THEN []_RefTab.Store[wpAttrs, keysForAttributes[attr], NEW[Attrs_attr]]; ENDLOOP; }; IF wpExtInt=NIL THEN { s: IO.STREAM = FS.StreamOpen[UserProfile.Token[ key: "ThrushWPMappings", default: "/Indigo/Voice/BTree/IntExt.map"]]; wpExtInt _ RefTab.Create[]; WHILE ~s.EndOf[] DO ENABLE IO.EndOfStream=>EXIT; int: ATOM _ Atom.MakeAtomFromRefText[s.GetCedarToken[buffer: buffer].token]; ext: ATOM; []_s.GetChar[]; ext _ Atom.MakeAtomFromRefText[s.GetLine[buffer]]; []_wpExtInt.Store[ext, int]; -- external version is the key. ENDLOOP; s.Close[]; }; RefText.ReleaseScratch[buffer]; wpState _ NEW[WPStateRecord]; keyRecord _ NEW[WPEntryRecord[LAST[EntSize]]]; wpState.keyRecord _ keyRecord; feepKeyRecord _ NEW[WPEntryRecord[LAST[EntSize]]]; wpState.feepKeyRecord _ feepKeyRecord; wpState.wpTree _ BTree.New[repPrim: wpRepPrim, storPrim: [BTreeVM.ReferencePage, BTreeVM.ReleasePage], minEntrySize: 4]; wpState.wpFeepTree _ BTree.New[repPrim: wpRepPrim, storPrim: [BTreeVM.ReferencePage, BTreeVM.ReleasePage], minEntrySize: 4]; { ENABLE FS.Error => IF error.group = user THEN { Log.Problem[error.explanation, howToComplain]; treeFile_FS.nullOpenFile; CONTINUE; }; SELECT accessOptions FROM $read => { treeFile_FS.Open[name: treeName, remoteCheck: FALSE]; feepFile_FS.Open[name: feepTreeName, remoteCheck: FALSE]; dataFile _ FS.Open[name: dataName, remoteCheck: FALSE]; }; $create => { treeFile_FS.Create[name: treeName, keep: 2, setKeep: TRUE, pages: 100]; feepFile_FS.Create[name: feepTreeName, keep: 2, setKeep: TRUE, pages: 100]; dataFile _ FS.Create[name: dataName, keep: 2, setKeep: TRUE, pages: 300]; }; $write => { treeFile_FS.OpenOrCreate[name: treeName, keep: 2, pages: 100]; feepFile_FS.OpenOrCreate[name: feepTreeName, keep: 2, pages: 100]; dataFile _ FS.OpenOrCreate[name: dataName, keep: 2, pages: 300]; }; ENDCASE => Log.Problem["Don't request $append option for BTree", howToComplain]; }; IF treeFile=NIL OR dataFile = NIL OR feepFile=NIL THEN RETURN[NIL]; wpState.wpDataVMFile _ dataFile; wpState.wpDataVMLen _ FS.GetInfo[dataFile].bytes/2; wpState.wpVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[treeFile], filePagesPerPage: 1, cacheSize: 20]; wpState.wpFeepVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[feepFile], filePagesPerPage: 1, cacheSize: 20]; wpState.wpDataVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[dataFile], filePagesPerPage: 1, cacheSize: 10]; []_OpenWhitePagesDatabase[wpState, howToComplain, FS.GetInfo[dataFile].bytes=0]; }; OpenWhitePagesDatabase: SAFE PROC[ wpState: WPState, howToComplain: Log.WhereToReport_$System, init: BOOL_FALSE] RETURNS [ok: BOOL_FALSE] = TRUSTED { ENABLE BTree.Error => { Log.Problem["Trouble opening BTree", howToComplain]; CONTINUE }; IF wpState=NIL THEN RETURN[FALSE]; IF wpState.wpOpen THEN RETURN; wpState.wpTree.Open[storage: wpState.wpVM, pageSize: FS.WordsForPages[1], initialize: init]; wpState.wpFeepTree.Open[storage: wpState.wpFeepVM, pageSize: FS.WordsForPages[1], initialize: init]; ok _ wpState.wpOpen _ TRUE; }; CloseWhitePagesDatabase: PUBLIC SAFE PROC[wpState: WPState, howToComplain: Log.WhereToReport_$System] RETURNS [ok: BOOL_TRUE] = TRUSTED { IF wpState=NIL OR ~wpState.wpOpen THEN RETURN; wpState.wpTree.SetState[closed!BTree.Error => { ok_FALSE; CONTINUE}]; wpState.wpFeepTree.SetState[closed!BTree.Error => { ok_FALSE; CONTINUE}]; wpState.wpDataVM.FlushCache[]; wpState.wpDataVM.FreeBuffers[]; FS.SetByteCountAndCreatedTime[wpState.wpDataVMFile, wpState.wpDataVMLen*2!FS.Error => IF error.group=user THEN { Log.Problem[error.explanation, howToComplain]; CONTINUE; }]; wpState.wpDataVMValid _ TRUE; wpState.wpOpen _ FALSE; }; SetProperLength: SAFE PROC[name: ROPE] = CHECKED { file: FS.OpenFile _ FS.Open[name, $write]; pages: INT _ FS.GetInfo[file].pages; FS.SetByteCountAndCreatedTime[file, FS.BytesForPages[pages]]; FS.Close[file]; }; WhitePagesLookup: PUBLIC SAFE PROC[ wpState: WPState, name: ROPE, feep: BOOL_FALSE ] RETURNS [ listing: WPListing_NIL ] = TRUSTED { tree: BTree.Tree _ IF feep THEN wpState.wpFeepTree ELSE wpState.wpTree; <> <> FindOne: SAFE PROC[record: BTree.Record] RETURNS [continue: BOOLEAN_FALSE] = TRUSTED { index: EntSize; key: ATOM; value: ROPE; entry: Entry _ EntryFromRecord[record]; [index, key, value] _ RopeFromWPEntryRecord[entry, 0]; SELECT CompareRopes[name, RopeFromWPEntryRecord[entry,0].rope, TRUE] FROM less => RETURN[TRUE]; equal => NULL; ENDCASE => RETURN[FALSE]; IF feep AND listing#NIL THEN { listing _ NIL; RETURN; }; IF index=lastEnt OR (record _ GetLinkedRecord[wpState, NARROW[record], $read]) = NIL THEN RETURN[feep]; entry _ EntryFromRecord[record]; listing _ RefTab.Create[]; index _ 0; WHILE index#lastEnt DO []_listing.Store[key, value]; [index, key, value] _ RopeFromWPEntryRecord[entry, index]; ENDLOOP; RETURN[feep]; }; IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN RETURN; []_BTree.EnumerateRecords[tree: tree, key: name, relation: less, Proc: FindOne]; }; WhitePagesEntry: PUBLIC SAFE PROC[ wpState: WPState, name: ROPE_NIL, key: ATOM_$officeNumber, feep: BOOL_FALSE, listing: WPListing _ NIL] RETURNS [fullRName: ROPE_NIL, entry: ROPE_NIL, newListing: WPListing_NIL] = TRUSTED { IF listing = NIL THEN listing _ WhitePagesLookup[wpState, name, feep]; IF listing = NIL THEN RETURN; newListing _ listing; fullRName _ NARROW[listing.Fetch[$rName].val]; IF key#NIL THEN entry _ NARROW[listing.Fetch[key].val]; }; WhitePagesEnter: PUBLIC SAFE PROC[wpState: WPState, listing: WPListing] = TRUSTED { <> IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN RETURN; WhitePagesDo[wpState, listing, enter]; }; WhitePagesPrint: PUBLIC SAFE PROC[wpState: WPState, listing: WPListing] = TRUSTED { <> WhitePagesDo[wpState, listing, print]; }; WPFn: TYPE = { print, enter, note }; WhitePagesDo: SAFE PROC[wpState: WPState, listing: WPListing, fn: WPFn] = TRUSTED { <> dataPage: BTree.PagePtr; dummyData: ARRAY [0..4) OF CARDINAL; dataEntry: Entry _ LOOPHOLE[LONG[@dummyData[0]]]; keyEntry: Entry _ EntryFromRecord[wpState.keyRecord]; feepKeyEntry: Entry _ EntryFromRecord[wpState.feepKeyRecord]; entry: Entry _ keyEntry; name, feepName: ROPE; linkIndex: INT _ keyEntry.linkIndex; linkPage: INT; inPageIndex: INT; refType: BTree.ReferenceType _ $write; doIt: BOOL_FALSE; EPA: RefTab.EachPairAction = TRUSTED { realKey: ATOM _ NARROW[key,ATOM]; SELECT fn FROM enter => RopeToWPEntryRecord[entry, realKey, NARROW[val], doIt]; print => PrintWPEntry[realKey, NARROW[val]]; ENDCASE; RETURN[FALSE]; }; <> name_NARROW[listing.Fetch[$rName].val]; feepName _ ThNet.FeepName[name]; IF name=NIL THEN ERROR; keyEntry.recordSize_0; feepKeyEntry.recordSize_0; SELECT fn FROM enter => { RopeToWPEntryRecord[keyEntry, $rName, name, TRUE]; RopeToWPEntryRecord[feepKeyEntry, $rName, feepName, TRUE]; }; note => { (GetCommanderHandle[]).out.Put[rope[name], rope["\n"]]; RETURN; }; ENDCASE; keyEntry[keyEntry.recordSize] _ 0; keyEntry.recordSize _ keyEntry.recordSize + (1+3); -- 1 for the ending 0, 2 for the header feepKeyEntry[feepKeyEntry.recordSize] _ 0; feepKeyEntry.recordSize _ feepKeyEntry.recordSize + (1+3); dataEntry.recordSize_0; entry _ dataEntry; []_RefTab.Pairs[listing, EPA]; dataEntry.recordSize_dataEntry.recordSize + (1+3); IF fn=enter THEN { linkIndex _ wpState.wpDataVMLen; IF ~([linkPage, inPageIndex, ]_GetLinkValues[wpState, linkIndex]).ok THEN RETURN; IF inPageIndex=0 OR inPageIndex+dataEntry.recordSize>FS.WordsForPages[1] THEN { <> refType _ $new; IF inPageIndex#0 THEN { linkPage_SUCC[linkPage]; inPageIndex _ 0; linkIndex _ FS.WordsForPages[linkPage]; }; }; dataPage _ BTreeVM.ReferencePage[wpState.wpDataVM, linkPage, refType]; entry _ dataEntry _ dataPage+inPageIndex; dataEntry.recordSize _ 0; doIt _ TRUE; []_RefTab.Pairs[listing, EPA]; dataEntry[dataEntry.recordSize] _ 0; dataEntry.recordSize _ dataEntry.recordSize + (1+3); dataEntry.linkIndex _ -1; wpState.wpDataVMLen _ linkIndex+dataEntry.recordSize; BTreeVM.ReleasePage[wpState.wpDataVM, linkPage, endOfUpdate]; keyEntry.linkIndex _ linkIndex; feepKeyEntry.linkIndex _ linkIndex; BTree.UpdateRecord[tree: wpState.wpTree, key: name, record: NARROW[wpState.keyRecord]]; BTree.UpdateRecord[ tree: wpState.wpFeepTree, key: feepName, record: NARROW[wpState.feepKeyRecord]]; }; }; PrintWPEntry: PROC[key: ATOM, val: ROPE] = { h: Commander.Handle = GetCommanderHandle[]; h.out.PutF["%g: %g\n", atom[key], rope[val]]; IF key=$rName AND h.out#h.err THEN h.err.Put[rope[val], rope["\n"]]; }; GetCommanderHandle: PROC RETURNS[handle: Commander.Handle] = { p: Atom.PropList = ProcessProps.GetPropList[]; IF p=NIL THEN RETURN[NIL]; RETURN[NARROW[Commander.GetProperty[$CommanderHandle, p]]]; }; GetLinkedRecord: SAFE PROC[wpState: WPState, keyRecord: Record, access: FS.AccessOptions] RETURNS[record: Record_NIL] = TRUSTED { linkIndex: INT _ keyRecord.linkIndex; linkPage: INT; inPageIndex: INT; entry: Entry; pagePtr: BTree.PagePtr; IF ([linkPage, inPageIndex,]_GetLinkValues[wpState, linkIndex]).ok=FALSE THEN RETURN; pagePtr _ BTreeVM.ReferencePage[wpState.wpDataVM, linkPage, SELECT access FROM $read => $read, $create => $new, ENDCASE => $write]; entry _ pagePtr+inPageIndex; record _ NewRecordFromEntry[entry]; BTreeVM.ReleasePage[wpState.wpDataVM, linkPage, endOfUpdate]; }; GetLinkValues: SAFE PROC[wpState: WPState, linkIndex: INT] RETURNS[linkPage, inPageIndex: INT, ok: BOOL_FALSE] = TRUSTED { IF linkIndex<0 OR linkIndex> wpState.wpDataVMLen THEN { Log.Problem["BTree structure error"]; RETURN; }; ok_TRUE; linkPage _ FS.PagesForWords[linkIndex+1]-1; inPageIndex _ linkIndex - FS.WordsForPages[linkPage]; }; RopeFromWPEntryRecord: PROC[entry: Entry, index: EntSize] RETURNS[newIndex: EntSize_lastEnt, key: ATOM, rope: ROPE] = { code: INTEGER; keyRope: ROPE; IF index=lastEnt THEN RETURN; code _ entry[index]; SELECT code FROM 0 => RETURN; <0 => { key _ keysForAttributes[LOOPHOLE[-code]]; index_index+1; }; ENDCASE => { [index, keyRope] _ RFromWPER[entry, index]; key _ Atom.MakeAtom[keyRope]; }; [newIndex, rope] _ RFromWPER[entry, index]; }; RFromWPER: PROC[entry: Entry, index: EntSize] RETURNS[newIndex: EntSize_lastEnt, rope: ROPE] = { length: INTEGER; wfc: LONG CARDINAL; textRope: Rope.Text; IF index=lastEnt THEN RETURN; length _ entry[index]; rope _ textRope _ Rope.NewText[size: length]; wfc _ LupineRuntime.WordsForChars[length]; PrincOpsUtils.LongCOPY[ from: @entry[index+1], to: BASE[DESCRIPTOR[textRope.text]], nwords: wfc ]; newIndex _ index+wfc+1; }; RopeToWPEntryRecord: PROC[entry: Entry, key: ATOM, rope: ROPE, doIt: BOOL] = { index: EntSize _ entry.recordSize; attr: REF Attrs _ NARROW[wpAttrs.Fetch[key].val]; IF attr#NIL THEN { IF doIt THEN entry[index]_-LOOPHOLE[attr^, INTEGER]; entry.recordSize _ index+1; } ELSE RToWPER[entry, Atom.GetPName[key], doIt]; RToWPER[entry, rope, doIt]; }; RToWPER: PROC[entry: Entry, rope: ROPE, doIt: BOOL] = { index: EntSize _ entry.recordSize; wfc: LONG CARDINAL; len: INTEGER _ rope.Length[]; IF doIt THEN entry[index] _ len; wfc _ LupineRuntime.WordsForChars[len]; IF doIt THEN PrincOpsUtils.LongCOPY[ to: @entry[index+1], from: BASE[DESCRIPTOR[Rope.Flatten[rope].text]], nwords: wfc ]; entry.recordSize _ index+wfc+1; }; <> ConcreteKey: PROCEDURE [key: BTree.Key] RETURNS [Key] = { RETURN [NARROW[key]] }; ConcreteEntry: PROCEDURE [entry: BTree.Entry] RETURNS [Entry] = { RETURN [entry] }; <> file: FS.OpenFile _ FS.nullOpenFile; WhitePagesFill: SAFE PROC[cmd: Commander.Handle, accessOptions: FS.AccessOptions_write] = TRUSTED { s: IO.STREAM; lineBuffer: REF TEXT = RefText.ObtainScratch[100]; tokenBuffer: REF TEXT = RefText.ObtainScratch[100]; listing: WPListing_NIL; wpState: WPState; numEntries: CARDINAL _ 0; argsL: LIST OF ROPE; IF ([argsL,] _ CommandTool.ParseToList[cmd, IO.SP]).length<2 THEN { Log.Problem["Not enough file names specified", $System]; argsL _ NIL; }; IF argsL=NIL THEN RETURN; s _ FS.StreamOpen[argsL.first]; wpState _ InitWhitePagesDatabase[argsL.rest.first, accessOptions]; IF wpState#NIL THEN WHILE ~s.EndOf[] DO ENABLE IO.EndOfStream=>EXIT; line: REF TEXT _ s.GetLine[buffer: lineBuffer]; lineS: IO.STREAM; extKey, key: ATOM_NIL; value: ROPE; keyVal: REF TEXT; KeyProc: IO.BreakProc = TRUSTED { RETURN[IF char=': THEN sepr ELSE other]; }; IF line.length=0 THEN { IF listing#NIL THEN { IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN EXIT; WhitePagesDo[wpState, listing, note]; WhitePagesEnter[wpState, listing]; key _ extKey _ NIL; numEntries _ SUCC[numEntries]; IF Basics.BITSHIFT[numEntries, 12]=0 THEN []_CloseWhitePagesDatabase[wpState]; }; listing _ NIL; LOOP; }; IF listing = NIL THEN listing _ NewListing[]; lineS _ IO.TIS[line]; keyVal _ NIL; keyVal _ lineS.GetToken[KeyProc, tokenBuffer!IO.EndOfStream => CONTINUE].token; IF lineS.EndOf[] THEN { -- no key, extension of previous value. IF key#NIL THEN value _ value.Cat["\n", Rope.FromRefText[keyVal]]; } ELSE { extKey _ Atom.MakeAtomFromRefText[keyVal]; key _ NARROW[wpExtInt.Fetch[extKey].val]; IF key=NIL THEN key_extKey; []_lineS.GetChar[!IO.EndOfStream => CONTINUE]; []_lineS.SkipWhitespace[!IO.EndOfStream => CONTINUE]; value _ lineS.GetLineRope[!IO.EndOfStream => CONTINUE]; }; IF key#NIL THEN List[listing, key, value]; ENDLOOP; (GetCommanderHandle[]).err.PutRope["-- Updates done.\n"]; s.Close[]; RefText.ReleaseScratch[lineBuffer]; RefText.ReleaseScratch[tokenBuffer]; []_CloseWhitePagesDatabase[wpState]; }; NewListing: SAFE PROC RETURNS [WPListing] = TRUSTED { RETURN[RefTab.Create[]]; }; List: SAFE PROC[listing: WPListing, key: ATOM, value: ROPE] = TRUSTED { []_RefTab.Store[listing, key, value]; }; FillCmdInit: Commander.CommandProc = TRUSTED { WhitePagesFill[cmd, $create]; }; FillCmd: Commander.CommandProc = TRUSTED { WhitePagesFill[cmd, $write]; }; keysForAttributes: ARRAY ThNet.Attrs OF ATOM = [ $unassigned, $name, $rName, $officeNumber, $officeDDDNumber, $outsideNumber, $officeAddress, $officeLocation, $organization, $homeAddress, $frequency, $mailSystem, $primaryKey, $unassigned, $unassigned, $unassigned ]; Commander.Register["WPFill", FillCmdInit]; Commander.Register["WPRefill", FillCmd]; END.