-- File: WalnutLogImpl.mesa -- Contents: -- types and procedures for writing on the log file -- Created by: Willie-Sue on July 16, 1982 -- Last edited by: -- Willie-Sue Hoo-Hah on June 6, 1983 1:31 pm DIRECTORY DB, DateAndTime USING [Parse, Unintelligible], GVBasics USING[Timestamp], GVRetrieve USING [Handle], FileIO USING[ commitAndReopenTransOnFlush, finishTransOnClose, Open], IO, Rope, UserCredentials USING [GetUserCredentials], ViewerTools USING [TiogaContentsRec], WalnutDB USING [InitializeDBVars], WalnutDBLog, WalnutLog, WalnutSendMail, WalnutStream, WalnutWindow; WalnutLogImpl: CEDAR MONITOR IMPORTS DateAndTime, FileIO, IO, Rope, UserCredentials, DB, WalnutDB, WalnutDBLog, WalnutSendMail, WalnutStream, WalnutWindow EXPORTS WalnutLog, WalnutDBLog = BEGIN OPEN DB, WalnutLog, WalnutStream, WalnutWindow; SchemaVersionTime: PUBLIC GreenwichMeanTime_ DateAndTime.Parse["March 24, 1983 9:24 am"].dt; msgIDRope: PUBLIC ROPE_ "gvMsgID"; categoriesRope: PUBLIC ROPE_ "Categories"; bufferSize: CARDINAL = 512; copyBuffer: PUBLIC REF TEXT _ NEW[TEXT[bufferSize]]; -- one to share -- ******************************************************** logStream: IO.STREAM_ NIL; lastMsgReadFromLog: INT; -- pos in log after last msg entered in database lastMsgWrittenToLog: INT; -- pos in log at end of last msg retrieved skipRope: ROPE_ "\nUnexpected EOF, skipping ahead."; currentLogName: ROPE; -- set by call to InitializeLog -- ******************************************************** -- schemaVersion is time found in database SchemaMismatch: PUBLIC SIGNAL[schemaVersion: GreenwichMeanTime] = CODE; walnutLogInfo: PUBLIC Relation; -- used to keep current info about the log file wExpectedLength: PUBLIC Attribute; -- int (how long the log file is) wExpectedDBPos: PUBLIC Attribute; -- int (how much of the log has been parsed into Msgs) wStartExpungePos: PUBLIC Attribute; -- int (where in log file the last/current expunge started) wCopyInProgress: PUBLIC Attribute; -- bool (TRUE while Copying onto tail of log file) wSchemaVersion: PUBLIC Attribute; -- time (for keeping track of changes in walnut's schema) walnutInfoRelship: PUBLIC Relship; -- need fetch & check only once -- ******************************************************** -- see WalnutDB for parallel operation on Walnut's database -- GVMessageToLog more or less corresponds to DeclareMsg GVMessageToLog: PUBLIC ENTRY PROC[ gvH: GVRetrieve.Handle, timeStamp: GVBasics.Timestamp, gvSender: RName] RETURNS[ok: BOOL] = -- reads message items from Grapevine & makes a log entry for this message { ENABLE UNWIND => NULL; prefix: ROPE_ Rope.Cat[msgIDRope, ": ", gvSender, " $ ", RopeFromTimestamp[timeStamp], "\nCategories: Active\n"]; [lastMsgWrittenToLog, ok]_ GVLogEntry[logStream, gvH, prefix]; }; msgSetEntry: ROPE = "Domain: MsgSet\nname: %g\n\n"; msgEntry: ROPE ="Relation: mCategory\nof: %g\nis: %g\n\n"; LogCreateMsgSet: PUBLIC ENTRY PROC[msName: ROPE] = -- writes an entry for creating a msgSet { ENABLE UNWIND => NULL; []_ MakeLogEntry[logStream, insertion, IO.PutFR[msgSetEntry, IO.rope[msName]]] }; LogDestroyMsgSet: PUBLIC ENTRY PROC[msName: ROPE] = -- writes an entry for destroying a msgSet { ENABLE UNWIND => NULL; []_ MakeLogEntry[logStream, deletion, IO.PutFR[msgSetEntry, IO.rope[msName]]]; }; LogAddMsg: PUBLIC ENTRY PROC[mName: ROPE, msName: ROPE] = -- writes an entry for adding a msg to a msgSet { ENABLE UNWIND => NULL; []_ MakeLogEntry [logStream, insertion, IO.PutFR[msgEntry, IO.rope[mName], IO.rope[msName]]]; }; LogRemoveMsg: PUBLIC ENTRY PROC[mName: ROPE, msName: ROPE] = -- writes an entry for removing a msg from a msgSet { ENABLE UNWIND => NULL; []_ MakeLogEntry [logStream, deletion, IO.PutFR[msgEntry, IO.rope[mName], IO.rope[msName]]]; }; LogMsgHasBeenRead: PUBLIC ENTRY PROC[mName: ROPE] = -- writes an entry for reading a msg (displaying for the first time) { ENABLE UNWIND => NULL; []_ MakeLogEntry[logStream, hasbeenread, NIL, mName.Concat["\n"]] }; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- like GVMessageToLog, but is used by OldMailReader AddMessageToLog: PUBLIC ENTRY PROC [entryText, prefix: ROPE] = -- makes a message LogEntryType on log (used by old mail reader) { ENABLE UNWIND => NULL; lastMsgWrittenToLog_ MakeLogEntry[logStream, message, entryText, prefix] }; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * InitializeLogVars: INTERNAL PROC = BEGIN OPEN WalnutDBLog; walnutLogInfo_ DeclareRelation["walnutLogInfo", $Walnut, OldOnly]; IF walnutLogInfo#NIL THEN DO -- check schema version numbers { rs: RelshipSet; curVersion: GreenwichMeanTime; wSchemaVersion_ DeclareAttribute[walnutLogInfo, "wSchemaVersion", TimeType]; rs_ RelationSubset[walnutLogInfo]; walnutInfoRelship_ NextRelship[rs]; -- should only be one relship in this relation IF NextRelship[rs] # NIL THEN ERROR; IF walnutInfoRelship = NIL THEN {ReleaseRelshipSet[rs]; EXIT}; ReleaseRelshipSet[rs]; curVersion_ V2T[GetF[walnutInfoRelship, wSchemaVersion]]; IF curVersion#SchemaVersionTime THEN SIGNAL SchemaMismatch[curVersion]; EXIT; }; ENDLOOP ELSE { walnutLogInfo_ DeclareRelation["walnutLogInfo", $Walnut]; wSchemaVersion_ DeclareAttribute[walnutLogInfo, "wSchemaVersion", TimeType]; }; wExpectedLength_ DeclareAttribute[walnutLogInfo, "wExpectedLength", IntType]; wExpectedDBPos_ DeclareAttribute[walnutLogInfo, "wExpectedDBPos", IntType]; wStartExpungePos_ DeclareAttribute[walnutLogInfo, "wStartExpungePos", IntType]; wCopyInProgress_ DeclareAttribute[walnutLogInfo, "wCopyInProgress", BoolType]; BEGIN rs: RelshipSet_ RelationSubset[walnutLogInfo]; walnutInfoRelship_ NextRelship[rs]; -- should only be one relship in this relation IF NextRelship[rs] # NIL THEN ERROR; IF walnutInfoRelship = NIL THEN walnutInfoRelship_ CreateRelship[walnutLogInfo]; ReleaseRelshipSet[rs]; END; []_ SetF[walnutInfoRelship, wSchemaVersion, T2V[SchemaVersionTime]]; END; GetExpectedLogLength: PUBLIC ENTRY PROC RETURNS[INT] = -- Retrieves last stored log length from database (used for recovery from crash) { ENABLE UNWIND => NULL; RETURN[V2I[GetF[walnutInfoRelship, wExpectedLength]]] }; SetExpectedLogLength: PUBLIC ENTRY PROC[len: INT] = -- sets the log length in the database { ENABLE UNWIND => NULL; SetF[walnutInfoRelship, wExpectedLength, I2V[len]]; SetWalnutUpdatesPending[TRUE] }; GetExpectedDBLogPos: PUBLIC ENTRY PROC RETURNS[INT] = -- Retrieves last stored DB log pos from database (used for recovery from crash) { ENABLE UNWIND => NULL; RETURN[V2I[GetF[walnutInfoRelship, wExpectedDBPos]]] }; SetExpectedDBLogPos: PUBLIC ENTRY PROC[len: INT] = -- sets the last stored DB log position { ENABLE UNWIND => NULL; SetF[walnutInfoRelship, wExpectedDBPos, I2V[len]]; SetWalnutUpdatesPending[TRUE] }; GetStartExpungePos: PUBLIC ENTRY PROC RETURNS[INT] = -- Retrieves the time at which new mail was last retrieved { ENABLE UNWIND => NULL; RETURN[V2I[GetF[walnutInfoRelship, wStartExpungePos]]] }; SetStartExpungePos: PUBLIC ENTRY PROC[len: INT] = -- set the starting position of the next expunge { ENABLE UNWIND => NULL; SetF[walnutInfoRelship, wStartExpungePos, I2V[len]]; SetWalnutUpdatesPending[TRUE] }; GetCopyInProgress: PUBLIC ENTRY PROC RETURNS [BOOL] = -- returns TRUE if a copy onto tail of log file is in progress { ENABLE UNWIND => NULL; RETURN[V2B[GetF[walnutInfoRelship, wCopyInProgress]]] }; SetCopyInProgress: PUBLIC ENTRY PROC[doingCopy: BOOL] = -- set the copy in progress bit in the DB { ENABLE UNWIND => NULL; SetF[walnutInfoRelship, wCopyInProgress, B2V[doingCopy]]; SetWalnutUpdatesPending[TRUE]; }; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- utility routines & ones that read the log LogLength: PUBLIC ENTRY PROC[doFlush: BOOL] RETURNS[curLength: INT] = -- returns length of log file { ENABLE UNWIND => NULL; IF logStream = NIL THEN RETURN[0]; IF doFlush THEN logStream.Flush[]; RETURN[logStream.GetLength[]]; }; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -- Expunge writes on or changes the current log ExpungeMsgs: PUBLIC ENTRY PROC [tempLog: IO.STREAM, doUpdates, tailRewrite: BOOL] RETURNS[ok: BOOL] = -- Dumping is driven from the log file & preserves the bits that came from Grapevine -- Dumps all the Msgs in the database to a tempLog -- IF doUpdates it sets all msgs' body pointers to reference the NEW log and -- copies tempLog to logStream BEGIN ENABLE UNWIND => NULL; startExpungePos: INT; [startExpungePos, ok]_ ExpungeFromStream[logStream, tempLog, doUpdates, tailRewrite]; IF ~ok THEN RETURN; IF ~doUpdates THEN RETURN; Report["Copying tempLog to log file"]; SetF[walnutInfoRelship, wCopyInProgress, B2V[TRUE]]; SetF[walnutInfoRelship, wStartExpungePos, I2V[startExpungePos]]; MarkTransaction[TransactionOf[$Walnut]]; CopyTempLogToLog[tempLog, startExpungePos]; END; -- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * InitializeLog: PUBLIC ENTRY PROC[fileName: ROPE] RETURNS [curLength: INT] = -- initializes fileName as the current log stream BEGIN ENABLE UNWIND => NULL; IF logStream # NIL THEN RETURN[logStream.GetLength[]]; -- already open currentLogName_ fileName; RETURN[DoInitializeLog[]]; END; CloseLogStream: PUBLIC ENTRY PROC = { ENABLE UNWIND => NULL; IF logStream#NIL THEN logStream.Close[]; logStream_ NIL }; FinishExpunge: PUBLIC ENTRY PROC = -- called if wCopyInProgress is TRUE during startup BEGIN ENABLE UNWIND => NULL; startPos: INT_ V2I[GetF[walnutInfoRelship, wStartExpungePos]]; tempLog: IO.Handle_ FileIO.Open["Walnut.TempLog"]; CopyTempLogToLog[tempLog, startPos]; MarkTransaction[TransactionOf[$Walnut]]; END; ResetLogToExpectedLength: PUBLIC ENTRY PROC = BEGIN ENABLE UNWIND => NULL; endPos: INT_ V2I[GetF[walnutInfoRelship, wExpectedLength]]; logStream.SetIndex[endPos]; logStream.SetLength[endPos]; logStream.Flush[]; END; -- must know when the state of Walnut's segment is changing OpenWalnutTransaction: PUBLIC ENTRY PROC[trans: Transaction, noLog: BOOL] = BEGIN ENABLE UNWIND => NULL; fileNotFound, transOpen: BOOL_ FALSE; IF walnut#NIL THEN ReportRope[" Opening Walnut transaction ..."]; OpenTransaction[ segment: $Walnut, userName: WalnutSendMail.userRName, password: UserCredentials.GetUserCredentials[].password, useTrans: trans, noLog: noLog ! Error => IF code=FileNotFound THEN {fileNotFound_ TRUE; CONTINUE} ELSE IF code=TransactionAlreadyOpen THEN {transOpen_ TRUE; CONTINUE}]; IF fileNotFound OR transOpen THEN { CloseTransaction[TransactionOf[$Walnut]]; IF fileNotFound THEN DeclareSegment[walnutSegmentFile, $Walnut,,, NewOnly]; OpenTransaction[ segment: $Walnut, userName: WalnutSendMail.userRName, password: UserCredentials.GetUserCredentials[].password, useTrans: trans, noLog: noLog]; IF fileNotFound THEN MarkTransaction[TransactionOf[$Walnut]]; -- make sure segment initializated }; InitializeLogVars[]; WalnutDB.InitializeDBVars[]; lastMsgReadFromLog_ V2I[GetF[walnutInfoRelship, wExpectedDBPos]]; END; CloseWalnutTransaction: PUBLIC ENTRY PROC = BEGIN ENABLE UNWIND => NULL; len: INT; ReportRope[" Closing Walnut transaction ..."]; IF logStream#NIL THEN { len_ logStream.GetLength[]; logStream.Flush[]; IF lastMsgReadFromLog >= lastMsgWrittenToLog THEN lastMsgReadFromLog_ len; SetF[walnutInfoRelship, wExpectedLength, I2V[len]]; SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]]; }; -- funny case of aborting during initialization, log not open IF TransactionOf[$Walnut]#NIL THEN CloseTransaction[TransactionOf[$Walnut]]; SetWalnutUpdatesPending[FALSE]; Report[" ... done"]; END; MarkWalnutTransaction: PUBLIC ENTRY PROC = BEGIN ENABLE UNWIND => NULL; len: INT_ logStream.GetLength[]; IF lastMsgReadFromLog >= lastMsgWrittenToLog THEN lastMsgReadFromLog_ len; ReportRope[" Saving Walnut updates ..."]; SetF[walnutInfoRelship, wExpectedLength, I2V[len]]; SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]]; MarkTransaction[TransactionOf[$Walnut]]; SetWalnutUpdatesPending[FALSE]; Report[" ... done"]; END; TiogaTextFromLog: PUBLIC ENTRY PROC[startPos, length: INT] RETURNS[contents: TiogaContents] = BEGIN ENABLE UNWIND => NULL; contents_ WalnutSendMail.TiogaTextFromStrm[logStream, startPos-1, length+1]; -- hack for now IF contents # NIL THEN { IF contents.formatting=NIL THEN contents.contents_ Rope.Substr[contents.contents, 1]; RETURN; }; contents_ NEW[ViewerTools.TiogaContentsRec]; contents.contents_ IO.PutFR["[message body lost!!! (%g,%g)]\n", IO.int[startPos], IO.int[length]]; END; RopeFromLog: PUBLIC ENTRY PROC[startPos, length: INT] RETURNS[ROPE] = -- used by ArchiveMsgSet BEGIN ENABLE UNWIND => NULL; RETURN[RopeFromStream[logStream, startPos, length]]; END; -- ******************************************************** -- operations that read the log UpdateFromLog: PUBLIC ENTRY PROC[startPos: INT] RETURNS[success: BOOL] = -- rebuild database BEGIN ENABLE UNWIND => NULL; logWasntOpen: BOOL_ logStream = NIL; IF logWasntOpen THEN []_ DoInitializeLog[]; IF success_ UpdateFromStream[logStream, startPos] THEN { IF startPos = 0 THEN Report[" WalnutScavenge done"]; lastMsgReadFromLog_ lastMsgWrittenToLog_ logStream.GetLength[]; SetF[walnutInfoRelship, wExpectedLength, I2V[lastMsgWrittenToLog]]; SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]]} ELSE Report["\nThere were problems reading the log file."]; IF logWasntOpen THEN {logStream.Close[]; logStream_ NIL}; END; NextMsgRecFromLog: PUBLIC ENTRY PROC[startOfNextMessage: INT] RETURNS[endPos: INT, anymore: BOOL, msgRec: MsgRec] = BEGIN ENABLE UNWIND => NULL; startPos, entryLength, prefixLength: INT; entryChar: CHAR; logStream.SetIndex[startOfNextMessage]; -- Read *start* from log [startPos, prefixLength, entryLength, entryChar]_ FindStartOfEntry[logStream, TRUE]; IF entryLength <= 0 THEN RETURN[lastMsgReadFromLog_ logStream.GetIndex[], FALSE, NIL]; -- ignore all log entries except those with entryChar = '? or SP -- any other entries have been processed SELECT entryChar FROM '?, ' => -- msg entry { msgRec_ MsgRecFromStream[logStream, prefixLength, entryLength-prefixLength]; IF entryChar # '? THEN msgRec.hasBeenRead_ TRUE; lastMsgReadFromLog_ startPos+entryLength; }; ENDCASE => NULL; RETURN[lastMsgReadFromLog, TRUE, msgRec]; END; ConstructMsgID: PUBLIC PROC[mr: MsgRec] RETURNS[ROPE] = -- uses gv address of Cabernet as default -- if date is Unintelligible => use Time.Current BEGIN ts: GVBasics.Timestamp_ [net: 3, host: 14, time: 0]; IF mr.gvSender = NIL THEN { IF mr.inMsgSender # NIL THEN mr.gvSender_ mr.inMsgSender ELSE IF mr.from = NIL THEN mr.gvSender_ "UnknownSender" ELSE mr.gvSender_ mr.from}; IF mr.date = NIL THEN mr.date_ IO.PutFR[NIL, IO.time[]]; -- current time BEGIN ts.time_ DateAndTime.Parse[mr.date ! DateAndTime.Unintelligible => GOTO badTime].dt; TRUSTED {mr.dateCode_ LOOPHOLE[ts.time, GreenwichMeanTime]}; EXITS badTime => TRUSTED {mr.dateCode_ Time.Current[]}; END; -- mr.gvID_ Rope.Cat[mr.gvSender, " $ ", ~~GVBasics.RopeFromTimestamp[ts]]; mr.gvID_ Rope.Cat[mr.gvSender, " $ ", RopeFromTimestamp[ts] ]; RETURN[mr.gvID]; END; ParseMsgID: PUBLIC PROC[mr: MsgRec] = -- parses gvSender out of mr.gvID, compute dateCode BEGIN local: ROPE_ mr.gvID; mr.gvSender_ local.Substr[0, local.Find[" $ "]]; mr.dateCode_ DateAndTime.Parse[mr.date ! DateAndTime.Unintelligible => GOTO badTime].dt; EXITS badTime => TRUSTED {mr.dateCode_ Time.Current[]}; END; RopeFromTimestamp: PUBLIC PROC[ts: GVBasics.Timestamp] RETURNS [ROPE] = BEGIN OPEN IO; RETURN[PutFR["%g#%g@%g", int[ts.net], int[ts.host], time[LOOPHOLE[ts.time, GreenwichMeanTime]] ]]; END; -- ******************************************************** DoInitializeLog: INTERNAL PROC RETURNS [curLength: INT] = BEGIN logStream_ FileIO.Open[fileName: currentLogName, accessOptions: write, closeOptions: FileIO.commitAndReopenTransOnFlush+FileIO.finishTransOnClose]; RETURN[lastMsgWrittenToLog_ logStream.GetLength[]] END; CopyTempLogToLog: INTERNAL PROC[tempLog: IO.Handle, logStreamStartPos: INT] = -- copy tempLog to logStream BEGIN bytes: NAT; logStream.SetIndex[logStreamStartPos]; tempLog.SetIndex[0]; DO IF (bytes_ tempLog.GetBlock[copyBuffer]) = 0 THEN EXIT; logStream.PutBlock[copyBuffer]; ENDLOOP; lastMsgReadFromLog_ lastMsgWrittenToLog_ logStream.GetIndex[]; logStream.SetLength[lastMsgReadFromLog]; -- set the log length to current length logStream.Flush[]; tempLog.Close[]; -- we don't need this guy any more, we've copied him to log. SetF[walnutInfoRelship, wCopyInProgress, B2V[FALSE]]; SetF[walnutInfoRelship, wStartExpungePos, I2V[lastMsgReadFromLog]]; END; END.