DIRECTORY Atom USING [ GetPName, GetProp, PutProp ], Commander USING [ CommandProc, Register ], Convert USING [ RopeFromInt, IntFromRope ], BasicTime USING [ Update, Now, GMT ], GVNames USING [ AddForward, AuthenticateKey, CreateIndividual, Expand, ExpandInfo, GetConnect, Outcome, RemoveForward, RListHandle, SetConnect ], IO, Log USING [ CLog, CloseCLog, MakeCLog, RedoCLog, Report, WriteCLog ], MBQueue USING [ Action, Create, DequeueAction, Flush, Queue, QueueClientAction ], MBQueueImpl USING [ QueueObj ], -- I HATE OPAQUE TYPES! Names, 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 ] ; NamesGVImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Commander, Convert, GVNames, IO, Log, MBQueue, MBQueueImpl, Names, Process, RefTab, SymTab, Rope EXPORTS NamesGV, MBQueue SHARES MBQueueImpl = { OPEN IO; Queue: TYPE = REF QueueObj; QueueObj: PUBLIC TYPE = MBQueueImpl.QueueObj; ROPE: TYPE= Names.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 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; gvCacheLog: Log.CLog _ NIL; cacheMod: CARDINAL _ 37; squawking: BOOL _ FALSE; queuesEmpty: BOOL_TRUE; queuesEmptyCond: CONDITION; gvCacheVersion: ATOM _ $V1; cache: DetailsCache_NIL; cacheQueue: Queue; refreshCacheQueue: Queue; 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] = { details: GVDetails _ GetGVDetails[rName].details; detailsAttribute: DetailsAttribute; IF ~details.valid THEN RETURN[default]; IF attribute=$dirty THEN RETURN[IF details.dirty THEN "TRUE" ELSE NIL]; IF details.attributes=NIL THEN RETURN[default]; detailsAttribute _ NARROW[details.attributes.Fetch[key: attribute].val]; IF detailsAttribute=NIL THEN RETURN[default]; RETURN[detailsAttribute.attributeValue]; }; GVSetAttribute: PUBLIC ENTRY PROC[rName: ROPE, attribute: ATOM, value: ROPE] = { 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 detailsAttribute = NIL THEN StoreAttribute[details, attribute, value, NIL] ELSE detailsAttribute.attributeValue _ value; }; GVIsAuthenticated: PUBLIC ENTRY PROC[rName: ROPE] RETURNS [authenticity: Authenticity] = { details: GVDetails _ GetGVDetails[rName].details; RETURN[details.authenticity]; }; GVAuthenticate: PUBLIC ENTRY PROC[rName: ROPE, key: RPC.EncryptionKey] RETURNS [authenticity: Authenticity] = { 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] = { 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 = { UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; }; GVUpdate: PUBLIC ENTRY PROC[rName: ROPE] = { SetGVDetails[GetGVDetails[rName, error].details]; }; GVUpdateAll: PUBLIC ENTRY PROC = { 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 = { GetGVCache[TRUE]; -- Old one fades into oblivion }; GVSaveCache: PUBLIC ENTRY PROC = { ros: IO.STREAM; SaveEachRName: INTERNAL SymTab.EachPairAction = { details: GVDetails _ NARROW[val]; SaveEachAttribute: RefTab.EachPairAction = { attributeName: ATOM _ NARROW[key]; detailsAttribute: DetailsAttribute _ NARROW[val]; quit_FALSE; ros.PutF["%s: %s\n", rope[Atom.GetPName[attributeName]], rope[detailsAttribute.attributeValue]]; }; quit_FALSE; ros _ IO.ROS[ros]; IF details.recording THEN UNTIL queuesEmpty DO WAIT queuesEmptyCond; ENDLOOP; -- GVWait IF ~details.valid OR details.authenticity=bogus THEN RETURN; ros.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]]; ros.PutF["key: %bB %bB\n", card[cardKey[0]], card[cardKey[1]]]; }; IF details.attributes#NIL THEN []_details.attributes.Pairs[SaveEachAttribute]; IF details.dirty THEN ros.PutRope["dirty: TRUE\n"]; ros.PutChar['\n]; Log.WriteCLog[gvCacheLog, IO.RopeFromROS[ros, FALSE]]; }; GetGVCache[FALSE]; gvCacheLog _ Log.MakeCLog["GVCacheLog.txt", RestoreEntry, TRUE, 3]; IF gvCacheLog=NIL THEN { Log.Report["GV:** Couldn't create GVCacheLog.txt", $System]; RETURN; }; ros _ IO.ROS[]; []_cache.Pairs[SaveEachRName]; gvCacheLog_Log.CloseCLog[gvCacheLog]; ros.Close[]; }; GVRestoreCache: PUBLIC ENTRY PROC = { GetGVCache[FALSE]; gvCacheLog _ Log.MakeCLog["GVCacheLog.txt", RestoreEntry, FALSE, 3]; IF gvCacheLog=NIL THEN { Log.Report["GV:** Couldn't find GVCacheLog.txt", $System]; RETURN; }; GetGVCache[TRUE]; -- Old one fades into oblivion Log.RedoCLog[gvCacheLog]; gvCacheLog_Log.CloseCLog[gvCacheLog]; }; 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 AND behavior#lookInCacheOnly THEN []_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 Names.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 Log.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 Log.Report[IO.PutFR["GV: Modified %g", rope[details.rName]], $System]; }; RestoreEntry: SAFE PROC[cLog: Log.CLog] = { -- DoCLog command procedure logStream: IO.STREAM = cLog.logStream; 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, val]; }; ENDLOOP; }; 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]; NOTIFY details.done; }; IF details.dirty THEN { IF squawking THEN Log.Report[IO.PutFR["GV:---Not seeking %g (dirty)", rope[rName]], $System]; RETURN; }; IF squawking THEN Log.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"] 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 Log.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 Log.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: Names.CurrentRName[], password: Names.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 THEN { oldFwd: ROPE_ Atom.GetPName[attributeName].Cat[": ", attribute.formerValue]; outcome _ GVNames.RemoveForward[ user: Names.CurrentRName[], password: Names.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 THEN { newFwd: ROPE_ Atom.GetPName[NARROW[key]].Cat[": ", attribute.attributeValue]; outcome _ GVNames.AddForward[ user: Names.CurrentRName[], password: Names.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 Log.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 Log.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 Log.Report[IO.PutFR["GV:---Creating %g", rope[details.rName]], $System]; info _ GVNames.CreateIndividual[ user: Names.CurrentRName[], password: Names.CurrentPasskey[], individual: details.rName, newPwd: Names.CurrentPasskey[password]]; SELECT info FROM individual => NULL; ENDCASE => []_Report[info, error]; }; ParseDetails: PROC[details: GVDetails, entries: GVNames.RListHandle] = { attributeTable: RefTab.Ref_NIL; 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, 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 _ Names.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 _ NEW[DetailsAttributeR _ [val, oldVal]]; IF details.attributes = NIL THEN details.attributes _ RefTab.Create[]; [] _ details.attributes.Store[key: key, val: detailsAttribute]; }; 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: Queue, notifyIfEmpty: BOOL_FALSE] RETURNS [empty: BOOL] = { empty _ q.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 }; Log.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]]; }; 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 _ Names.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 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; Log.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"]; }. XNamesGVImpl.Mesa Last modified by Swinehart, July 19, 1984 10:37:40 am PDT Last Edited by: Pier, May 20, 1984 7:08:08 pm PDT Data GV Procedures exported to Names 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. 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 Names 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 ÊȘJšœ™Jšœ9™9J™1J˜šÏk ˜ Jšœœ ˜*Jšœ œ˜+J˜+Jšœ œœ˜%Jšœœ„˜‘Jšœ˜Jšœœ<˜EJšœœD˜QJšÏb ÐbkžÐbc˜7J˜J˜J˜5Jšœœ?˜KJšœœ6˜BJšœœ%œ ˜>J˜J˜J˜—šœ œ˜Jšœ/œž œ$˜yšœž Ÿœž œ˜/Jšœœ˜J˜——™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šœœœ˜/šœœœ˜"Jšœœ¡1˜GJšœ œœ¡0˜FJ˜J˜—Jšœœ˜ Jšœœ˜%J˜Jšœœ˜Jšœ œ˜Jšœ œœ˜Jšœ œœ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœ˜Jšœ˜J˜Jšœœ¡M˜mJšœ!œ¡Ó˜ÿJšœ,˜,J˜—J™˜šÏnœœœœœ œ œœ˜RJšœ œ˜J˜1J˜#Jšœœœ ˜'Jšœœœœœœœ˜GJšœœœœ ˜/Jšœœ/˜HJšœœœœ ˜-Jšœ"˜(J˜J˜—š¢œœœœœ œ œ˜PJ˜FJ˜#Jšœœœ˜šœœ˜-Jš œ œœœ¡ ˜=—Jšœœœ¡˜0Jšœœ˜šœœ˜ Jšœœ/˜H—Jšœœœ+œ˜MJšœ)˜-J˜J˜—š ¢œœœœœ˜1Jšœ!˜(J˜1Jšœ˜J˜J˜—š ¢œœœœœœ˜FJšœ!˜(JšœUœ ˜cJšœ˜J˜J˜—š ¢œœœ œ œ˜=Jšœ˜!Jšœœ$˜8Jšœœ˜ Jš œœœœœ˜)Jšœœ˜!šœ!˜(Jšœ œ/˜œ˜^Jšœ œ¡˜0J˜Jšœ%˜%J˜J˜—J˜—J™J™ ˜š¢ œœœœLœœœœœ*œœ˜ÝJšœ œ˜Jšœ œ˜)šœ œœ˜0šœ˜Jšœ9˜<——Jšœ4œ˜DJšœœ˜'Jšœ#œ ˜3šœ ˜šœ˜Jšœ0˜0Jšœ˜J˜—šœ˜šœ œœœ˜AJš œœœœœœ˜K—šœœ ˜-šœ>œ˜DJ™D—Jšœ9˜9Jšœ¡#™$šœ ˜ Jšœœ œ œ-˜N—Jšœ œœ˜#J˜—J˜—Jšœ˜—Jšœœ˜šœœœ˜Jšœ œ œ/˜L—Jšœ œ œ.˜MJ˜——˜š¢ œ œ˜3Jšœ œ˜Jš œ œœœœœ˜4J˜DJšœœ˜J˜7Jšœ œ œ9˜XJ˜J˜—š¢ œœœ¡˜GJšœ œœ˜&J˜š˜Jšœœ˜Jšœœ˜ Jšœœ˜ Jš œœœœœ˜+J˜"šœ˜˜ Jšœ œœœ˜šœ œ ˜Jšœ/œ˜M—J˜'J˜—šœœ˜Jš œ œœœœ˜#Jšœœ˜Jšœœœœœœœœœœ˜LJšœ œ˜#Jšœ œ˜#Jšœ œœœ˜J˜Jšœ˜Jšœœ#˜BJ˜—šœ œ˜Jšœ œœœ˜Jšœœ˜1J˜—Jšœœ¡˜#šœ˜ Jšœ œœœ˜Jšœ'˜'J˜——Jšœ˜—J˜——J˜J™™š ¢ œœœœœœ˜˜]Jšœ˜J˜—Jšœ œ œ2˜QJ˜,Jšœ˜šœœ˜Jšœœ#˜8Jšœ ˜$—šœ˜Jšœ#˜#šœ ˜Jšœ˜Jšœœ˜Jšœ$˜+—J˜—šœ œœ˜J˜,Jšœ œœ œ ˜-Jšœ œ˜&J˜—šœœœ˜.Jš œ!œœœœ˜PJš˜šœœ˜"šœ ˜Jšœ œ9˜F—šœ5˜?Jšœ˜Jšœ%˜%Jšœ%˜,—J˜—Jšœ˜—Jšœœ˜1Jšœ˜Jšœ œ œ.˜MJ˜——˜š ¢ œœœœœœ˜˜>——šœ ˜JšœI˜Išœ˜ Jšœ#œ˜)Jšœœ˜Jšœ˜ ——J˜—šœœœ˜%Jšœœ@˜Lšœ ˜ ˜=Jšœ)˜)——šœ ˜Jšœ0œ˜4šœ˜ Jšœ#œ˜)Jšœœ˜Jšœ˜ ——J˜—šœœœ˜(Jšœœœ+˜Mšœ˜˜=Jšœ)˜)——šœ ˜JšœI˜Išœ˜ Jšœ#œ˜*Jšœœ˜Jšœ˜ ——J˜—J˜—šœ˜Jšœœœ˜+šœ ˜Jšœ œH˜U—Jšœœœ4˜TJšœœ˜Jšœ$˜$Jšœ œ)˜:J˜—J˜J˜—š ¢œœœœœ˜^Jšœœœ ˜,šœ ˜Jšœ œ;˜H—šœ ˜ Jšœ=˜=JšœC˜C—šœ˜Jšœœ˜Jšœ˜"—Jšœ˜——˜š¢ œœ6˜HJšœœ˜Jšœœ˜Jšœœœ˜šœ*œœ˜;Jšœœ˜ Jšœœ˜ J˜%Jš œœœœ¡$˜:J˜'Jšœ˜—J˜J˜—š¢œœœœœœœœ˜TJšœœ˜&Jšœ œœ¡$˜Jšœœ˜—Jšœ˜—Jšœ%œ¡˜Ešœ*œ˜9Jšœ#œ˜>Jšœœ˜—Jšœ˜—Jšœ˜—J™Jš œ œ œœœ œœ ˜MJš œ œ œœœ œœ ˜NJ˜š ¢œœ:œ œœ˜_JšœœT™`Jšœ˜Jšœœœ˜J˜šœ œœ˜Jšœœ˜*Jšœ œ&¡%˜ZJšœ˜—šœ7œ ˜JJ˜J˜J˜J˜J˜"J˜J˜J˜J˜J˜Jšœ˜——J™š¢ œœ œœ˜