-- 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.