<> <> <> <> <> DIRECTORY Booting USING [switches, RollbackProc, RegisterProcs], Convert USING [IntFromRope], File USING [IsDebugger, SystemVolume], FS USING [FileInfo, Error, StreamOpen], IO USING [ Backup, BreakProc, Close, CR, EndOfStream, Error, GetChar, GetIndex, GetRopeLiteral, GetTokenRope, int, PeekChar, PutF, PutFR, rope, SP, STREAM, TAB, time], Process USING [Detach], Rope USING [Cat, Concat, Equal, ROPE, SkipTo, Substr], RuntimeError USING [UNCAUGHT], UserCredentials USING [CredentialsChangeProc, Get, RegisterForChange], UserProfile USING [ProfileChangeReason, ProfileChangedProc] ; UserProfileImpl: CEDAR MONITOR IMPORTS Booting, Convert, File, FS, IO, Process, Rope, RuntimeError, UserCredentials EXPORTS UserProfile = BEGIN <> ProfileList: TYPE = LIST OF ProfileEntry; ProfileEntry: TYPE = REF ProfileRecord; ProfileRecord: TYPE = RECORD[key: ROPE, tokens: LIST OF ROPE, position: INT]; ROPE: TYPE = Rope.ROPE; ProfileChangedItem: TYPE = RECORD [ proc: UserProfile.ProfileChangedProc, clientData: REF ANY ]; <> Boolean: PUBLIC PROC [key: ROPE, default: BOOL _ FALSE] RETURNS [value: BOOL] = { entry: ProfileEntry = Lookup[key]; val: ROPE = GetRope[entry]; IF val = NIL THEN RETURN[default]; IF val.Equal["TRUE", FALSE] THEN RETURN[TRUE]; IF val.Equal["FALSE", FALSE] THEN RETURN[FALSE]; Report[entry, val.Concat[" is not a Boolean"]]; RETURN[default]; }; Number: PUBLIC PROC [key: ROPE, default: INT] RETURNS [value: INT] = { entry: ProfileEntry = Lookup[key]; val: ROPE = GetRope[entry]; IF val = NIL THEN RETURN[default]; value _ Convert.IntFromRope[val ! RuntimeError.UNCAUGHT => { value _ default; Report[entry, val.Concat[" is not an INT"]]; CONTINUE; } ]; }; Token: PUBLIC PROC [key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry = Lookup[key]; val: ROPE = GetRope[entry]; IF val = NIL THEN RETURN[default]; value _ val; }; ListOfTokens: PUBLIC PROC [key: ROPE, default: LIST OF ROPE] RETURNS [value: LIST OF ROPE] = { entry: ProfileEntry = Lookup[key]; IF entry = NIL THEN RETURN[default] ELSE RETURN[entry.tokens]; }; Line: PUBLIC PROC [key: ROPE, default: ROPE] RETURNS [value: ROPE] = { entry: ProfileEntry = Lookup[key]; IF entry = NIL THEN RETURN[default]; FOR l: LIST OF ROPE _ entry.tokens, l.rest UNTIL l = NIL DO IF value = NIL THEN value _ l.first ELSE value _ value.Cat[" ", l.first]; ENDLOOP; }; Lookup: ENTRY PROC [key: ROPE] RETURNS [ProfileEntry] = { ENABLE UNWIND => NULL; RETURN[LookupInternal[key]] }; LookupInternal: INTERNAL PROC [key: ROPE] RETURNS [ProfileEntry] = { IF File.IsDebugger[File.SystemVolume[]] THEN { s2: ROPE = Rope.Concat["Debugger.", key]; FOR l: ProfileList _ profileList, l.rest UNTIL l = NIL DO IF Rope.Equal[s1: l.first.key, s2: s2, case: FALSE] THEN RETURN[l.first]; ENDLOOP; }; 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; RETURN[NIL]; }; GetRope: PROC [entry: ProfileEntry] RETURNS [value: 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] = { RETURN[profileName]; }; <> profileList: ProfileList _ NIL; profileName: ROPE _ NIL; ParseProfile: ENTRY PROC [startNewErrorLog: BOOL _ FALSE] = { OPEN IO; name: ROPE; stream: IO.STREAM; CheckForFile: PROC [file: ROPE] RETURNS [found: BOOL _ TRUE] = { [] _ FS.FileInfo[file ! FS.Error => {found _ FALSE; CONTINUE}]; }; IF startNewErrorLog THEN errorLog _ NIL; profileList _ NIL; profileName _ NIL; IF Booting.switches[p] THEN RETURN; -- return default values for the profile name _ UserCredentials.Get[].name; name _ name.Substr[0, name.SkipTo[0, "."]]; profileName _ name.Concat[".profile"]; IF NOT CheckForFile[profileName] THEN profileName _ "User.profile"; { ENABLE RuntimeError.UNCAUGHT => GOTO Out; stream _ FS.StreamOpen[fileName: profileName, accessOptions: $read]; DO SkipWhite: PROC [flushLines: BOOL _ FALSE] RETURNS [c: CHAR] = { DO ENABLE IO.Error, IO.EndOfStream => GO TO stop; c _ stream.PeekChar[]; SELECT c FROM '\n => IF NOT flushLines THEN RETURN; <= 40C => {}; '- => {-- could be a comment [] _ stream.GetChar[]; IF stream.PeekChar[] # '- THEN { <> stream.Backup[c]; RETURN [c]; }; DO <> c _ stream.GetChar[]; SELECT c FROM '\n => { <> stream.Backup[c]; IF flushLines THEN EXIT; RETURN; }; '- => IF stream.PeekChar[] = '- THEN EXIT; ENDCASE; ENDLOOP; }; ENDCASE => RETURN; [] _ stream.GetChar[]; ENDLOOP; EXITS stop => {c _ 0C}; }; LocalToken: PROC [flushLines: BOOL _ FALSE] = { stop: CHAR _ SkipWhite[flushLines]; position _ stream.GetIndex[]; token _ NIL; SELECT stop FROM 0C, '\n => RETURN; '" => token _ stream.GetRopeLiteral[]; ENDCASE => token _ stream.GetTokenRope[tokenProc].token; }; tokenProc: IO.BreakProc = { RETURN[SELECT char FROM IO.SP, IO.TAB, ', => sepr, IO.CR, ': => break, ENDCASE => other ]; }; token: ROPE _ NIL; tokens, tail: LIST OF ROPE _ NIL; position: INT; key: ROPE _ NIL; LocalToken[TRUE]; IF (key _ token) = NIL THEN EXIT; SELECT SkipWhite[] FROM ': => [] _ stream.GetChar[]; -- flush the ': ENDCASE => { <> DO IF stream.GetChar[ ! IO.EndOfStream => EXIT] = '\n THEN EXIT; ENDLOOP; ReportInternal[msg: IO.PutFR["missing : at [%d]", IO.int[position]]]; LOOP; }; DO list: LIST OF ROPE _ NIL; LocalToken[]; IF token = NIL THEN EXIT; list _ LIST[token]; IF tail = NIL THEN {tail _ tokens _ list} ELSE {tail.rest _ list; tail _ list}; ENDLOOP; IF LookupInternal[key] # NIL THEN ReportInternal[ entry: LookupInternal[key], msg: IO.PutFR["%g also appears at [%d]", IO.rope[key], IO.int[position]] ]; profileList _ CONS[NEW[ProfileRecord _ [key, tokens, position]], profileList]; ENDLOOP; EXITS Out => NULL; }; IF stream # NIL THEN stream.Close[]; }; <> Report: ENTRY PROC [entry: ProfileEntry _ NIL, msg: ROPE] = { ReportInternal[entry, msg]; }; ReportInternal: INTERNAL PROC [entry: ProfileEntry _ NIL, msg: ROPE] = { ENABLE RuntimeError.UNCAUGHT => CONTINUE; IF errorLog = NIL THEN { errorLog _ FS.StreamOpen[fileName: "UserProfile.log", accessOptions: $create]; errorLog.PutF["Processing %g at %t", IO.rope[profileName], IO.time[]]; }; errorLog.PutF["\n\n%g", IO.rope[msg]]; IF entry # NIL THEN errorLog.PutF[", at %g [%d]", IO.rope[entry.key], IO.int[entry.position]]; }; GetErrorLog: PUBLIC ENTRY PROC RETURNS [fileName: ROPE] = { IF errorLog # NIL THEN { errorLog.Close[]; errorLog _ NIL; RETURN["UserProfile.log"]; } ELSE RETURN[NIL] }; errorLog: IO.STREAM _ NIL; <> <> profileChangedList: LIST OF ProfileChangedItem _ NIL; profileChangedListLast: LIST OF ProfileChangedItem _ NIL; listInUse: BOOL _ FALSE; listAvailable: CONDITION _ [timeout: 0]; CallWhenProfileChanges: PUBLIC PROC [ proc: UserProfile.ProfileChangedProc--, clientData: REF ANY _ NIL--] = { itemL: LIST OF ProfileChangedItem = CONS[[proc, --clientData--NIL], NIL]; AcquireList[]; IF profileChangedList = NIL THEN profileChangedList _ itemL ELSE profileChangedListLast.rest _ itemL; profileChangedListLast _ itemL; ReleaseList[]; DoIt[itemL.first, firstTime]; }; ProfileChanged: PUBLIC PROC [reason: UserProfile.ProfileChangeReason] = { AcquireList[]; ParseProfile[startNewErrorLog: TRUE ! RuntimeError.UNCAUGHT => CONTINUE]; FOR l: LIST OF ProfileChangedItem _ profileChangedList, l.rest UNTIL l = NIL DO DoIt[l.first, reason]; ENDLOOP; ReleaseList[]; }; DoIt: PROC [item: ProfileChangedItem, reason: UserProfile.ProfileChangeReason] = { item.proc[reason--, item.clientData-- ! RuntimeError.UNCAUGHT => { Report[msg: "Problem while executing ProfileChangedProc"]; CONTINUE } ]; }; NoticeCredentialsChange: UserCredentials.CredentialsChangeProc = { TRUSTED{Process.Detach[FORK ProfileChanged[rollBack]]}; }; NoticeRollback: Booting.RollbackProc = {ProfileChanged[rollBack]}; AcquireList: ENTRY PROC = { WHILE listInUse DO WAIT listAvailable; ENDLOOP; listInUse _ TRUE; }; ReleaseList: ENTRY PROC = { listInUse _ FALSE; BROADCAST listAvailable; }; <> UserCredentials.RegisterForChange[proc: NoticeCredentialsChange, clientData: NIL]; TRUSTED {Booting.RegisterProcs[r: NoticeRollback]}; ParseProfile[! RuntimeError.UNCAUGHT => CONTINUE]; END.