<> <> DIRECTORY AMBridge USING [TVForProc], AMEvents USING [Debugging, Debugged], AMTypes USING [GlobalParent, TVToName, TV], CedarSnapshot USING [CheckpointProc, Register, RollbackProc], Convert USING [Value, Parse, ValueToRope], Directory USING [Lookup, ignore, Error], IO USING [Close, CR, CreateFilterCommentsStream, CreateOutputStreamToRope, EndOf, GetChar, GetIndex, GetOutputStreamRope, int, PeekChar, PutF, PutSignal, rope, STREAM, time, TAB, SP,GetRope, GetToken, SkipOver, TokenProc, BreakProc, WhiteSpace], FileIO USING [Open], PilotSwitches USING [switches], Rope USING [Cat, Concat, Equal, Find, ROPE, Substr, ToRefText], UserCredentials USING [GetUserCredentials], UserProfile USING [ProfileChangeReason, ProfileChangedProc] ; UserProfileImpl: CEDAR MONITOR IMPORTS CedarSnapshot, Convert, FileIO, UserCredentials, IO, Rope, Directory, PilotSwitches, AMTypes, AMBridge, AMEvents EXPORTS UserProfile = BEGIN OPEN UserProfile; <> ProfileList: TYPE = LIST OF ProfileEntry; ProfileEntry: TYPE = REF ProfileRecord; ProfileRecord: TYPE = RECORD[key: Rope.ROPE, tokens: LIST OF Rope.ROPE, position: INT]; <> Boolean: PUBLIC PROCEDURE [key: Rope.ROPE, default: BOOLEAN _ FALSE] RETURNS [value: BOOLEAN] = { entry: ProfileEntry = Lookup[key]; val: Rope.ROPE = GetRope[entry]; IF val = NIL THEN RETURN[default]; IF Rope.Equal[val, "TRUE", FALSE] THEN RETURN[TRUE]; IF Rope.Equal[val, "FALSE", FALSE] THEN RETURN[FALSE]; Report[entry, Rope.Concat[val, " is not a Boolean"]]; RETURN[default]; }; Number: PUBLIC PROCEDURE [key: Rope.ROPE, default: INT] RETURNS [value: INT] = { entry: ProfileEntry = Lookup[key]; val: Rope.ROPE = GetRope[entry]; IntFromRope: PROCEDURE [rope: Rope.ROPE] RETURNS [INT] = { v: Convert.Value = Convert.Parse[[rope[rope]]].value; RETURN [NARROW[v, Convert.Value[signed]].signed]; }; IF val = NIL THEN RETURN[default]; value _ IntFromRope[val ! ANY => {value _ default; Report[entry, Rope.Concat[val, " is not an INT"]]; CONTINUE; }] }; Token: PUBLIC PROCEDURE [key: Rope.ROPE, default: Rope.ROPE] RETURNS [value: Rope.ROPE] = { entry: ProfileEntry = Lookup[key]; val: Rope.ROPE = GetRope[entry]; IF val = NIL THEN RETURN[default]; value _ val; }; ListOfTokens: PUBLIC PROCEDURE [key: Rope.ROPE, default: LIST OF Rope.ROPE] RETURNS [value: LIST OF Rope.ROPE] = { entry: ProfileEntry = Lookup[key]; IF entry = NIL THEN RETURN[default] ELSE RETURN[entry.tokens]; }; Line: PUBLIC PROCEDURE [key: Rope.ROPE, default: Rope.ROPE] RETURNS [value: Rope.ROPE] = { entry: ProfileEntry = Lookup[key]; IF entry = NIL THEN RETURN[default]; FOR l: LIST OF Rope.ROPE _ entry.tokens, l.rest UNTIL l = NIL DO IF value = NIL THEN value _ l.first ELSE value _ Rope.Cat[value, " ", l.first]; ENDLOOP; }; Lookup: PROCEDURE [key: Rope.ROPE] RETURNS [ProfileEntry _ NIL] = INLINE { FOR l: ProfileList _ profileList, l.rest UNTIL l = NIL DO IF Rope.Equal[s1: l.first.key, s2: key, case: FALSE] THEN RETURN[l.first]; ENDLOOP; }; GetRope: PROCEDURE [entry: ProfileEntry] RETURNS [value: Rope.ROPE] = INLINE { IF entry = NIL OR entry.tokens = NIL THEN RETURN[NIL]; value _ entry.tokens.first; IF entry.tokens.rest # NIL THEN Report[entry, "extra material on line"]; }; GetProfileName: PUBLIC PROC RETURNS [Rope.ROPE] = { RETURN[profileName]; }; <> profileList: ProfileList _ NIL; profileName: Rope.ROPE _ NIL; ParseProfile: ENTRY PROCEDURE = { OPEN IO; name: Rope.ROPE; i: INT; stream: IO.STREAM; CheckForFile: PROC [file: Rope.ROPE] RETURNS [found: BOOLEAN] = TRUSTED { -- Directory fName: LONG STRING; fName _ LOOPHOLE[Rope.ToRefText[file]]; found _ TRUE; [] _ Directory.Lookup[fileName: fName, permissions: Directory.ignore ! Directory.Error => {found _ FALSE; CONTINUE}]; }; profileList _ NIL; profileName _ NIL; IF PilotSwitches.switches.n = down THEN RETURN; -- return default values name _ UserCredentials.GetUserCredentials[].name; i _ Rope.Find[name, "."]; IF i # -1 THEN name _ Rope.Substr[base: name, len: i]; profileName _ Rope.Concat[name, ".profile"]; IF NOT CheckForFile[profileName] THEN profileName _ "User.profile"; { ENABLE { UNWIND, AMEvents.Debugging, AMEvents.Debugged => NULL; ANY => GOTO Out; }; stream _ CreateFilterCommentsStream[FileIO.Open[fileName: profileName, accessOptions: read, createOptions: oldOnly]]; DO tokenProc: IO.BreakProc = { RETURN[SELECT char FROM IO.SP, IO.TAB, ', => sepr, IO.CR => break, ENDCASE => other ]; }; whiteSpace: IO.BreakProc = { RETURN[IF char = CR THEN break ELSE IO.WhiteSpace[char]] }; isACR: IO.BreakProc = { RETURN[IF char = CR THEN break ELSE other] }; key, token: Rope.ROPE; tokens, tail: LIST OF Rope.ROPE; position: INT; position _ stream.GetIndex[]; key _ IO.GetToken[stream]; <<-- see if key is followed by a :>> IO.SkipOver[stream, whiteSpace]; -- skips over spaces, tabs, but stops at CR IF stream.PeekChar[] # ': THEN {IO.SkipOver[stream, isACR]; Report1[msg: Rope.Cat["missing : at [", Convert.ValueToRope[[signed[position]]], "]"]]; LOOP; }; [] _ stream.GetChar[]; -- the : IO.SkipOver[stream, whiteSpace]; IF stream.PeekChar[] = '" THEN tokens _ LIST[IO.GetRope[stream]] ELSE UNTIL stream.EndOf[] OR Rope.Equal[(token _ IO.GetToken[stream, tokenProc]), "\n"] DO l: LIST OF Rope.ROPE = LIST[token]; IF tail = NIL THEN {tail _ l; tokens _ l} ELSE {tail.rest _ l; tail _ l}; ENDLOOP; IF Lookup[key] # NIL THEN Report1[entry: Lookup[key], msg: Rope.Cat[key, " also appears at [", Convert.ValueToRope[[signed[position]]], "]"]]; profileList _ CONS[NEW[ProfileRecord _ [key, tokens, position]], profileList]; ENDLOOP; EXITS Out => NULL; }; IF stream # NIL THEN stream.Close[]; }; <> Report: ENTRY PROCEDURE [entry: ProfileEntry _ NIL, msg: Rope.ROPE] = { Report1[entry, msg]; }; Report1: INTERNAL PROCEDURE [entry: ProfileEntry _ NIL, msg: Rope.ROPE] = { OPEN IO; ENABLE { UNWIND, AMEvents.Debugging, AMEvents.Debugged => NULL; ANY => CONTINUE; }; IF errorLog = NIL THEN {errorLog _ FileIO.Open[fileName: "UserProfile.log", accessOptions: overwrite]; errorLog.PutF["Processing %g at %t", rope[profileName], time[]]; }; errorLog.PutF["\n\n%g", rope[msg]]; IF entry # NIL THEN errorLog.PutF[", at %g [%d]", rope[entry.key], int[entry.position]]; }; GetErrorLog: PUBLIC ENTRY PROC RETURNS [fileName: Rope.ROPE] = { IF errorLog # NIL THEN {errorLog.Close[]; errorLog _ NIL; RETURN["UserProfile.log"]; } ELSE RETURN[NIL] }; errorLog: IO.STREAM _ NIL; <> profileChangedList: LIST OF ProfileChangedProc _ NIL; CallWhenProfileChanges: PUBLIC PROC [proc: ProfileChangedProc] = { profileChangedList _ CONS[proc, profileChangedList]; DoIt[proc, firstTime]; }; ProfileChanged: PUBLIC PROC [reason: ProfileChangeReason] = { errorLog _ NIL; ParseProfile[! UNWIND, AMEvents.Debugging, AMEvents.Debugged => NULL; ANY => CONTINUE]; FOR l: LIST OF ProfileChangedProc _ profileChangedList, l.rest UNTIL l = NIL DO DoIt[l.first, reason]; ENDLOOP; }; DoIt: PROC [proc: ProfileChangedProc, reason: ProfileChangeReason] = { inner: PROC = { proc[reason ! UNWIND, AMEvents.Debugging, AMEvents.Debugged => NULL; ANY => { OPEN IO; stream: STREAM; procTV: AMTypes.TV; stream _ CreateOutputStreamToRope[]; TRUSTED {procTV _ AMBridge.TVForProc[proc]}; stream.PutF["Problem while executing ProfileChangedProc %g.%g: ", rope[AMTypes.TVToName[AMTypes.GlobalParent[procTV]]], rope[AMTypes.TVToName[procTV]]]; IO.PutSignal[stream]; Report[msg: GetOutputStreamRope[stream]]; CONTINUE; }; ]; }; inner[! UNWIND, AMEvents.Debugging, AMEvents.Debugged => NULL; ANY => CONTINUE; -- to bulletproof, in case something goes wrong with the AMTypes stuff in the ANY inside of DoIt1. ]; }; NoticeChanges: CedarSnapshot.RollbackProc = {ProfileChanged[rollBack]}; <> TRUSTED {CedarSnapshot.Register[c: NIL, r: NoticeChanges]; }; ParseProfile[! ANY => CONTINUE]; END. April 27, 1982 4:52 pm fixed Open to first check for username.profile and use User.Profile only if no profile for that user name June 2, 1982 2:24 pm When booting with n switches down, always return default value without looking at disk June 8, 1982 5:49 pm added CallWhenProfileChanges and ProfileChangedProc. Changed GetLineValue to recognize " and read entire rope to matching ". June 17, 1982 10:24 pm added ENABLE UNWIND => NULL to entry procedures. Changed OPEN not to remember username if no specific username.profile, so that when one appears, it is not necessary for user to boot, or login as somebody else and then login as himself again, in order to get the profile noticed. July 17, 1982 11:53 pm added checkpoint proc to check to see if profile is open and if so, close it. Fixed DoIt to restore openCount in case of problem with ProfileProc. September 15, 1982 9:41 pm defined CreateFilterCommentsStream to filter out comments December 3, 1982 1:57 pm major changes: parse userprofile into data structure. Add mechanism for reporting errors to log., December <<>> <> <> <> <> <> <> <> <<>> <<>> <<>> <<>> <<>>