WalnutLogImpl.mesa - types and procedures for writing on the log file
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
types and procedures for writing on the log file
Willie-Sue, March 20, 1985 8:59:28 am PST
Russ Atkinson (RRA) March 21, 1985 0:16:51 am PST
Donahue, August 25, 1983 2:56 pm
DIRECTORY
AlpineFS USING [StreamOptions, StreamOpen],
AlpineWalnutCmds USING [CopyForExpunge],
BasicTime USING [GMT, FromPupTime, Now, ToPupTime, Update],
DB USING [Attribute, BoolType, Entity, EntitySet, Error, GMT, IntType, Relation, Relship, RelshipSet, RopeType, TimeType, Transaction, CloseTransaction, DeclareAttribute, DeclareRelation, DeclareRelship, DomainSubset, EraseSegment, GetF, GetP, GetSegmentInfo, MarkTransaction, NameOf, NextEntity, NextRelship, Null, OpenTransaction, RelationSubset, ReleaseEntitySet, ReleaseRelshipSet, SetF, B2V, I2V, T2V, V2B, V2I, V2S, V2T],
GVBasics USING [Timestamp],
GVRetrieve USING [Handle],
FS USING [Error, defaultStreamOptions, StreamOptions, StreamOpen],
IO,
Rope,
UserCredentials USING [Get],
ViewerTools USING [TiogaContentsRec],
WalnutDB USING [mPrefixPos, MsgDomain, InitializeDBVars],
WalnutDBLog USING [ walnutLogInfo],
WalnutLog USING [MsgRec, RName, TiogaContents],
WalnutLogExtras,
WalnutSendOps USING [userRName, RFC822Date, RopeFromStream, TiogaTextFromStrm],
WalnutStream USING [ ExpungeFromStream, FindStartOfEntry, FlushAndSetCreateDate, GVLogEntry, MakeLogEntry, MsgRecFromStream, UpdateFromStream],
WalnutWindow USING [excessBytesInLogFile, logIsAlpineFile, readOnlyAccess, walnut, walnutLogName, walnutNullTrans, Report, ReportRope, SetWalnutUpdatesPending];
WalnutLogImpl: CEDAR MONITOR
IMPORTS AlpineFS, AlpineWalnutCmds, BasicTime, DB, FS, IO, Rope, UserCredentials, WalnutDB, WalnutDBLog, WalnutSendOps, WalnutStream, WalnutWindow
EXPORTS WalnutLog, WalnutLogExtras, WalnutDBLog =
BEGIN OPEN DB, WalnutLog, WalnutStream, WalnutWindow;
ROPE: TYPE = Rope.ROPE;
SchemaVersionTime: PUBLIC DB.GMT← [TimeParse["July 25, 1983 2:12 pm"]];
currentSegment: PUBLIC ATOM← $Walnut;  -- default
onlyOneTempLog: PUBLIC BOOLTRUE;
walnutTempLogName: PUBLIC ROPE;
msgIDRope: PUBLIC ROPE← "gvMsgID";
categoriesRope: PUBLIC ROPE← "Categories";
doneRope: ROPE← " ...done";
bufferSize: CARDINAL = 512;
copyBuffer: PUBLIC REF TEXTNEW[TEXT[bufferSize]];  -- one to share
********************************************************
logStream: IO.STREAMNIL;
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
alpineStreamOptions: AlpineFS.StreamOptions;
localStreamOptions: FS.StreamOptions;
********************************************************
schemaVersion is time found in database
SchemaMismatch: PUBLIC SIGNAL[schemaVersion: DB.GMT] = 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)
wLogFileName: PUBLIC Attribute; -- string (where to find the log file)
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, " $ "];
prefix ← prefix.Cat[ 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, FALSE].endStrmPos
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
InitializeLogVars: INTERNAL PROC =
BEGIN OPEN WalnutDBLog;
walnutLogInfo← DeclareRelation["walnutLogInfo", currentSegment, OldOnly];
IF walnutLogInfo#NIL THEN DO  -- check schema version numbers
{ rs: RelshipSet;
curVersion: BasicTime.GMT;
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 SchemaVersionTime.time#curVersion THEN SIGNAL SchemaMismatch[[curVersion]];
EXIT;
};
ENDLOOP
ELSE
{ walnutLogInfo← DeclareRelation["walnutLogInfo", currentSegment];
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];
wLogFileName← DeclareAttribute[walnutLogInfo, "wLogFileName", RopeType];
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 ← DeclareRelship[r: walnutLogInfo, version: NewOnly];
ReleaseRelshipSet[rs];
END;
IF ~WalnutWindow.readOnlyAccess THEN
[]← SetF[walnutInfoRelship, wSchemaVersion, T2V[SchemaVersionTime]];
END;
GetCurrentLogFile: PUBLIC ENTRY PROC RETURNS[ROPE] =
Retrieves name for current log file from database
{ ENABLE UNWIND => NULL;
RETURN[V2S[GetF[walnutInfoRelship, wLogFileName]]]
};
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[-1];
IF doFlush THEN FlushAndSetCreateDate[logStream];
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
phase: INTEGER← 1;
BEGIN ENABLE UNWIND =>
{ IF phase = 1 THEN Report[" Expunge NOT done"]
ELSE IF phase = 2 THEN
Report[IO.PutFR[" Copy of %g NOT done; will be done after restart",
IO.rope[walnutTempLogName]]];
IF tempLog#NIL THEN tempLog.Close[];
};
startExpungePos: INT;
IF tempLog # NIL THEN { tempLog.Close[]; tempLog← NIL};
[startExpungePos, ok, tempLog]← ExpungeFromStream[logStream, doUpdates, tailRewrite];
IF ~ok OR ~doUpdates THEN
{ IF tempLog#NIL THEN tempLog.Close[]; RETURN};
SetF[walnutInfoRelship, wCopyInProgress, B2V[TRUE]];
SetF[walnutInfoRelship, wStartExpungePos, I2V[startExpungePos]];
MarkTransaction[GetSegmentInfo[currentSegment].trans];
phase← 2;
CopyTempLogToLog[tempLog, startExpungePos];
IF startExpungePos = 0 THEN FinishFullExpunge[];
END;
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
};
AbortLogTransaction: PUBLIC ENTRY PROC =
{ ENABLE UNWIND => NULL;
log: IO.STREAM← logStream;
logStream← NIL;
IF log#NIL THEN log.Close[abort: TRUE];
};
FinishExpunge: PUBLIC ENTRY PROC =
called if wCopyInProgress is TRUE during startup
BEGIN ENABLE UNWIND => NULL;
startPos: INT← V2I[GetF[walnutInfoRelship, wStartExpungePos]];
tempLog: IO.STREAMFS.StreamOpen[walnutTempLogName];
CopyTempLogToLog[tempLog, startPos];
IF startPos = 0 THEN FinishFullExpunge[];
END;
FinishFullExpunge: INTERNAL PROC =
BEGIN
InternalCloseWalnutTransaction[];
DB.EraseSegment[segment: currentSegment];
InternalOpenWalnutTransaction[currentSegment, walnutNullTrans, TRUE]; -- no trans, noLog
[]← InternalUpdateFromLog[0, FALSE];
SetF[walnutInfoRelship, wStartExpungePos, I2V[logStream.GetLength[]]];
InternalCloseWalnutTransaction[];
InternalOpenWalnutTransaction[currentSegment, NIL, FALSE] -- with transaction
END;
ResetLogToExpectedLength: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
endPos: INT← V2I[GetF[walnutInfoRelship, wExpectedLength]];
logStream.SetIndex[endPos];
logStream.SetLength[endPos];
FlushAndSetCreateDate[logStream];
END;
must know when the state of Walnut's segment is changing
OpenWalnutTransaction:
PUBLIC ENTRY PROC[segment: ATOM, trans: Transaction, noLog: BOOL] =
BEGIN ENABLE UNWIND => NULL;
InternalOpenWalnutTransaction[segment, trans, noLog];
END;
InternalOpenWalnutTransaction:
INTERNAL PROC[segment: ATOM, trans: Transaction, noLog: BOOL] =
BEGIN
transOpen: BOOLFALSE;
IF walnut#NIL THEN ReportRope[" Opening Walnut transaction ..."];
OpenTransaction[
segment: segment,
userName: WalnutSendOps.userRName,
password: UserCredentials.Get[].password,
useTrans: trans ! Error =>
IF code=TransactionAlreadyOpen THEN {transOpen← TRUE; CONTINUE}];
IF transOpen THEN
{ CloseTransaction[GetSegmentInfo[segment].trans];
OpenTransaction[
segment: segment,
userName: WalnutSendOps.userRName,
password: UserCredentials.Get[].password,
useTrans: trans];
};
currentSegment← segment;
InitializeLogVars[];
WalnutDB.InitializeDBVars[];
lastMsgReadFromLog← V2I[GetF[walnutInfoRelship, wExpectedDBPos]];
Report[doneRope];
END;
CloseWalnutTransaction: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
InternalCloseWalnutTransaction[];
END;
InternalCloseWalnutTransaction: INTERNAL PROC =
BEGIN
len: INT;
ReportRope[" Closing Walnut transaction ..."];
IF logStream#NIL AND ~readOnlyAccess THEN
{ len← logStream.GetLength[];
FlushAndSetCreateDate[logStream];
IF lastMsgReadFromLog >= lastMsgWrittenToLog THEN lastMsgReadFromLog← len;
might have been aborted
IF GetSegmentInfo[currentSegment].trans#NIL THEN
{ SetF[walnutInfoRelship, wExpectedLength, I2V[len]];
SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]];
};
};
funny case of aborting during initialization, log not open
IF GetSegmentInfo[currentSegment].trans#NIL THEN CloseTransaction[GetSegmentInfo[currentSegment].trans];
SetWalnutUpdatesPending[FALSE];
Report[doneRope];
END;
MarkWalnutTransaction: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
MarkTrans[doReport: TRUE];
END;
MarkTrans: INTERNAL PROC[doReport: BOOL] =
BEGIN
len: INT;
IF readOnlyAccess THEN RETURN;
IF logStream # NIL THEN
{ len← logStream.GetLength[];
IF lastMsgReadFromLog >= lastMsgWrittenToLog THEN lastMsgReadFromLog← len;
SetF[walnutInfoRelship, wExpectedLength, I2V[len]];
SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]];
};
IF doReport THEN ReportRope["\nSaving Walnut updates ..."];
MarkTransaction[GetSegmentInfo[currentSegment].trans];
SetWalnutUpdatesPending[FALSE];
IF doReport THEN Report[doneRope];
END;
QuietlyMarkTransaction: PUBLIC ENTRY PROC =
BEGIN ENABLE UNWIND => NULL;
MarkTrans[doReport: FALSE];
END;
TiogaTextFromLog: PUBLIC ENTRY PROC[startPos, length: INT]
RETURNS[contents: TiogaContents] =
BEGIN ENABLE UNWIND => NULL;
contents← WalnutSendOps.TiogaTextFromStrm[logStream, startPos-1, length+1];
all Tioga texts must begin with a carriage return, which we get from the preceding header
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[WalnutSendOps.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;
RETURN[InternalUpdateFromLog[startPos, TRUE]];
END;
InternalUpdateFromLog: INTERNAL PROC[startPos: INT, doReport: BOOL]
  RETURNS[success: BOOL] =
BEGIN
logWasntOpen: BOOL← logStream = NIL;
IF logWasntOpen THEN []← DoInitializeLog[];
IF success← UpdateFromStream[logStream, startPos] THEN
{ IF startPos = 0 AND doReport 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 => {lastMsgReadFromLog← startPos + entryLength}; -- after this entry
RETURN[lastMsgReadFromLog, TRUE, msgRec];
END;
ConstructMsgID: PUBLIC PROC[mr: MsgRec] RETURNS[ROPE] =
uses gv address of Cabernet as default
if date is Unintelligible => use BasicTime.Now
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← WalnutSendOps.RFC822Date[]; -- current time
mr.dateCode← TimeParse[mr.date];
ts.time← BasicTime.ToPupTime[mr.dateCode];
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;
date: ROPE;
pos: INT;
mr.gvSender← local.Substr[0, pos← local.Find[" $ "]];
IF mr.date.Length[] = 0 THEN mr.date← WalnutSendOps.RFC822Date[]; -- current time
date← local.Substr[local.Find["@", pos]+1];
mr.dateCode← TimeParse[date];
END;
TimeParse: PROC[date: ROPE] RETURNS[dateCode: BasicTime.GMT] =
BEGIN
ris: IO.STREAMIO.RIS[date];
dateCode← IO.GetTime[ris ! IO.Error => GOTO badTime];
EXITS
badTime => dateCode← BasicTime.Now[];
END;
RopeFromTimestamp: PUBLIC PROC[ts: GVBasics.Timestamp] RETURNS [ROPE] =
BEGIN
tr: ROPE← WalnutSendOps.RFC822Date[BasicTime.FromPupTime[ts.time]];
RETURN[IO.PutFR["%g#%g@%g", IO.int[ts.net], IO.int[ts.host], IO.rope[tr]]];
END;
GenerateNewMsgID: PUBLIC PROC[mr: MsgRec] =
used to create a new id for a msg (probably from an OldMailReader file)
BEGIN
lc: LONG CARDINAL← BasicTime.ToPupTime[BasicTime.Update[mr.dateCode, 11]];
ts: GVBasics.Timestamp← [net: 3, host: 14, time: lc];
mr.dateCode← BasicTime.FromPupTime[lc];
mr.gvID← Rope.Cat[mr.gvSender, " $ ", RopeFromTimestamp[ts]];
END;
********************************************************
DoInitializeLog: INTERNAL PROC RETURNS [curLength: INT] =
BEGIN
IF WalnutWindow.logIsAlpineFile THEN
BEGIN
IF WalnutWindow.readOnlyAccess THEN
logStream←
AlpineFS.StreamOpen[currentLogName, $read, alpineStreamOptions]
ELSE
logStream← AlpineFS.StreamOpen[currentLogName, $write, alpineStreamOptions ! FS.Error =>
IF error.group = user AND error.code = $unknownFile THEN GOTO newFileNeeded
ELSE REJECT];
EXITS
newFileNeeded =>
logStream← AlpineFS.StreamOpen[currentLogName, $create, alpineStreamOptions];
END
ELSE BEGIN
IF WalnutWindow.readOnlyAccess THEN
logStream← FS.StreamOpen[currentLogName, $read, localStreamOptions]
ELSE
logStream← FS.StreamOpen[currentLogName, $write, localStreamOptions ! FS.Error =>
IF error.group = user AND error.code = $unknownFile THEN GOTO newFileNeeded
ELSE REJECT];
EXITS
newFileNeeded => logStream← FS.StreamOpen[currentLogName, $create, localStreamOptions];
END;
RETURN[lastMsgWrittenToLog← logStream.GetLength[]]
END;
CopyTempLogToLog: INTERNAL PROC[tempLog: IO.STREAM, logStreamStartPos: INT] =
copy tempLog to logStream
BEGIN
bytes: NAT;
Report[IO.PutFR["Copying %g to %g, starting at position %g",
  IO.rope[walnutTempLogName], IO.rope[currentLogName], IO.int[logStreamStartPos]]];
tempLog.SetIndex[0];
IF logIsAlpineFile AND logStreamStartPos=0 THEN
{ logStream.Close[];
logStream← NIL;
tempLog.Close[];
AlpineWalnutCmds.CopyForExpunge[
walnutLogName, walnutTempLogName, excessBytesInLogFile];
must reopen the log stream
lastMsgReadFromLog← DoInitializeLog[];
}
ELSE
{ logStream.SetIndex[logStreamStartPos];
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
FlushAndSetCreateDate[logStream];
tempLog.Close[];
};
SetF[walnutInfoRelship, wCopyInProgress, B2V[FALSE]];
SetF[walnutInfoRelship, wStartExpungePos, I2V[lastMsgReadFromLog]];
SetF[walnutInfoRelship, wExpectedLength, I2V[lastMsgReadFromLog]];
SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]];
MarkTransaction[GetSegmentInfo[currentSegment].trans];
END;
* * * * * * * * * *
for debugging, called from exec with NOTHING ELSE happening
CheckLogPointers: PROC RETURNS[ans: ROPE] =
BEGIN
es: EntitySet;
msg: Entity;
firstLine: ROPE;
num, numBad: INT← 0;
BEGIN ENABLE UNWIND =>
{IF es#NIL THEN ReleaseEntitySet[es ! UNWIND => CONTINUE]};
es← DomainSubset[WalnutDB.MsgDomain];
Report["\nChecking log pointers for all msgs"];
UNTIL DB.Null[msg← NextEntity[es]] DO
logPrefixPos: INT← V2I[GetP[msg, WalnutDB.mPrefixPos]];
logStream.SetIndex[logPrefixPos];
firstLine← logStream.GetLineRope[];
IF (num← num + 1) MOD 10 = 0 THEN
ReportRope[IF num MOD 100 = 0 THEN "!" ELSE "~"];
IF Rope.Equal[firstLine, "*start*"] THEN LOOP;
numBad← numBad + 1;
Report[IO.PutFR["\nMsg: %g is listed at %g", IO.rope[NameOf[msg]], IO.int[logPrefixPos]]];
ENDLOOP;
ReleaseEntitySet[es];
IF numBad # 0 THEN
Report[IO.PutFR["There are %g bad messages in the database", IO.int[numBad]]];
END;
RETURN[IO.PutFR["There are %g messages in the database", IO.int[num]]];
END;
********************************************************
alpineStreamOptions← [tiogaRead: FALSE, truncatePagesOnClose: FALSE];
localStreamOptions←
[ tiogaRead: FALSE,
commitAndReopenTransOnFlush: TRUE,
truncatePagesOnClose: FALSE,
finishTransOnClose: TRUE,
closeFSOpenFileOnClose: TRUE];
END.