DIRECTORY Atom USING [ GetPName, GetProp, PutProp ], Basics USING [ Comparison ], BasicTime USING [ GMT, Now, nullGMT, Period, ToNSTime, FromNSTime, Update ], Commander USING [ CommandProc, Register ], Convert USING [ CardFromRope, 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, NamesGVExtras, NamesGVImpExp USING [ gvWorkingDirectory ], 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, Equal, Find, Index, Length, ROPE, Substr ], RPC USING [ EncryptionKey ], VoiceUtils USING [ CurrentRName, CurrentPasskey, MakeAtom, NetAddress, NetAddressFromRope, nullNetAddress, Report ] ; NamesGVImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Commander, Convert, FS, GVNames, IO, MBQueue, NamesGVImpExp, Process, RefTab, SymTab, Rope, VoiceUtils EXPORTS NamesGV, NamesGVExtras = { 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. timedAttributeValue: ROPE, -- valid only when the entry has not expired. formerValue: ROPE_NIL, -- Non-NIL only when this attribute has changed. expiration: BasicTime.GMT _ BasicTime.nullGMT ]; 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] ELSE { detailsAttribute.attributeValue _ value; detailsAttribute.expiration _ BasicTime.nullGMT; }; }; GVSetAttributeTimed: PUBLIC ENTRY PROC[ rName: ROPE, attribute: ATOM, value: ROPE, time: BasicTime.GMT, interval: INT] = { ENABLE UNWIND => NULL; details: GVDetails _ GetGVDetails[rName: rName, mode: create].details; detailsAttribute: DetailsAttribute; IF value=NIL OR value.Length[]=0 THEN RETURN; -- ignore null requests 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 time=BasicTime.nullGMT THEN time _ BasicTime.Update[BasicTime.Now[], interval]; IF detailsAttribute = NIL THEN StoreAttribute[details, attribute, NIL, NIL, value, time] ELSE { detailsAttribute.timedAttributeValue _ value; detailsAttribute.expiration _ time; }; }; 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[IF details=NIL THEN $unknown ELSE IF details.authenticity = $authentic AND key#details.key THEN $bogus ELSE 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; IF value.length=2 AND value[0].attributeName=$timed THEN { GVSetAttributeTimed[rName, attribute, value[1].attributeValue, BasicTime.FromNSTime[Convert.CardFromRope[value[0].attributeValue]], 0]; RETURN; }; 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], CurrentValue[NARROW[val, DetailsAttribute]]]; 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 AND detailsAttribute.attributeValue.Length[]#0 THEN s.PutF["%s: %s\n", rope[Atom.GetPName[attributeName]], rope[detailsAttribute.attributeValue]]; IF ~Expired[detailsAttribute] THEN s.PutF["timed.%s: %g %s\n", rope[Atom.GetPName[attributeName]], card[BasicTime.ToNSTime[detailsAttribute.expiration]], rope[detailsAttribute.timedAttributeValue]]; }; 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[fileName: "GVCacheLog.txt", accessOptions: $create, wDir: NamesGVImpExp.gvWorkingDirectory]; 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[fileName: "GVCacheLog.txt", wDir: NamesGVImpExp.gvWorkingDirectory]; 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[]; }; GVHostFromInstance: PUBLIC PROC[instance: ROPE] RETURNS [VoiceUtils.NetAddress_VoiceUtils.nullNetAddress] = { ENABLE UNWIND => NULL; netAddressAsRope: ROPE = GVGetAttribute[instance, $connect]; IF netAddressAsRope#NIL THEN RETURN[VoiceUtils.NetAddressFromRope[netAddressAsRope]]; }; RestoreEntry: SAFE PROC[logStream: IO.STREAM] = { details: GVDetails; DO line: ROPE_ReadLine[logStream]; key: ATOM; val: ROPE; expiration: BasicTime.GMT; IF line=NIL OR line.Length[]=0 THEN RETURN; [key, val, expiration] _ 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; ENDCASE => { IF details=NIL THEN ERROR; IF expiration # BasicTime.nullGMT THEN StoreAttribute[details, key, useCurrentValue, NIL, val, expiration] ELSE StoreAttribute[details, key, val]; }; 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, expiration: BasicTime.GMT _ BasicTime.nullGMT] = { index: INT _ attributeSpec.Find[": "]; keyRope: ROPE; IF index<0 THEN RETURN; -- Not the right syntax for an entry keyRope _ attributeSpec.Substr[start: 0, len: index]; IF Rope.Equal["timed.", keyRope.Substr[len: 6]] THEN { keyRope _ keyRope.Substr[start: 6]; attributeSpec _ attributeSpec.Substr[start: index+2]; index _ attributeSpec.Find[" "]; expiration _ BasicTime.FromNSTime[Convert.CardFromRope[attributeSpec.Substr[len: index]]]; } ELSE index _ index+1; key _ VoiceUtils.MakeAtom[rName: keyRope, case: FALSE]; val _ attributeSpec.Substr[start: index+1]; }; StoreAttribute: PROC[details: GVDetails, key: ATOM, val: ROPE, oldVal: ROPE_NIL, timedVal: ROPE_NIL, expiration: BasicTime.GMT _ BasicTime.nullGMT] = { detailsAttribute: DetailsAttribute; IF val=useCurrentValue THEN val _ FetchAttribute[details, key]; detailsAttribute _ NEW[DetailsAttributeR _ [val, timedVal, oldVal, expiration]]; 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] = { RETURN[IF details.attributes=NIL THEN NIL ELSE CurrentValue[NARROW[details.attributes.Fetch[key: key].val]]]; }; CurrentValue: PROC[detailsAttribute: DetailsAttribute] RETURNS [ROPE] = { RETURN[ IF detailsAttribute = NIL THEN NIL ELSE IF Expired[detailsAttribute] THEN detailsAttribute.attributeValue ELSE detailsAttribute.timedAttributeValue]; }; Expired: PROC[detailsAttribute: DetailsAttribute] RETURNS [expired: BOOL] = { RETURN[detailsAttribute.expiration = BasicTime.nullGMT OR BasicTime.Period[from: detailsAttribute.expiration, to: BasicTime.Now[]] > 0] }; 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, 1986 by Xerox Corporation. All rights reserved. Last modified by Swinehart, February 11, 1987 5:57:56 pm PST 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. Temporary (timed) values: July 15, 1986 9:12:43 am PDT DCS An attribute of the form :