File: WalnutLogImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
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 August 28, 1984 12:27:25 pm PDT
Last Edited by: 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, CreateRelship, DeclareAttribute, DeclareRelation,
DomainSubset, EraseSegment, GetF, GetName, GetP, MarkTransaction, NextEntity,
NextRelship, Null, OpenTransaction, RelationSubset, ReleaseEntitySet, ReleaseRelshipSet,
SetF, TransactionOf, 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, TiogaTextFromStrm],
WalnutStream
USING [ ExpungeFromStream, FindStartOfEntry, FlushAndSetCreateDate,
GVLogEntry, MakeLogEntry, MsgRecFromStream,
RopeFromStream, UpdateFromStream],
WalnutWindow
USING [excessBytesInLogFile, logIsAlpineFile, readOnlyAccess, walnut,
walnutLogName, walnutNullTrans,
Report, ReportRope, SetWalnutUpdatesPending];
WalnutLogImpl:
CEDAR
MONITOR
IMPORTS
AlpineFS, AlpineWalnutCmds, BasicTime, FS, IO, Rope, UserCredentials,
DB, 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 BOOL← TRUE;
walnutTempLogName: PUBLIC ROPE;
msgIDRope: PUBLIC ROPE← "gvMsgID";
categoriesRope: PUBLIC ROPE← "Categories";
doneRope: ROPE← " ...done";
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
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, " $ ", 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← CreateRelship[walnutLogInfo];
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[TransactionOf[currentSegment]];
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.STREAM← FS.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: BOOL← FALSE;
IF walnut#NIL THEN ReportRope[" Opening Walnut transaction ..."];
OpenTransaction[
segment: segment,
userName: WalnutSendOps.userRName,
password: UserCredentials.Get[].password,
useTrans: trans,
noLog: noLog ! Error =>
IF code=TransactionAlreadyOpen THEN {transOpen← TRUE; CONTINUE}];
IF transOpen
THEN
{ CloseTransaction[TransactionOf[segment]];
OpenTransaction[
segment: segment,
userName: WalnutSendOps.userRName,
password: UserCredentials.Get[].password,
useTrans: trans,
noLog: noLog];
};
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 TransactionOf[currentSegment]#
NIL
THEN
{ SetF[walnutInfoRelship, wExpectedLength, I2V[len]];
SetF[walnutInfoRelship, wExpectedDBPos, I2V[lastMsgReadFromLog]];
};
};
funny case of aborting during initialization, log not open
IF TransactionOf[currentSegment]#NIL THEN CloseTransaction[TransactionOf[currentSegment]];
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[TransactionOf[currentSegment]];
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[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]];
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.STREAM ← IO.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[TransactionOf[currentSegment]];
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[GetName[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.