<> <> <> DIRECTORY Atom, Basics, BTree, BTreeSimple, BTreeVM, Commander, CommandToolExtras, FS, FSBackdoor, IO, Log, LupineRuntime, PrincOps, PrincOpsUtils, ProcessProps, RefTab, RefText, Rope, ThNet, UserProfile; WPBTreeImpl: PROGRAM IMPORTS Atom, Basics, BTree, BTreeSimple, BTreeVM, Commander, CommandToolExtras, 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; <> <> WPState: TYPE = ThNet.WPState; WPStateRecord: TYPE = ThNet.WPStateRecord; <> WPValue: TYPE = LONG POINTER TO RECORD [ linkIndex: INT ]; <> EntSize: TYPE = [0..LAST[BTree.PageSize]-BTree.reservedWordsPerPage+1]; lastEnt: EntSize = LAST[EntSize]-1; Entry: TYPE = LONG POINTER TO WPEntryRecord; WPEntryRecord: TYPE = RECORD [ recordSize: EntSize, recordData: SEQUENCE COMPUTED EntSize OF INTEGER ]; <> <<>> CompareRopes: PROC[s1: ROPE, s2: ROPE, substringEqual: BOOL] RETURNS[res: BTreeSimple.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; <> InitWhitePagesDatabase: PUBLIC SAFE PROC[ fileName: ROPE, extIntMapName: ROPE, accessOptions: FS.AccessOptions _ $read, howToComplain: Log.WhereToReport_$System] RETURNS[wpState: WPState_NIL] = TRUSTED { buffer: REF TEXT = RefText.ObtainScratch[100]; s: IO.STREAM; wpState _ NEW[WPStateRecord]; wpState.accessOptions _ accessOptions; IF fileName=NIL THEN fileName _ UserProfile.Token[key: "ThrushWPTreeName", default: "///Strowger/WPTree"]; wpState.treeRootName _ FS.ExpandName[fileName].fullFName; IF extIntMapName=NIL THEN extIntMapName _ UserProfile.Token[key:"ThrushWPMappings", default: "/Indigo/Voice/BTree/IntExt.map"]; 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; }; s _ FS.StreamOpen[extIntMapName]; wpState.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]]; []_wpState.wpExtInt.Store[ext, int]; -- external version is the key. ENDLOOP; s.Close[]; RefText.ReleaseScratch[buffer]; []_OpenWhitePagesDatabase[wpState, howToComplain]; }; OpenWhitePagesDatabase: SAFE PROC[ wpState: WPState, howToComplain: Log.WhereToReport_$System] RETURNS [ok: BOOL_FALSE] = TRUSTED { ENABLE BTree.Error => { Log.Problem["Trouble opening BTree", howToComplain]; CONTINUE }; treeName: ROPE_wpState.treeRootName.Concat[".Tree"]; dataName: ROPE_wpState.treeRootName.Concat[".TreeData"]; feepTreeName: ROPE_wpState.treeRootName.Concat[".FTree"]; treeFile, dataFile, feepFile: FS.OpenFile; init: BOOL; IF wpState=NIL THEN RETURN[FALSE]; IF wpState.wpOpen THEN RETURN; wpState.wpTree _ BTreeSimple.New[]; wpState.wpFeepTree _ BTreeSimple.New[]; { ENABLE FS.Error => IF error.group = user THEN { Log.Problem[error.explanation, howToComplain]; treeFile_FS.nullOpenFile; CONTINUE; }; SELECT wpState.accessOptions FROM $read => { treeFile_FS.Open[name: treeName]; feepFile_FS.Open[name: feepTreeName]; dataFile _ FS.Open[name: dataName]; }; $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[FALSE]; wpState.wpFile _ treeFile; wpState.wpFeepFile _ feepFile; wpState.wpDataVMFile _ dataFile; wpState.wpDataVMLen _ FS.GetInfo[dataFile].bytes/2; wpState.wpDataVM _ BTreeVM.Open[file: FSBackdoor.GetFileHandle[dataFile], filePagesPerPage: 1, cacheSize: 10]; init _ FS.GetInfo[dataFile].bytes=0; BTreeSimple.Open[tree: wpState.wpTree, file: wpState.wpFile, initialize: init]; BTreeSimple.Open[tree: wpState.wpFeepTree, file: wpState.wpFeepFile, initialize: init]; IF wpState.accessOptions = $create THEN wpState.accessOptions _ $write; 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; BTreeSimple.SetState[wpState.wpTree, closed!BTree.Error => { ok_FALSE; CONTINUE}]; BTreeSimple.SetState[wpState.wpTree, closed!BTree.Error => { ok_FALSE; CONTINUE}]; wpState.wpDataVM.FlushCache[]; wpState.wpDataVM.FreeBuffers[]; IF wpState.accessOptions # $read THEN FS.SetByteCountAndCreatedTime[wpState.wpDataVMFile, wpState.wpDataVMLen*2!FS.Error => IF error.group=user THEN { Log.Problem[error.explanation, howToComplain]; CONTINUE; }]; wpState.wpFile.Close[!FS.Error => IF error.group>=lock THEN CONTINUE]; wpState.wpFeepFile.Close[!FS.Error => IF error.group>=lock THEN CONTINUE]; wpState.wpDataVMFile.Close[!FS.Error => IF error.group>=lock THEN CONTINUE]; wpState.wpDataVMValid _ TRUE; wpState.wpOpen _ FALSE; }; SetProperLength: SAFE PROC[name: ROPE] = CHECKED { pages: INT; file: FS.OpenFile _ FS.Open[name, $write]; pages _ 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 { <> <> FindOne: UNSAFE PROC[key: BTreeSimple.EntryKey, value: BTreeSimple.EntryValue] RETURNS [continue: BOOLEAN_FALSE] = { index: EntSize; entry: Entry; wpValue: WPValue = LOOPHOLE[@value[0]]; recordKey: ATOM; recordValue: ROPE; SELECT CompareRopes[name, BTreeSimple.KeyFromEntry[key], TRUE] FROM greater => RETURN[TRUE]; equal => NULL; ENDCASE => RETURN[FALSE]; IF feep AND listing#NIL THEN { listing _ NIL; RETURN; }; IF (entry _ GetLinkedEntry[wpState, wpValue, $read]) = NIL THEN RETURN[feep]; listing _ RefTab.Create[]; [index, recordKey, recordValue] _ RopeFromWPEntry[entry, 0]; WHILE index#lastEnt DO []_listing.Store[recordKey, recordValue]; [index, recordKey, recordValue] _ RopeFromWPEntry[entry, index]; ENDLOOP; ReleaseLinkedEntry[wpState, wpValue]; RETURN[feep]; }; IF ~wpState.wpOpen AND ~OpenWhitePagesDatabase[wpState] THEN RETURN; []_BTreeSimple.EnumerateEntries[ tree: IF feep THEN wpState.wpFeepTree ELSE wpState.wpTree, 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; -- For collecting length statistics entry: Entry _ LOOPHOLE[LONG[@dummyData[0]]]; name, feepName: ROPE; linkIndex: INT_ wpState.wpDataVMLen; linkPage: INT; inPageIndex: INT; refType: BTree.ReferenceType _ $write; doIt: BOOL_FALSE; EPA: RefTab.EachPairAction = TRUSTED { realKey: ATOM _ NARROW[key,ATOM]; SELECT fn FROM enter => RopeToWPEntry[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; SELECT fn FROM note => { (GetCommanderHandle[]).out.Put[rope[name], rope["\n"]]; RETURN; }; ENDCASE; entry.recordSize_0; []_RefTab.Pairs[listing, EPA]; entry.recordSize_entry.recordSize + (1+1); -- one for size field, one for ending 0 IF fn#enter THEN RETURN; IF ~([linkPage, inPageIndex, ]_GetLinkValues[wpState, linkIndex]).ok THEN RETURN; IF inPageIndex=0 OR inPageIndex+entry.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 _ dataPage+inPageIndex; entry.recordSize _ 0; doIt _ TRUE; []_RefTab.Pairs[listing, EPA]; entry[entry.recordSize] _ 0; entry.recordSize _ entry.recordSize + (1+1); wpState.wpDataVMLen _ linkIndex+entry.recordSize; BTreeVM.ReleasePage[wpState.wpDataVM, linkPage, endOfUpdate]; { SetVal: PROC[value: BTreeSimple.EntryValue] = { LOOPHOLE[value, LONG POINTER TO CARDINAL]^ _ 2; -- Compensate BTree bug! LOOPHOLE[@value[0], LONG POINTER TO INT]^ _ linkIndex; }; BTreeSimple.UpdateEntry[tree: wpState.wpTree, key: name, valueLength: 2, Proc: SetVal]; BTreeSimple.UpdateEntry[tree: wpState.wpFeepTree, key: feepName, valueLength: 2, Proc: SetVal]; }; }; 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]]]; }; GetLinkedEntry: SAFE PROC[wpState: WPState, wpValue: WPValue, access: FS.AccessOptions] RETURNS[entry: Entry _NIL] = TRUSTED { linkIndex: INT _ wpValue.linkIndex; linkPage: INT; inPageIndex: INT; 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; }; ReleaseLinkedEntry: SAFE PROC[wpState: WPState, wpValue: WPValue] = TRUSTED { linkPage: INT; IF ([linkPage,,]_GetLinkValues[wpState, wpValue.linkIndex]).ok=FALSE THEN RETURN; 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]; }; RopeFromWPEntry: 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] _ RFromWPE[entry, index]; key _ Atom.MakeAtom[keyRope]; }; [newIndex, rope] _ RFromWPE[entry, index]; }; RFromWPE: 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; }; RopeToWPEntry: 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 RToWPE[entry, Atom.GetPName[key], doIt]; RToWPE[entry, rope, doIt]; }; RToWPE: 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; }; <> 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; sourceFile: ROPE _ CommandToolExtras.NextArgument[cmd]; treeRoot, intExt: ROPE; IF sourceFile=NIL THEN { Log.Problem["Source file not specified", $System]; RETURN; }; s _ FS.StreamOpen[sourceFile]; treeRoot _ CommandToolExtras.NextArgument[cmd]; intExt _ CommandToolExtras.NextArgument[cmd]; wpState _ InitWhitePagesDatabase[ treeRoot, intExt, 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, 8]=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[wpState.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]; SetProperLength[wpState.treeRootName.Concat[".Tree"]]; SetProperLength[wpState.treeRootName.Concat[".FTree"]]; }; NewListing: PUBLIC SAFE PROC RETURNS [WPListing] = TRUSTED { RETURN[RefTab.Create[]]; }; List: PUBLIC 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, "WPFill (opt)> creates new set of tree files from entries in textEntriesFile"]; Commander.Register["WPRefill", FillCmd, "WPRefill (opt)> adds to set of tree files from entries in textEntriesFile"]; END.