DIRECTORY Atom USING [ GetPName, GetProp, PutProp ], Basics USING [ Comparison ], BasicTime USING [ GMT, Now, Period, Update ], Commander USING [ CommandProc, Register ], Convert USING [ RopeFromInt, IntFromRope ], FS USING [ StreamOpen ], GVNames USING [ AddForward, AuthenticateKey, CreateIndividual, Expand, ExpandInfo, GetConnect, Outcome, RemoveForward, RListHandle, SetConnect ], IO, MBQueue USING [ Action, Create, DequeueAction, Flush, Queue, QueueClientAction ], NamesGV, Process USING [ Detach --, Pause, SecondsToTicks-- ], RefTab USING [ Create, EachPairAction, Fetch, GetSize, Pairs, Ref, Store ], SymTab USING [ Create, EachPairAction, Fetch, Pairs, Ref, Store ], Rope USING [ Cat, Concat, Find, Index, Length, ROPE, Substr ], RPC USING [ EncryptionKey ], VoiceUtils USING [ CurrentRName, CurrentPasskey, MakeAtom, Report ] ; NamesGVImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Commander, Convert, FS, GVNames, IO, MBQueue, Process, RefTab, SymTab, Rope, VoiceUtils EXPORTS NamesGV = { OPEN IO; ROPE: TYPE= Rope.ROPE; larkRegistry: ROPE=".Lark"; Results: TYPE = {ok, notFound, error}; ModeNotFound: TYPE = { ok, create, error }; -- create not available CacheBehavior: TYPE = { lookupAfter, lookupFirst, lookInCacheOnly }; Authenticity: TYPE = NamesGV.Authenticity; -- { unknown, authentic, bogus }; AttributeSeq: TYPE = NamesGV.AttributeSeq; AttributeSeqRec: TYPE = NamesGV.AttributeSeqRec; GVDetails: TYPE=REF GVDetailsR; GVDetailsR: TYPE=RECORD [ rName: ROPE, attributes: DetailsAttributes_NIL, authenticity: Authenticity_unknown, key: RPC.EncryptionKey_NULL, valid: BOOL_FALSE, canCreate: BOOL_FALSE, -- GetDetails can make one if it isn't there. mustAuthenticate: BOOL_FALSE, lastTimeValid: BasicTime.GMT, recording: BOOL_FALSE, -- Attribute update is illegal while this is TRUE dirty: BOOL_FALSE, -- Some attribute has been changed by the client refreshMode: BOOL_FALSE, -- See Note, below done: CONDITION ]; DetailsAttribute: TYPE = REF DetailsAttributeR; DetailsAttributeR: TYPE = RECORD [ attributeValue: ROPE, -- NIL only when this attribute is being deleted. formerValue: ROPE_NIL -- Non-NIL only when this attribute has changed. ]; DetailsCache: TYPE = SymTab.Ref; DetailsAttributes: TYPE = RefTab.Ref; cacheMod: CARDINAL _ 37; squawking: BOOL _ FALSE; queuesEmpty: BOOL_TRUE; queuesEmptyCond: CONDITION; gvCacheVersion: ATOM _ $V1; cache: DetailsCache_NIL; cacheQueue: MBQueue.Queue; refreshCacheQueue: MBQueue.Queue; useCurrentValue: ROPE = "Use Current Value, but replace former value, in StoreAttribute"; assumedValidInterval: INT _ 60; -- GV updates done elsewhere may take a minute, and two calls, to be noticed. assumedValidIntervalAfterChange: INT _ 600; -- GV updates done elsewhere may take a minute, and two calls, to be noticed. GV updates done elsewhere after a change here may take up to ten minutes to get noticed unless you explicitly flush the local cache. realOldTime: BasicTime.GMT _ ValidUntil[-1]; GVGetAttribute: PUBLIC ENTRY PROC[rName: ROPE, attribute: ATOM, default: ROPE_NIL] RETURNS [value: ROPE] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName].details; IF ~details.valid THEN RETURN[default]; IF attribute=$dirty THEN RETURN[IF details.dirty THEN "TRUE" ELSE NIL]; value _ FetchAttribute[details, attribute]; IF value=NIL THEN value_default; }; GVSetAttribute: PUBLIC ENTRY PROC[rName: ROPE, attribute: ATOM, value: ROPE] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: create].details; detailsAttribute: DetailsAttribute; IF ~details.valid THEN RETURN; IF (~details.dirty) OR details.recording THEN UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; -- GVWait IF details.recording THEN ERROR; -- Sanity check details.dirty _ TRUE; IF details.attributes # NIL THEN detailsAttribute _ NARROW[details.attributes.Fetch[key: attribute].val]; IF value#NIL AND value.Length[]=0 THEN value_NIL; IF detailsAttribute = NIL THEN StoreAttribute[details, attribute, value, NIL] ELSE detailsAttribute.attributeValue _ value; }; GVIsAuthenticated: PUBLIC ENTRY PROC[rName: ROPE] RETURNS [authenticity: Authenticity] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName].details; RETURN[details.authenticity]; }; GVAuthenticate: PUBLIC ENTRY PROC[rName: ROPE, key: RPC.EncryptionKey] RETURNS [authenticity: Authenticity] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: error, key: key, authenticate: TRUE].details; RETURN[details.authenticity]; }; GVGetAttributeSeq: PUBLIC PROC [rName: ROPE, attribute: ATOM] RETURNS [value: AttributeSeq] = { attributeValue: ROPE _ GVGetAttribute[rName, attribute]; index: INT_0; IF attributeValue = NIL THEN RETURN[NIL]; value _ NEW[AttributeSeqRec[10]]; WHILE index < attributeValue.Length[] DO newIndex: INT _ attributeValue.Index[pos1: index, s2: ", "]; value[value.length].attributeValue _ attributeValue.Substr[index, newIndex-index]; value.length _ value.length+1; index _ newIndex+2; ENDLOOP; }; GVSetAttributeSeq: PUBLIC PROC[rName: ROPE, attribute: ATOM, value: AttributeSeq] = { attVal: ROPE_NIL; FOR i: INT IN [0..value.length) DO attVal _ Rope.Cat[attVal, value[i].attributeValue, ", "]; ENDLOOP; IF attVal#NIL THEN attVal _ attVal.Substr[len: attVal.Length[]-2]; GVSetAttribute[rName, attribute, attVal]; }; GVGetAttributes: PUBLIC ENTRY PROC[rName: ROPE] RETURNS [value: AttributeSeq_NIL] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: create].details; len: CARDINAL; i: CARDINAL_0; GetEachAttribute: RefTab.EachPairAction = { quit_FALSE; value[i] _ [NARROW[key], NARROW[val, DetailsAttribute].attributeValue]; i_i+1; }; IF ~details.valid THEN RETURN; IF details.attributes#NIL THEN len _ details.attributes.GetSize[]; IF details.dirty THEN len_len+1; IF len=0 THEN RETURN; value _ NEW[AttributeSeqRec[len]]; value.length _ len; []_details.attributes.Pairs[GetEachAttribute]; IF details.dirty THEN value[len-1] _ [$dirty, "TRUE"]; }; GVWait: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; }; GVUpdate: PUBLIC ENTRY PROC[rName: ROPE] = { ENABLE UNWIND => NULL; SetGVDetails[GetGVDetails[rName, error].details]; }; GVUpdateAll: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; UpdateEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; IF details.dirty AND ~details.recording THEN QueueAction[refreshCacheQueue, SetNewDetails, details]; quit_FALSE; }; []_SymTab.Pairs[cache, UpdateEachRName]; }; GVFlushCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; GetGVCache[TRUE]; -- Old one fades into oblivion }; GVSaveCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; s: IO.STREAM; SaveEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; SaveEachAttribute: RefTab.EachPairAction = { attributeName: ATOM _ NARROW[key]; detailsAttribute: DetailsAttribute _ NARROW[val]; quit_FALSE; IF detailsAttribute.attributeValue=NIL OR detailsAttribute.attributeValue.Length[]=0 THEN RETURN; s.PutF["%s: %s\n", rope[Atom.GetPName[attributeName]], rope[detailsAttribute.attributeValue]]; }; quit_FALSE; IF details.recording THEN UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; -- GVWait IF ~details.valid OR details.authenticity=bogus THEN RETURN; s.PutF["rname: %s\n", rope[details.rName]]; IF details.authenticity = authentic THEN TRUSTED { key: RPC.EncryptionKey _ details.key; cardKey: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL = LOOPHOLE[LONG[@key]]; s.PutF["key: %bB %bB\n", card[cardKey[0]], card[cardKey[1]]]; }; IF details.attributes#NIL THEN []_details.attributes.Pairs[SaveEachAttribute]; IF details.dirty THEN s.PutRope["dirty: TRUE\n"]; s.PutChar['\n]; }; GetGVCache[FALSE]; s _ FS.StreamOpen["GVCacheLog.txt", $create]; IF s=NIL THEN { VoiceUtils.Report["GV:** Couldn't create GVCacheLog.txt", $System]; RETURN; }; []_cache.Pairs[SaveEachRName]; s.Close[]; }; GVRestoreCache: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; s: IO.STREAM; GetGVCache[FALSE]; s _ FS.StreamOpen["GVCacheLog.txt"]; IF s=NIL THEN { VoiceUtils.Report["GV:** Couldn't find GVCacheLog.txt", $System]; RETURN; }; GetGVCache[TRUE]; -- Old one fades into oblivion WHILE ~s.EndOf[] DO RestoreEntry[s]; ENDLOOP; s.Close[]; }; RestoreEntry: SAFE PROC[logStream: IO.STREAM] = { details: GVDetails; DO line: ROPE_ReadLine[logStream]; key: ATOM; val: ROPE; IF line=NIL OR line.Length[]=0 THEN RETURN; [key, val] _ ParseAttribute[line]; SELECT key FROM $rname => { IF details#NIL THEN ERROR; details _ NEW[GVDetailsR_[ rName: val, lastTimeValid: realOldTime, valid: TRUE, authenticity: perhaps]]; []_cache.Store[key: val, val: details]; }; $key => TRUSTED { keyStream: IO.STREAM _ IO.RIS[val]; key: RPC.EncryptionKey; cardKey:LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL = LOOPHOLE[LONG[@key]]; cardKey[0] _ IO.GetCard[keyStream]; cardKey[1] _ IO.GetCard[keyStream]; IF details=NIL THEN ERROR; details.key _ key; IO.Close[keyStream]; details.mustAuthenticate _ TRUE; details.authenticity _ authentic; }; $dirty => TRUSTED { IF details=NIL THEN ERROR; details.dirty _ TRUE; -- inhibits GV refreshes!!! }; NIL => ERROR; -- Didn't find a key! ENDCASE => { IF details=NIL THEN ERROR; StoreAttribute[details, key, val, NIL]; }; ENDLOOP; }; GetGVDetails: INTERNAL PROC[rName: ROPE, mode: ModeNotFound_ok, behavior: CacheBehavior_lookupAfter, authenticate: BOOL_FALSE, key: RPC.EncryptionKey_NULL] RETURNS [results: Results_ok, details: GVDetails_NIL] = TRUSTED { GetGVCache[FALSE]; details _ NARROW[cache.Fetch[rName].val]; IF details=NIL THEN IF behavior=lookInCacheOnly THEN RETURN[notFound, NIL] ELSE []_cache.Store[rName, details _ NEW[GVDetailsR_[rName: rName, lastTimeValid: realOldTime]]]; details.mustAuthenticate _ details.mustAuthenticate OR authenticate; IF authenticate THEN details.key _ key; details.canCreate _ ~details.valid AND mode=create; SELECT behavior FROM lookupFirst => { QueueAction[cacheQueue, LookupDetails, details]; WAIT details.done; }; lookupAfter => { dontKnow: BOOL = ~details.valid OR (details.mustAuthenticate AND (SELECT details.authenticity FROM unknown, perhaps=>TRUE, ENDCASE=>FALSE)); IF (details.mustAuthenticate AND dontKnow) OR GMTComp[BasicTime.Now[], details.lastTimeValid]=greater THEN { details.lastTimeValid _ ValidUntil[assumedValidInterval]; QueueAction[ (IF dontKnow THEN cacheQueue ELSE refreshCacheQueue), LookupDetails, details]; IF dontKnow THEN WAIT details.done; }; }; ENDCASE; details.canCreate _ FALSE; IF ~details.valid THEN RETURN[ IF mode=ok THEN notFound ELSE Report[notFound, notFound, details], details]; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV: Found %g", rope[rName]], $System]; }; SetGVDetails: INTERNAL PROC[details: GVDetails] = { GetGVCache[FALSE]; IF details=NIL OR details.dirty = FALSE THEN RETURN; details.lastTimeValid _ ValidUntil[assumedValidIntervalAfterChange]; details.recording _ TRUE; QueueAction[refreshCacheQueue, SetNewDetails, details]; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV: Modified %g", rope[details.rName]], $System]; }; LookupDetails: SAFE PROC[reallyDetails: REF ANY] = TRUSTED { details: GVDetails _ NARROW[reallyDetails]; authenticity: Authenticity_details.authenticity; rName: ROPE = details.rName; connect: ROPE; expandInfo: GVNames.ExpandInfo _ [notFound []]; info, aInfo: GVNames.Outcome_notFound; entries: GVNames.RListHandle_NIL; valid: BOOL_FALSE; RecordDetails: ENTRY PROC = CHECKED INLINE { details.rName _ rName; details.valid _ valid; details.lastTimeValid _ ValidUntil[ IF details.canCreate AND details.valid THEN assumedValidIntervalAfterChange ELSE assumedValidInterval]; details.authenticity _ authenticity; details.canCreate _ FALSE; ParseDetails[details, entries]; details.refreshMode _ FALSE; NOTIFY details.done; }; IF details.dirty AND ~details.refreshMode THEN { IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---Not seeking %g (dirty)", rope[rName]], $System]; RETURN; }; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---Seeking %g", rope[rName]], $System]; [info, connect] _ GVNames.GetConnect[rName]; valid _ info=individual; IF ~valid THEN IF info=notFound THEN { info _ DoCreate[details, "MFLFLX"]; valid _ info=individual; } ELSE []_Report[info, error, details] ELSE { expandInfo _ GVNames.Expand[rName]; WITH expandInfo SELECT FROM group => entries_members; noChange, individual => NULL; ENDCASE => []_Report[type, error, details]; }; IF connect#NIL THEN { connect _ Rope.Concat["connect: ", connect]; IF entries=NIL THEN entries _ LIST[ connect ] ELSE entries _ CONS[connect, entries]; }; IF valid AND (SELECT details.authenticity FROM bogus, authentic, nonexistent => FALSE, unknown, perhaps =>TRUE, ENDCASE=>ERROR) THEN IF details.mustAuthenticate THEN { IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---Authenticating %g", rope[rName]], $System]; SELECT (aInfo_GVNames.AuthenticateKey[rName, details.key]) FROM badPwd => authenticity_bogus; individual => authenticity_authentic; ENDCASE => []_Report[aInfo, error, details]; } ELSE authenticity _ perhaps; IF info=notFound THEN authenticity _ nonexistent; RecordDetails[]; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---End %g", rope[rName]], $System]; }; SetNewDetails: SAFE PROC[reallyDetails: REF ANY] = TRUSTED { details: GVDetails _ NARROW[reallyDetails]; outcome: GVNames.Outcome; somethingStillDirty: BOOL_FALSE; EachAttribute: RefTab.EachPairAction = TRUSTED { attribute: DetailsAttribute _ NARROW[val]; attributeName: ATOM _ NARROW[key]; quit_FALSE; IF attribute.attributeValue=attribute.formerValue THEN RETURN; IF attributeName=$connect THEN { outcome _ GVNames.SetConnect[ user: VoiceUtils.CurrentRName[], password: VoiceUtils.CurrentPasskey[], individual: details.rName, connect: attribute.attributeValue]; SELECT outcome FROM noChange, individual => attribute.formerValue _ attribute.attributeValue; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; IF attribute.formerValue # NIL AND attribute.formerValue.Length[]#0 THEN { oldFwd: ROPE_ Atom.GetPName[attributeName].Cat[": ", attribute.formerValue]; outcome _ GVNames.RemoveForward[ user: VoiceUtils.CurrentRName[], password: VoiceUtils.CurrentPasskey[], individual: details.rName, dest: oldFwd]; SELECT outcome FROM noChange, individual => attribute.formerValue _ NIL; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; IF attribute.attributeValue # NIL AND attribute.attributeValue.Length[]#0 THEN { newFwd: ROPE_ Atom.GetPName[NARROW[key]].Cat[": ", attribute.attributeValue]; outcome _ GVNames.AddForward[ user: VoiceUtils.CurrentRName[], password: VoiceUtils.CurrentPasskey[], individual: details.rName, dest: newFwd]; SELECT outcome FROM noChange, individual => attribute.formerValue _ attribute.attributeValue; ENDCASE=> { []_Report[outcome, error, details, TRUE]; somethingStillDirty _ TRUE; RETURN; }; }; }; { ENABLE UNWIND => details.recording _ FALSE; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---Change attributes for %g", rope[details.rName]], $System]; IF details.attributes # NIL THEN []_RefTab.Pairs[details.attributes, EachAttribute]; details.recording _ FALSE; details.dirty _ somethingStillDirty; IF squawking THEN VoiceUtils.Report["GV:---End update", $System]; }; }; DoCreate: PROC[details: GVDetails, password: ROPE] RETURNS [info: GVNames.Outcome] = TRUSTED { IF ~details.canCreate THEN RETURN[notFound]; IF squawking THEN VoiceUtils.Report[IO.PutFR["GV:---Creating %g", rope[details.rName]], $System]; info _ GVNames.CreateIndividual[ user: VoiceUtils.CurrentRName[], password: VoiceUtils.CurrentPasskey[], individual: details.rName, newPwd: VoiceUtils.CurrentPasskey[password]]; SELECT info FROM individual => NULL; ENDCASE => []_Report[info, error]; }; ParseDetails: INTERNAL PROC[details: GVDetails, entries: GVNames.RListHandle] = { attributeTable: RefTab.Ref_NIL; IF ~details.refreshMode THEN details.attributes_NIL; IF ~details.valid THEN RETURN; FOR e: GVNames.RListHandle _ entries, e.rest WHILE e#NIL DO key: ATOM; val: ROPE; [key, val] _ ParseAttribute[e.first]; IF key=NIL THEN LOOP; -- Not the right syntax for an entry StoreAttribute[details, key, IF details.refreshMode THEN useCurrentValue ELSE val, val]; ENDLOOP; }; ParseAttribute: PROC[attributeSpec: ROPE] RETURNS [key: ATOM_NIL, val: ROPE_NIL] = { index: INT _ attributeSpec.Find[": "]; IF index<0 THEN RETURN; -- Not the right syntax for an entry key _ VoiceUtils.MakeAtom[rName: attributeSpec.Substr[start: 0, len: index], case: FALSE]; val _ attributeSpec.Substr[start: index+2]; }; StoreAttribute: PROC[details: GVDetails, key: ATOM, val: ROPE, oldVal: ROPE] = { detailsAttribute: DetailsAttribute; IF val=useCurrentValue THEN val _ FetchAttribute[details, key]; detailsAttribute _ NEW[DetailsAttributeR _ [val, oldVal]]; IF details.attributes = NIL THEN details.attributes _ RefTab.Create[]; [] _ details.attributes.Store[key: key, val: detailsAttribute]; }; FetchAttribute: PROC[details: GVDetails, key: ATOM] RETURNS [attributeValue: ROPE] = { detailsAttribute: DetailsAttribute; IF details.attributes=NIL THEN RETURN[NIL]; detailsAttribute _ NARROW[details.attributes.Fetch[key: key].val]; RETURN[IF detailsAttribute # NIL THEN detailsAttribute.attributeValue ELSE NIL]; }; QueueAction: INTERNAL PROC [q: MBQueue.Queue, proc: PROC [REF ANY], data: REF ANY] = { queuesEmpty_FALSE; MBQueue.QueueClientAction[q, proc, data]; IF q=cacheQueue THEN MBQueue.QueueClientAction[refreshCacheQueue, Arise, NIL]; }; Arise: SAFE PROC [whatever: REF] = { NULL }; MBQueueEmpty: ENTRY PROC[q: MBQueue.Queue, notifyIfEmpty: BOOL_FALSE] RETURNS [empty: BOOL] = TRUSTED { SomethingThatLooksLikeAnMBQueue: TYPE = RECORD [ lock: CARDINAL, -- something that looks like a lock. firstEvent: LIST OF MBQueue.Action, otherStuff: CARDINAL -- and so on, but who cares? ]; qp: LONG POINTER TO SomethingThatLooksLikeAnMBQueue = LOOPHOLE[q]; empty _ qp.firstEvent=NIL; IF ~empty OR ~notifyIfEmpty THEN RETURN; queuesEmpty_TRUE; NOTIFY queuesEmptyCond; }; Action: TYPE = MBQueue.Action; Notifier: PROC [] = { DO ENABLE ABORTED => { MBQueue.Flush[cacheQueue]; MBQueue.Flush[refreshCacheQueue]; LOOP; }; UNTIL MBQueueEmpty[cacheQueue] DO WITH MBQueue.DequeueAction[cacheQueue] SELECT FROM e2: Action.client => { queuesEmpty_FALSE; e2.proc[e2.data]; }; ENDCASE => ERROR; ENDLOOP; [] _ MBQueueEmpty[refreshCacheQueue, TRUE]; -- if TRUE, about to wait WITH MBQueue.DequeueAction[refreshCacheQueue] SELECT FROM e2: Action.client => { queuesEmpty_FALSE; e2.proc[e2.data]; }; ENDCASE => ERROR; ENDLOOP; }; SemiTok: IO.BreakProc = TRUSTED {RETURN[IF char='; THEN break ELSE other]; }; CommaTok: IO.BreakProc = TRUSTED {RETURN[IF char=', THEN break ELSE other]; }; Report: PROC[outcome: GVNames.Outcome, r: Results, details: GVDetails_NIL, timeout: BOOL_FALSE] RETURNS[rr: Results] = { rName: ROPE_NIL; rr_r; IF details#NIL THEN { details.valid_FALSE; rName_details.rName; IF timeout THEN details.lastTimeValid _ realOldTime; -- next query will go to GV fer sherr }; VoiceUtils.Report[IO.PutFR["GV: **%s %s\n", rope[rName], rope[SELECT outcome FROM noChange => "no change", group => "group", individual => "individual", notFound => "not found", protocolError => "protocol error", wrongServer => "wrong server", allDown => "all servers down", badPwd => "bad password", outOfDate => "out of date", notAllowed => "not allowed", ENDCASE => "??"]], $System]; }; ValidUntil: PROC[interval: INT] RETURNS [BasicTime.GMT] = { RETURN[BasicTime.Update[BasicTime.Now[], interval]]; }; GMTComp: PUBLIC PROC[t1, t2: BasicTime.GMT] RETURNS [c: Basics.Comparison] = { period: INT = BasicTime.Period[t2, t1]; RETURN[IF period>0 THEN greater ELSE IF period=0 THEN equal ELSE less]; }; GetGVCache: INTERNAL PROC[new: BOOL_FALSE] = { cRef: REF DetailsCache _ NIL; cache_NIL; DO ref: REF _ Atom.GetProp[$GVCache, gvCacheVersion]; IF ref=NIL THEN EXIT; WITH ref SELECT FROM cRef1: REF DetailsCache => { cRef_cRef1; EXIT; }; ENDCASE; gvCacheVersion _ VoiceUtils.MakeAtom[ rName: Rope.Cat["V", Convert.RopeFromInt[ Convert.IntFromRope[ Rope.Substr[ Atom.GetPName[gvCacheVersion], 1] ]+1 ] ], case: TRUE ]; ENDLOOP; IF ~new AND cRef#NIL THEN { cache _ cRef^; RETURN}; cache _ SymTab.Create[cacheMod, FALSE]; Atom.PutProp[$GVCache, gvCacheVersion, NEW[DetailsCache _ cache]]; }; CmdSaveGVCache: Commander.CommandProc = { GVSaveCache[]; }; CmdRestoreGVCache: Commander.CommandProc = { GVRestoreCache[]; }; CmdRefreshGVCache: ENTRY Commander.CommandProc = { RefreshEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; quit_FALSE; IF details.dirty THEN details.refreshMode _ TRUE; QueueAction[refreshCacheQueue, LookupDetails, details]; }; []_SymTab.Pairs[cache, RefreshEachRName]; }; CmdUpdateGVCache: Commander.CommandProc = { GVUpdateAll[]; }; CmdFlushGVCache: Commander.CommandProc = { GVFlushCache[]; }; CmdWaitForGV: Commander.CommandProc = { GVWait[]; }; CmdGVSquawk: Commander.CommandProc = { squawking _ ~squawking; VoiceUtils.Report[IO.PutFR["Squawking[%g]", bool[squawking]], $System]; }; ReadLine: PROC[s: IO.STREAM] RETURNS [ line: ROPE] = { RETURN[s.GetLineRope[]]; }; cacheQueue _ MBQueue.Create[pushModel: FALSE]; refreshCacheQueue _ MBQueue.Create[pushModel: FALSE]; TRUSTED { Process.Detach[FORK Notifier[]]; }; Commander.Register["GVFlushCache", CmdFlushGVCache, "Flush GV Cache"]; Commander.Register["GVSaveCache", CmdSaveGVCache, "Save GV Cache"]; Commander.Register["GVRestoreCache", CmdRestoreGVCache, "Restore GV Cache"]; Commander.Register["GVRefreshCache", CmdRefreshGVCache, "Refresh all GV Cache entries from GV"]; Commander.Register["GVUpdateCache", CmdUpdateGVCache, "Write all dirty cache entries to GV"]; Commander.Register["GVWait", CmdWaitForGV, "Wait for GV communications to complete"]; Commander.Register["GVSquawk", CmdGVSquawk, "Inform user of GV activity"]; }. ØNamesGVImpl.Mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Last modified by Swinehart, October 24, 1985 4:36:45 pm PDT Last Edited by: Pier, May 20, 1984 7:08:08 pm PDT Data Note: There are two ways to store GV entries: in GV and in a local cache file. If the entry is not dirty, a cache-file version will look a lot like the GV version, and anyway, the GV version is preferred. But for cached dirty values the idea is that they should be believed first. Unfortunately, one eventually needs to know what GV thinks before one can properly update the GV entries. RefreshAllEntries sets the refreshMode bit. When the bit is set and the entry's dirty, the current GV entry is read, but only into the "formerValue" fields. Now an update operation can clear dirty and write the proper values. After fetching the former values, the refreshMode condition is cleared. NB: When you're counting on this kind of thing, you're playing with fire. There are no interlocks to be sure all the reads are done before you do the write, and that sort of thing. Be sure to use Squawk mode, or do GVWaits, or something, to be sure things are adequately synchronized. DCS 10/7/84. GV Procedures exported to VoiceUtils The following nonsense truncates the log file before beginning to write. GV Utilities DontKnow: cached copy doesn't exist, is incorrect, or is incomplete. At most one refresh try per interval Queued GV Utilities Special case: connect attribute is really RName's connect field. val=useCurrentValue: update only oldVal field, unless there's no current attribute. GV Utilities Utilities Derived from MBQueueImpl.Notifier; runs all the time. Exhaust cacheQueue before considering entries on refreshCacheQueue. When cacheQueue operations are queued, dummy refresh operations are, too, to wake us up. timeout=TRUE means that next attempt to fetch info about this RName consult Grapevine right away All copies of the same version of VoiceUtils on same machine get the same cache!! Different versions jockey for position among the gvCacheVersion entries. A cache is a SymTab.Ref, which is opaque, which means that a REF ANY containing one can't be narrowed, so it's stored inside another REF. Sigh. GV user commands Initialization Êõ˜šœ™Icodešœ Ïmœ1™J˜J˜CJ˜J˜—šœ žœž˜Jšžœ3žœ4˜pšžœ ˜Jšžœžœ˜J˜——™J™Jšžœžœžœ˜Jšœžœ ˜J˜&JšœžœÏc˜CJšœžœ1˜DJšœžœŸ!˜LJšœžœ˜*Jšœžœ˜0J˜Jšœ žœžœ ˜šœ žœžœ˜Jšœžœ˜ Jšœžœ˜"Jšœ#˜#J˜Jšœžœžœ˜Jšœžœž˜Jšœ žœžœŸ-˜DJšœžœžœ˜J˜Jšœžœ˜Jšœ žœžœŸ1˜HJšœžœžœŸ0˜CJšœ žœžœŸ˜+Jšœž ˜J˜—Jšœ#žœ žœfžœžœ–žœ+žœožœÆ™´J™¬J˜Jšœžœžœ˜/šœžœžœ˜"JšœžœŸ1˜GJšœ žœžœŸ0˜FJ˜J˜—Jšœžœ˜ Jšœžœ˜%J˜Jšœ žœ˜Jšœ žœžœ˜Jšœ žœžœ˜Jšœž œ˜Jšœžœ˜Jšœžœ˜Jšœ˜Jšœ!˜!JšœžœD˜YJ˜JšœžœŸM˜mJšœ!žœŸÓ˜ÿJšœ,˜,J˜—J™$˜šÏnœžœžœžœžœ žœ žœžœ˜RJšžœ žœ˜Jšžœžœžœ˜J˜1Jšžœžœžœ ˜'Jšžœžœžœžœžœžœžœ˜GJ˜+Jšžœžœžœ˜ J˜J˜—š œžœžœžœžœ žœ žœ˜PJšžœžœžœ˜J˜FJ˜#Jšžœžœžœ˜šžœžœž˜-Jš žœ žœžœžœŸ ˜=—JšžœžœžœŸ˜0Jšœžœ˜šžœžœž˜ Jšœžœ/˜H—Jš žœžœžœžœžœ˜1Jšžœžœžœ+žœ˜MJšžœ)˜-J˜J˜—š  œžœžœžœžœ˜1Jšžœ!˜(Jšžœžœžœ˜J˜1Jšžœ˜J˜J˜—š  œžœžœžœžœžœ˜FJšžœ!˜(Jšžœžœžœ˜JšœUžœ ˜cJšžœ˜J˜J˜—š  œžœžœ žœ žœ˜=Jšžœ˜!Jšœžœ$˜8Jšœžœ˜ Jš žœžœžœžœžœ˜)Jšœžœ˜!šžœ!ž˜(Jšœ žœ/˜J™D—Jšœ9˜9JšœŸ#™$šœ ˜ Jšœžœ žœ žœ-˜N—Jšžœ žœžœ˜#J˜—J˜—Jšžœ˜—Jšœžœ˜šžœžœžœ˜Jšžœ žœ žœ/˜L—Jšžœ žœžœ.˜TJ˜——˜š  œž œ˜3Jšœ žœ˜Jš žœ žœžœžœžœžœ˜4J˜DJšœžœ˜J˜7Jšžœ žœžœ9˜_J˜J˜——J™™š   œžœžœžœžœžœ˜˜dJšžœ˜J˜—Jšžœ žœžœ2˜XJ˜,Jšœ˜šžœžœ˜JšžœžœA˜VJšžœ ˜$—šžœ˜Jšœ#˜#šžœ ž ˜Jšœ˜Jšœžœ˜Jšžœ$˜+—J˜—šžœ žœžœ˜J˜,Jšžœ žœžœ žœ ˜-Jšžœ žœ˜&J˜—šžœžœžœž˜.Jš œ!žœžœžœžœ˜PJšž˜šžœžœ˜"šžœ ž˜Jšœžœ9˜M—šžœ5ž˜?Jšœ˜Jšœ%˜%Jšžœ%˜,—J˜—Jšžœ˜—Jšžœžœ˜1Jšœ˜Jšžœ žœžœ.˜TJ˜——˜š   œžœžœžœžœžœ˜˜>——šžœ ž˜JšœI˜Išžœ˜ Jšœ#žœ˜)Jšœžœ˜Jšžœ˜ ——J˜—šžœžœžœ"žœ˜JJšœžœ@˜Lšœ ˜ ˜GJšœ)˜)——šžœ ž˜Jšœ0žœ˜4šžœ˜ Jšœ#žœ˜)Jšœžœ˜Jšžœ˜ ——J˜—šžœžœžœ%žœ˜PJšœžœžœ+˜Mšœ˜˜GJšœ)˜)——šžœ ž˜JšœI˜Išžœ˜ Jšœ#žœ˜*Jšœžœ˜Jšžœ˜ ——J˜—J˜—šœ˜Jšžœžœžœ˜+šžœ ž˜JšœžœH˜\—Jšžœžœžœ4˜TJšœžœ˜Jšœ$˜$Jšžœ žœ0˜AJ˜—J˜J˜—š  œžœžœžœžœ˜^Jšžœžœžœ ˜,šžœ ž˜Jšœžœ;˜O—šœ ˜ JšœG˜GJšœH˜H—šžœž˜Jšœžœ˜Jšžœ˜"—Jšœ˜——˜š  œžœžœ6˜QJšœžœ˜Jšžœžœžœ˜4Jšžœžœžœ˜šžœ*žœžœž˜;Jšœžœ˜ Jšœžœ˜ J˜%Jš žœžœžœžœŸ$˜:Jšœžœžœžœ ˜XJšžœ˜—J˜J˜—š œžœžœžœžœžœžœžœ˜TJšœžœ˜&Jšžœ žœžœŸ$˜Jšžœžœ˜—Jšžœ˜—Jšœ%žœŸ˜Ešžœ*žœž˜9Jšœ#žœ˜>Jšžœžœ˜—Jšžœ˜—Jšœ˜—J™Jš œ žœ žœžœžœ žœžœ ˜MJš œ žœ žœžœžœ žœžœ ˜NJ˜š  œžœ:žœ žœžœ˜_JšœžœT™`Jšžœ˜Jšœžœžœ˜J˜šžœ žœžœ˜Jšœžœ˜*Jšžœ žœ&Ÿ%˜ZJšœ˜—šœ>žœ ž˜QJ˜J˜J˜J˜J˜"J˜J˜J˜J˜J˜Jšžœ˜——J™š  œžœ žœžœ˜