WalnutStreamImpl.mesa - procedures for reading & writing Walnut log-Style files
Copyright © 1985 by Xerox Corporation. All rights reserved.
Created by: Willie-Sue on April 27, 1983
Willie-Sue on December 5, 1983 11:27 am
Russ Atkinson (RRA) March 21, 1985 0:47:59 am PST
This module is NOT a MONITOR; its assumes that its caller has a lock on the stream if that is appropriate.
DIRECTORY
AlpineFS USING [OpenFileFromStream],
BasicTime USING [Now],
FS USING [OpenFile, OpenFileFromStream, SetByteCountAndCreatedTime],
IO,
GVBasics USING [ItemHeader, ItemType],
GVRetrieve USING [Failed, GetItem, Handle, NextItem],
Rope,
RuntimeError USING [BoundsFault],
WalnutLog USING [LogEntryType, MsgRec, MessageRecObject, minPrefixLength, categoriesRope, copyBuffer, msgIDRope, AddMessageToLog, ConstructMsgID, ParseMsgID],
WalnutRetrieve USING [ParseMsgIntoFields],
WalnutSendOps USING [RopeFromStream],
WalnutStream,
WalnutWindow
USING [logIsAlpineFile, Report, ReportRope];
WalnutStreamImpl:
CEDAR
PROGRAM
IMPORTS AlpineFS, BasicTime, FS, IO, GVRetrieve, Rope, RuntimeError, WalnutLog, WalnutRetrieve, WalnutSendOps, WalnutWindow
EXPORTS WalnutStream = { OPEN WalnutLog;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TiogaCTRL: GVBasics.ItemType;
********************************************************
ReadPrefixInfo:
PUBLIC
PROC[strm:
STREAM, headersPos:
INT]
RETURNS[msgID:
ROPE, categories:
ROPE, outOfSynch:
BOOL] = {
curPos: INT;
outOfSynch← FALSE;
UNTIL (curPos← strm.GetIndex[]) = headersPos
DO
tag, value: ROPE;
IF curPos > headersPos THEN {outOfSynch← TRUE; RETURN};
[tag, value]← TagAndValue[strm: strm, inc: 2];
IF Rope.Equal[tag, msgIDRope,
FALSE]
THEN msgID← value
ELSE IF Rope.Equal[tag, categoriesRope, FALSE] THEN categories← value;
ENDLOOP;
};
TagAndValue:
PUBLIC
PROC[strm:
STREAM, inc:
INT]
RETURNS[tag, value:
ROPE] = {
line: ROPE← strm.GetLineRope[];
pos: INT← line.Find[":"];
IF pos < 0 THEN RETURN;
tag← line.Substr[0, pos];
value← line.Substr[pos+inc, Rope.MaxLen ! RuntimeError.BoundsFault =>
{tag← NIL; CONTINUE}];
};
FindStartOfEntry:
PUBLIC
PROC[strm:
STREAM, doReport:
BOOL]
RETURNS[startPos, prefixLength, entryLength:
INT, entryChar:
CHAR] = {
ENABLE IO.EndOfStream => GOTO eoS;
line: ROPE;
relPos: INT← 0;
startFound: BOOL;
prefixLength← entryLength← 0;
entryChar← 'X; -- not a valid entryType char
IF strm.EndOf[] THEN RETURN;
startPos← strm.GetIndex[];
line← strm.GetLineRope[];
[startFound, relPos]← IsStart[line];
IF
NOT startFound
THEN {
IF doReport
THEN
WalnutWindow.Report[
IO.PutFR["*start* not found at pos %g. Skipping to next entry.", IO.int[startPos]]];
UNTIL startFound
DO
IF strm.EndOf[] THEN RETURN;
startPos← strm.GetIndex[];
line← strm.GetLineRope[];
[startFound, relPos]← IsStart[line];
ENDLOOP;
};
Read entry info line from log, e.g.: 00101 00029 US+
startPos← startPos + relPos;
entryLength← strm.GetInt[];
prefixLength← strm.GetInt[];
line← strm.GetLineRope[];
entryChar← line.Fetch[line.Length[]-1];
EXITS
eoS => {entryLength← -1; RETURN};
};
starStartStar: ROPE = "*start*";
IsStart:
PUBLIC
PROC [line:
ROPE]
RETURNS[startFound:
BOOL, relPos:
INT] = {
relPos← 0;
IF line.Length = 0 THEN RETURN[FALSE, relPos];
IF line.Equal[starStartStar] THEN RETURN[TRUE, relPos];
IF (relPos← line.Length[] - starStartStar.Length[]) <=0 THEN RETURN[FALSE, relPos];
IF starStartStar.Equal[line.Substr[relPos]] THEN RETURN[TRUE, relPos];
RETURN[FALSE, relPos];
};
FlushAndSetCreateDate:
PUBLIC
PROC[strm:
STREAM] = {
flushes the stream and sets the create date to BasicTime.Now
of: FS.OpenFile;
strm.Flush[];
of←
IF WalnutWindow.logIsAlpineFile
THEN AlpineFS.OpenFileFromStream[strm]
ELSE FS.OpenFileFromStream[strm];
FS.SetByteCountAndCreatedTime[of, -1, BasicTime.Now[]];
};
********************************************************
MakeLogEntry:
PUBLIC
PROC [strm:
STREAM, entryType: LogEntryType, entryText:
ROPE, msgID:
ROPE←
NIL, doFlush:
BOOL←
TRUE]
RETURNS [endStrmPos, prefixLength, entryLength:
INT] = {
Puts entryText out in Laurel format on the given stream.
There are four kinds of entries:
(1) messages: entryText is the header and body of the message
(2) insertions: entryText is of form (insertion of new relship):
Relation: relation
attr1: xxx
attr2: yyy
or of the form (for insertion of new entity):
Domain: domain
name: xxx
(3) deletions: entryText same form as above, but represents deletion of that relship or entity
(4) hasbeenread: entryText is the messageID
Returns integer position in file of end of log entry.
OPEN IO;
typeChar: CHAR;
isMessage: BOOL← (entryType=message) OR (entryType=newMessage);
prefixLength← minPrefixLength + msgID.Length[]; -- magic number
entryLength← entryText.Length[] + prefixLength;
SELECT entryType
FROM
message => typeChar← ' ;
newMessage => typeChar← '?;
insertion => typeChar← '+;
deletion => typeChar← '-;
hasbeenread => typeChar← '←
ENDCASE;
can't fool with msg if it has formatting, so don't ever add CR at end
strm.SetIndex[strm.GetLength[]];
strm.PutF["*start*\n%05d %05d US%g\n",
int[entryLength], int[prefixLength], char[typeChar]];
IF msgID.Length[]#0 THEN strm.PutRope[msgID];
IO.PutRope[strm, entryText];
IF doFlush THEN FlushAndSetCreateDate[strm];
RETURN[strm.GetIndex[], prefixLength, entryLength]
};
GVLogEntry:
PUBLIC
PROC [strm:
STREAM, gvH: GVRetrieve.Handle, prefix:
ROPE]
RETURNS [lastIndex:
INT, ok:
BOOL] = {
startPos: INT ← strm.GetLength[]; -- where this message begins
strm.SetIndex[startPos];
{
ENABLE GVRetrieve.Failed => GOTO gvFailed;
prefixLen: INT← minPrefixLength + prefix.Length[];
msgLen, lenWritten: INT← 0;
prefixWritten, ctrlInfo: BOOL← FALSE;
WritePrefix:
PROC = {
strm.PutF["*start*\n%05d %05d US%g\n", IO.int[prefixLen+msgLen], IO.int[prefixLen], IO.char['?]];
strm.PutRope[prefix];
lenWritten← msgLen;
prefixWritten← TRUE;
};
CopyItemToLog:
PROC = {
strm.SetIndex[strm.GetLength[]];
is: STREAM← GVRetrieve.GetItem[gvH];
DO
bytes: INT;
IF (bytes ← is.GetBlock[copyBuffer, 0, 512]) = 0 THEN EXIT;
copyBuffer.length← bytes;
strm.PutBlock[copyBuffer];
ENDLOOP;
};
DO
item: GVBasics.ItemHeader ← GVRetrieve.NextItem[gvH];
SELECT item.type
FROM
PostMark, Sender, ReturnTo => ERROR;
Audio => NULL; -- we may use this soon
Recipients, Capability, updateItem, reMail => NULL;
Text => {
IF ctrlInfo THEN ERROR; -- items in wrong order!!!
msgLen← msgLen + LOOPHOLE[item.length, INT];
IF ~prefixWritten THEN WritePrefix[];
CopyItemToLog[];
};
TiogaCTRL => {
ctrlInfo← TRUE;
msgLen← msgLen + LOOPHOLE[item.length, INT];
CopyItemToLog[];
};
LastItem => EXIT;
ENDCASE => LOOP;
ENDLOOP;
IF ~ctrlInfo
THEN {
ch: CHAR;
strm.SetIndex[strm.GetLength[]-1];
IF (ch← strm.GetChar[]) # '\n THEN {strm.PutChar['\n]; msgLen← msgLen + 1};
};
IF msgLen # lenWritten THEN { strm.SetIndex[startPos]; WritePrefix[]};
strm.Flush[];
RETURN[strm.GetLength[], TRUE];
EXITS
gvFailed => {strm.SetIndex[startPos]; strm.SetLength[startPos]; RETURN[startPos, FALSE]};
};
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
MsgRecFromStream:
PUBLIC
PROC [strm:
STREAM, prefixLength, msgLength:
INT]
RETURNS[msgRec: MsgRec] = {
ENABLE
IO.EndOfStream =>
{WalnutWindow.Report["\nUnexpected EOF, ignoring msg entry"]; GOTO badMessage};
prefixPos: INT← strm.GetIndex[] - minPrefixLength;
headersPos: INT← prefixPos + prefixLength;
msgID, categories: ROPE← NIL;
outOfSynch: BOOL;
categoriesList: LIST OF ROPE;
[msgID, categories, outOfSynch]← ReadPrefixInfo[strm, headersPos];
IF outOfSynch THEN GOTO badMessage;
IF categories #
NIL
THEN
{
OPEN
IO;
h: STREAM← RIS[categories];
UNTIL h.EndOf[]
DO
categoriesList← CONS[h.GetTokenRope[breakProc: IO.IDProc].token, categoriesList] ENDLOOP;
h.Close[];
};
msgRec← NEW[MessageRecObject];
[]← WalnutRetrieve.ParseMsgIntoFields[msgRec, strm, msgLength];
IF (msgRec.gvID← msgID) =
NIL
THEN
[]← ConstructMsgID[msgRec] ELSE ParseMsgID[msgRec];
msgRec.prefixPos← prefixPos;
msgRec.headersPos← headersPos;
msgRec.msgLength← msgLength;
msgRec.categoriesList← categoriesList;
EXITS
badMessage => RETURN[NIL];
};
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
works for Laurel & Hardy files, as well as archiveLogs
ReadOldMailFile:
PUBLIC
PROC[strm:
STREAM, defaultPrefix:
ROPE]
RETURNS [ok:
BOOL] = {
ENABLE IO.EndOfStream => GOTO eos;
DO
beginMsgPos, msgLength, entryLength, prefixLength: INT;
msgID, msgText, categories: ROPE;
outOfSynch: BOOL← FALSE;
pos: INT← 0;
prefix: ROPE ← defaultPrefix;
[beginMsgPos, prefixLength, entryLength, ]← FindStartOfEntry[strm, TRUE];
IF entryLength = -1 THEN RETURN[FALSE];
IF (entryLength=0)
OR (entryLength=prefixLength)
THEN
EXIT;
Hardy's null message at end
IF
IO.PeekChar[strm] = '@
THEN strm.SetIndex[beginMsgPos + prefixLength] -- hardy file
ELSE [msgID, categories, outOfSynch]← ReadPrefixInfo[strm, beginMsgPos+prefixLength];
IF outOfSynch
THEN {
WalnutWindow.Report["Can't find prefix info; skipping to next entry"];
strm.SetIndex[beginMsgPos + entryLength];
LOOP
};
IF categories#NIL THEN prefix ← Rope.Cat[categoriesRope, ": ", categories, "\n"];
IF msgID # NIL THEN prefix ← Rope.Cat[msgIDRope, ": ", msgID, "\n", prefix];
msgLength ← entryLength-prefixLength;
msgText ← WalnutSendOps.RopeFromStream[strm, IO.GetIndex[strm], msgLength];
DO
replace \240's with spaces
pos ← msgText.SkipTo[pos, "\240\000"];
IF pos = msgLength THEN EXIT;
IF msgText.Fetch[pos] = '\000
THEN { IF msgText.Fetch[pos+1] = '\000 THEN EXIT ELSE pos← pos + 1}
ELSE msgText← msgText.Replace[start: pos, len: 1, with: " "];
ENDLOOP;
AddMessageToLog[entryText: msgText, prefix: prefix];
WalnutWindow.ReportRope["."];
ENDLOOP;
RETURN[TRUE];
EXITS eos => RETURN[FALSE]
};
TRUSTED { TiogaCTRL← LOOPHOLE[1013B] };
}.