DIRECTORY
AlpFile USING [LockOption, PropertyValuePair],
AlpineFS USING [ErrorFromStream, FileOptions, StreamOptions, Abort, Open, OpenFile, OpenFileFromStream, OpenOrCreate, StreamFromOpenFile, WriteProperties],
Atom USING [MakeAtomFromRefText],
BasicTime USING [GMT, nullGMT, FromPupTime, Now],
Convert USING [Error, IntFromRope, TimeFromRope],
FS
USING [Error, nullOpenFile, OpenFile, StreamBufferParms, StreamOptions,
Create, GetInfo, Open, PagesForBytes, SetByteCountAndCreatedTime,
SetPageCount, StreamFromOpenFile, StreamOpen],
GVBasics USING [RName, Timestamp],
IO,
RefText USING[line, page, TrustTextAsRope],
Rope,
ViewerTools USING [TiogaContents],
WalnutKernelDefs USING [LogEntry, LogEntryObject, MsgLogEntry],
WalnutParseMsg USING [MsgHeaders, ParseProc, ParseMsgFromStream],
WalnutSendOps USING [RFC822Date, RopeFromStream],
WalnutStream USING [];
WalnutStreamImpl:
CEDAR
PROGRAM
IMPORTS
AlpineFS, Atom, BasicTime, Convert, FS, IO, RefText, Rope, WalnutParseMsg, WalnutSendOps
=
BEGIN
OPEN WalnutStream;
Types
GMT: TYPE = BasicTime.GMT;
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
LogEntry: TYPE = WalnutKernelDefs.LogEntry;
LogEntryObject: TYPE = WalnutKernelDefs.LogEntryObject;
MsgLogEntry: TYPE = WalnutKernelDefs.MsgLogEntry;
Variables
entryHeaderRope: ROPE = "*entry* %10g\n";
entryHeaderLen: INT = 19;
copyBuffer: REF TEXT = NEW[TEXT[RefText.page]];
field1: REF TEXT ← NEW[TEXT[RefText.line]];
field2: REF TEXT ← NEW[TEXT[RefText.line]];
field3: REF TEXT ← NEW[TEXT[RefText.line]];
field4: REF TEXT ← NEW[TEXT[RefText.line]];
-- the following appear in the other log files
logFileInfo: PUBLIC REF LogFileInfo LogEntryObject = NEW[LogFileInfo LogEntryObject];
createMsg: PUBLIC REF CreateMsg LogEntryObject = NEW[CreateMsg LogEntryObject];
expungeMsgs:
PUBLIC REF ExpungeMsgs LogEntryObject =
NEW[ExpungeMsgs LogEntryObject];
writeExpungeLog:
PUBLIC
REF WriteExpungeLog LogEntryObject =
NEW[WriteExpungeLog LogEntryObject];
createMsgSet:
PUBLIC REF CreateMsgSet LogEntryObject =
NEW[CreateMsgSet LogEntryObject];
emptyMsgSet:
PUBLIC REF EmptyMsgSet LogEntryObject =
NEW[EmptyMsgSet LogEntryObject];
destroyMsgSet:
PUBLIC REF DestroyMsgSet LogEntryObject =
NEW[DestroyMsgSet LogEntryObject];
addMsg: PUBLIC REF AddMsg LogEntryObject = NEW[AddMsg LogEntryObject];
removeMsg: PUBLIC REF RemoveMsg LogEntryObject = NEW[RemoveMsg LogEntryObject];
moveMsg: PUBLIC REF MoveMsg LogEntryObject = NEW[MoveMsg LogEntryObject];
hasbeenRead:
PUBLIC REF HasBeenRead LogEntryObject =
NEW[HasBeenRead LogEntryObject];
recordNewMailInfo: PUBLIC REF RecordNewMailInfo LogEntryObject = NEW[RecordNewMailInfo LogEntryObject];
startCopyNewMail:
PUBLIC
REF StartCopyNewMail LogEntryObject =
NEW[StartCopyNewMail LogEntryObject];
endCopyNewMailInfo:
PUBLIC
REF EndCopyNewMailInfo LogEntryObject =
NEW[EndCopyNewMailInfo LogEntryObject];
acceptNewMail:
PUBLIC
REF AcceptNewMail LogEntryObject =
NEW[AcceptNewMail LogEntryObject];
startReadArchiveFile:
PUBLIC REF StartReadArchiveFile LogEntryObject =
NEW[StartReadArchiveFile LogEntryObject];
endReadArchiveFile:
PUBLIC REF EndReadArchiveFile LogEntryObject =
NEW[EndReadArchiveFile LogEntryObject];
startCopyReadArchive:
PUBLIC
REF StartCopyReadArchive LogEntryObject =
NEW[StartCopyReadArchive LogEntryObject];
endCopyReadArchiveInfo:
PUBLIC
REF EndCopyReadArchiveInfo LogEntryObject =
NEW[EndCopyReadArchiveInfo LogEntryObject];
Procedures
Used for general opening of files
Open:
PUBLIC
PROC[name:
ROPE, readOnly:
BOOL ←
FALSE, pages:
INT ← 200,
useOldIfFound:
BOOL ←
FALSE, exclusive:
BOOL ←
FALSE]
RETURNS [strm:
STREAM] = {
IF name.Find[".alpine]", 0,
FALSE] = -1
THEN {
-- file elsewhere
IF readOnly
THEN
strm ←
FS.StreamOpen[ fileName: name,
streamOptions: localStreamOptions, streamBufferParms: streamBufferOption]
ELSE {
openFile: FS.OpenFile ← FS.nullOpenFile;
IF useOldIfFound
THEN
openFile ←
FS.Open[name, $write !
FS.Error =>
IF error.code = $unknownFile THEN CONTINUE ELSE REJECT];
IF openFile =
FS.nullOpenFile
THEN
openFile ← FS.Create[name: name, keep: 2, pages: pages];
strm ←
FS.StreamFromOpenFile[
openFile: openFile,
accessRights: $write,
streamOptions: localStreamOptions, streamBufferParms: streamBufferOption];
};
}
ELSE {
-- alpine file
openFile: AlpineFS.OpenFile;
IF readOnly
THEN {
openFile ← AlpineFS.Open[name: name, options: alpineFileOptions];
strm ← AlpineFS.StreamFromOpenFile[openFile: openFile, streamOptions: alpineStreamOptions, streamBufferParms: streamBufferOption];
}
ELSE {
actualPages: INT;
lock: AlpFile.LockOption ← IF exclusive THEN [$write, $fail] ELSE [$none, $wait];
openFile ← AlpineFS.OpenOrCreate[
-- AlpineFS ignores the pages param
name: name, pages: pages, options: alpineFileOptions];
actualPages ← FS.GetInfo[openFile].pages;
IF pages > actualPages THEN FS.SetPageCount[openFile, pages];
strm ← AlpineFS.StreamFromOpenFile[openFile: openFile, accessRights: $write,
initialPosition: $start, streamOptions: alpineStreamOptions,
streamBufferParms: streamBufferOption];
};
};
};
alpineFileOptions: AlpineFS.FileOptions ← [
updateCreateTime: TRUE,
referencePattern: sequential,
recoveryOption: $log,
finishTransOnClose: TRUE
];
streamBufferOption: FS.StreamBufferParms = [vmPagesPerBuffer: 4, nBuffers: 4];
alpineStreamOptions: AlpineFS.StreamOptions ←
[ tiogaRead:
FALSE,
commitAndReopenTransOnFlush: TRUE,
truncatePagesOnClose: FALSE,
finishTransOnClose: TRUE,
closeFSOpenFileOnClose: TRUE];
localStreamOptions:
FS.StreamOptions ←
[ tiogaRead:
FALSE,
commitAndReopenTransOnFlush: TRUE,
truncatePagesOnClose: FALSE,
finishTransOnClose: TRUE,
closeFSOpenFileOnClose: TRUE];
-- Miscellaneous stream operations
Aborted:
PUBLIC
PROC [strm:
STREAM]
RETURNS [aborted:
BOOL] = {
code: ATOM ← AlpineFS.ErrorFromStream[strm].code;
aborted ← (code = $transAborted);
};
AbortStream:
PUBLIC PROC[strm:
STREAM] =
{ AlpineFS.Abort[AlpineFS.OpenFileFromStream[strm]] };
FlushStream:
PUBLIC
PROC[strm:
STREAM, setCreateDate:
BOOL ←
FALSE] = {
IF setCreateDate
THEN {
of: AlpineFS.OpenFile = AlpineFS.OpenFileFromStream[strm];
FS.SetByteCountAndCreatedTime[of, -1, BasicTime.Now[]];
};
strm.Flush[]
};
SetHighWaterMark:
PUBLIC
PROC[
strm: STREAM, hwmBytes: INT, numPages: INT, setCreateDate: BOOL] = {
of: AlpineFS.OpenFile = AlpineFS.OpenFileFromStream[strm];
prop: highWaterMark AlpFile.PropertyValuePair =
[highWaterMark[FS.PagesForBytes[hwmBytes]]];
strm.SetLength[hwmBytes];
strm.Flush[]; -- make it notice the SetLength
IF setCreateDate THEN FS.SetByteCountAndCreatedTime[of, -1, BasicTime.Now[]];
AlpineFS.WriteProperties[of, LIST[prop]];
IF numPages = -1 THEN RETURN;
IF FS.GetInfo[of].pages <= numPages THEN RETURN;
FS.SetPageCount[of, numPages];
};
SetPosition:
PUBLIC PROC[strm:
STREAM, index:
INT]
RETURNS[ok:
BOOL] = {
pos: INT ← IF index = -1 THEN strm.GetLength[] ELSE index;
ok ← TRUE;
strm.SetIndex[pos ! IO.Error, IO.EndOfStream => {ok ← FALSE; CONTINUE}];
};
ReadRope:
PUBLIC
PROC[strm:
STREAM, len:
INT]
RETURNS[r:
ROPE] = {
r ← WalnutSendOps.RopeFromStream[strm, strm.GetIndex[], len !
IO.EndOfStream => CONTINUE];
};
Reading and writing log entries
FindNextEntry:
PUBLIC PROC[strm:
STREAM]
RETURNS[startPos:
INT] = {
ENABLE IO.EndOfStream => GOTO exit;
state: INTEGER ← 0;
length: INT;
initialPos: INT = strm.GetIndex[];
startPos ← -1;
DO
SELECT strm.GetChar[]
FROM
'* => IF state = 6 THEN state ← 7 ELSE state ← 1;
'e => IF state = 1 THEN state ← 2 ELSE state ← 0;
'n => IF state = 2 THEN state ← 3 ELSE state ← 0;
't => IF state = 3 THEN state ← 4 ELSE state ← 0;
'r => IF state = 4 THEN state ← 5 ELSE state ← 0;
'y => IF state = 5 THEN state ← 6 ELSE state ← 0;
ENDCASE => state ← 0;
IF state = 7
THEN {
strm.SetIndex[startPos ← strm.GetIndex[] - 7];
[, length] ← CheckForValidPrefix[strm];
IF length # -1
THEN
{
strm.SetIndex[startPos];
RETURN
};
strm.SetIndex[startPos+1];
};
ENDLOOP;
EXITS
exit => {startPos ← -1; RETURN};
ReadEntry:
PUBLIC
PROC[strm:
STREAM, quick:
BOOL]
RETURNS[le: LogEntry, length:
INT] = {
ENABLE IO.EndOfStream => ERROR; -- Shouldn't get EOS's
type: ATOM;
startPos: INT;
[startPos, length] ← CheckForValidPrefix[strm];
IF length = -1 THEN RETURN; -- not a valid entry here
type ← Atom.MakeAtomFromRefText[strm.GetLine[field1]];
SELECT type
FROM
$LogFileInfo => {
logFileInfo.key ← strm.GetLine[field1];
logFileInfo.internalFileID ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field2]]];
logFileInfo.logSeqNo ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field2]]];
RETURN[logFileInfo, length];
};
$CreateMsg => {
createMsg.msg ← strm.GetLine[field1];
createMsg.textLen ← strm.GetInt[];
createMsg.formatLen ← strm.GetInt[];
[] ← strm.GetChar[]; -- glide over the CR after formatLen
createMsg.entryStart ← startPos;
createMsg.textOffset ← strm.GetIndex[] - startPos;
IF quick
THEN ScanForHeadersLen[strm, createMsg]
ELSE MsgEntryInfoFromStream[strm, createMsg];
strm.SetIndex[startPos+length]; -- consume entire entry
RETURN[createMsg, length];
};
$ExpungeMsgs => RETURN[expungeMsgs, length];
$WriteExpungeLog => {
writeExpungeLog.internalFileID ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field1]]];
RETURN[writeExpungeLog, length];
};
$CreateMsgSet => {
createMsgSet.msgSet ← strm.GetLine[field1];
RETURN[createMsgSet, length];
};
$EmptyMsgSet => {
emptyMsgSet.msgSet ← strm.GetLine[field1];
RETURN[emptyMsgSet, length];
};
$DestroyMsgSet => {
destroyMsgSet.msgSet ← strm.GetLine[field1];
RETURN[destroyMsgSet, length];
};
$AddMsg => {
addMsg.msg ← strm.GetLine[field1];
addMsg.to ← strm.GetLine[field3];
RETURN[addMsg, length];
};
$RemoveMsg => {
removeMsg.msg ← strm.GetLine[field1];
removeMsg.from ← strm.GetLine[field2];
RETURN[removeMsg, length];
};
$MoveMsg => {
moveMsg.msg ← strm.GetLine[field1];
moveMsg.from ← strm.GetLine[field2];
moveMsg.to ← strm.GetLine[field3];
RETURN[moveMsg, length];
};
$HasBeenRead => {
hasbeenRead.msg ← strm.GetLine[field1];
RETURN[hasbeenRead, length];
};
$RecordNewMailInfo => {
recordNewMailInfo.logLen ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field1]]];
recordNewMailInfo.when ←
Convert.TimeFromRope[RefText.TrustTextAsRope[strm.GetLine[field2]]];
recordNewMailInfo.server ← strm.GetLine[field3];
recordNewMailInfo.num ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field4]]];
RETURN[recordNewMailInfo, length]
};
$StartCopyNewMail => RETURN[startCopyNewMail, length];
$EndCopyNewMailInfo => {
endCopyNewMailInfo.startCopyPos ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field1]]];
RETURN[endCopyNewMailInfo, length];
};
$AcceptNewMail => RETURN[acceptNewMail, length];
$StartReadArchiveFile => {
startReadArchiveFile.file ← strm.GetLine[field1];
startReadArchiveFile.msgSet ← strm.GetLine[field2];
RETURN[startReadArchiveFile, length];
};
$EndReadArchiveFile => RETURN[endReadArchiveFile, length];
$StartCopyReadArchive => RETURN[startCopyReadArchive, length];
$EndCopyReadArchiveInfo => {
endCopyReadArchiveInfo.startCopyPos ←
Convert.IntFromRope[RefText.TrustTextAsRope[strm.GetLine[field1]]];
RETURN[endCopyReadArchiveInfo, length];
};
ENDCASE => ERROR;
};
PeekEntry:
PUBLIC
PROC [strm:
STREAM]
RETURNS[ident:
ATOM, msgID:
REF TEXT, length:
INT] = {
startPos: INT;
BEGIN ENABLE IO.EndOfStream => GOTO eos;
[startPos, length] ← CheckForValidPrefix[strm];
IF length = -1 THEN RETURN; -- not a valid entry here
ident ← Atom.MakeAtomFromRefText[strm.GetLine[field1]];
SELECT ident
FROM
$CreateMsg => msgID ← strm.GetLine[field1];
$AddMsg => msgID ← strm.GetLine[field1];
$RemoveMsg => msgID ← strm.GetLine[field1];
$MoveMsg => msgID ← strm.GetLine[field1];
$DestroyMsg => msgID ← strm.GetLine[field1];
$HasBeenRead => msgID ← strm.GetLine[field1];
ENDCASE => NULL;
EXITS
eos => length ← -1; -- not a valid entry here
END;
strm.SetIndex[startPos];
};
WriteEntry:
PUBLIC
PROC[strm:
STREAM, le: LogEntry, pos:
INT ← -1]
RETURNS[startPos:
INT] = {
entry: ROPE;
extra, length: INT ← 0;
TRUSTED {
WITH le: le
SELECT
FROM
LogFileInfo =>
entry ←
IO.PutFR["LogFileInfo\n%g\n%g\n%g\n",
IO.text[le.key],
IO.int[le.internalFileID],
IO.int[le.logSeqNo]
];
CreateMsg => {
entry ←
IO.PutFR["CreateMsg\n%g\n%10g %10g\n",
IO.text[le.msg],
IO.int[le.textLen],
IO.int[le.formatLen]
];
extra ← le.textLen + le.formatLen + 1;
};
ExpungeMsgs => entry ← "ExpungeMsgs\n";
WriteExpungeLog =>
entry ←
IO.PutFR["WriteExpungeLog\n%g\n",
IO.int[le.internalFileID]
];
CreateMsgSet =>
entry ←
IO.PutFR["CreateMsgSet\n%g\n",
IO.text[le.msgSet]
];
EmptyMsgSet =>
entry ←
IO.PutFR["EmptyMsgSet\n%g\n",
IO.text[le.msgSet]
];
DestroyMsgSet =>
entry ←
IO.PutFR["DestroyMsgSet\n%g\n",
IO.text[le.msgSet]
];
AddMsg =>
entry ←
IO.PutFR["AddMsg\n%g\n%g\n",
IO.text[le.msg],
IO.text[le.to]
];
RemoveMsg =>
entry ←
IO.PutFR["RemoveMsg\n%g\n%g\n",
IO.text[le.msg],
IO.text[le.from]
];
MoveMsg =>
entry ←
IO.PutFR["MoveMsg\n%g\n%g\n%g\n",
IO.text[le.msg],
IO.text[le.from],
IO.text[le.to]
];
HasBeenRead =>
entry ←
IO.PutFR["HasBeenRead\n%g\n",
IO.text[le.msg],
];
RecordNewMailInfo =>
entry ←
IO.PutFR["RecordNewMailInfo\n%g\n%g\n%g\n%g\n",
IO.int[le.logLen],
IO.time[le.when],
IO.text[le.server],
IO.int[le.num]
];
StartCopyNewMail => entry ← "StartCopyNewMail\n";
EndCopyNewMailInfo =>
entry ←
IO.PutFR["EndCopyNewMailInfo\n%g\n",
IO.int[le.startCopyPos]
];
AcceptNewMail => entry ← "AcceptNewMail\n";
StartReadArchiveFile =>
entry ←
IO.PutFR["StartReadArchiveFile\n%g\n%g\n",
IO.text[le.file],
IO.text[le.msgSet]
];
EndReadArchiveFile => entry ← "EndReadArchiveFile\n";
StartCopyReadArchive => entry ← "StartCopyReadArchive\n";
EndCopyReadArchiveInfo =>
entry ←
IO.PutFR["EndCopyReadArchiveInfo\n%g\n",
IO.int[le.startCopyPos]
];
ENDCASE => ERROR;
};
entry: the rope representation of the log entry
write at the end of the stream unless given a pos (for fixing up CreateMsg headers)
startPos ← IF pos = -1 THEN strm.GetLength[] ELSE pos;
strm.SetIndex[startPos];
length ← entry.Length[] + extra + entryHeaderLen; -- for messages
strm.PutRope[IO.PutFR[entryHeaderRope, IO.int[length]]]; -- entryHeaderLen (19 characters)
strm.PutRope[entry];
};
WriteMsgBody:
PUBLIC PROC[strm:
STREAM, body: ViewerTools.TiogaContents] = {
strm.PutRope[body.contents];
strm.PutRope[body.formatting];
strm.PutChar['\n];
};
Overwrite:
PUBLIC
PROC[to, from:
STREAM, startPos:
INT, fromPos:
INT ← -1] = {
IF startPos = -1 THEN to.SetIndex[to.GetLength[]] ELSE to.SetIndex[startPos];
IF fromPos = -1 THEN from.SetIndex[0] ELSE from.SetIndex[fromPos];
StrmToStrmCopy[to, from];
};
CopyBytes:
PUBLIC PROC[from, to:
STREAM, num:
INT] = {
bytes: INT ← num;
WHILE bytes >= 512
DO
[] ← from.GetBlock[copyBuffer, 0, 512];
to.PutBlock[copyBuffer];
bytes ← bytes - 512;
ENDLOOP;
IF bytes # 0
THEN {
[] ← from.GetBlock[copyBuffer, 0, bytes];
to.PutBlock[copyBuffer];
};
};
StrmToStrmCopy:
PROC[to, from:
STREAM] = {
DO
IF from.GetBlock[copyBuffer, 0, 512] = 0 THEN EXIT;
to.PutBlock[copyBuffer];
ENDLOOP
};
CheckForValidPrefix:
PROC [strm:
STREAM]
RETURNS [startPos, length:
INT] = {
entryRope: ROPE = "*entry* ";
lenRope, prefix: ROPE;
startPos ← strm.GetIndex[];
prefix ← WalnutSendOps.RopeFromStream[strm, startPos, entryHeaderLen];
IF
NOT prefix.Find[entryRope] = 0
THEN {
strm.SetIndex[startPos];
RETURN[startPos, -1];
};
IF
NOT prefix.Fetch[entryHeaderLen-1] = '\n
THEN {
strm.SetIndex[startPos];
RETURN[startPos, -1];
};
lenRope ← prefix.Substr[entryRope.Length[], 10];
length ← Convert.IntFromRope[lenRope ! Convert.Error => {
length ← -1;
strm.SetIndex[startPos];
CONTINUE }];
};
MsgEntryInfoFromStream:
PUBLIC
PROC[strm:
STREAM, mle: MsgLogEntry] = {
date: ROPE = "Date";
subject: ROPE = "Subject";
from: ROPE = "From";
sender: ROPE = "Sender";
to: ROPE = "To";
mh: WalnutParseMsg.MsgHeaders;
WantThisField: WalnutParseMsg.ParseProc = {
SELECT
TRUE
FROM
fieldName.Equal[date, FALSE] => RETURN[TRUE, TRUE];
fieldName.Equal[subject, FALSE] => RETURN[TRUE, TRUE];
fieldName.Equal[from, FALSE] => RETURN[TRUE, TRUE];
fieldName.Equal[sender, FALSE] => RETURN[TRUE, TRUE];
fieldName.Equal[to, FALSE] => RETURN[TRUE, TRUE];
ENDCASE => RETURN[FALSE, TRUE];
};
-- sigh, the joys of re-using the same MsgLogEntry; must clear all entries
mle.date ← BasicTime.nullGMT;
mle.subject ← NIL;
mle.sender ← NIL;
mle.to ← NIL;
IF strm.PeekChar[] = '\n THEN [] ← strm.GetChar[]; -- formatting madness
mh ← WalnutParseMsg.ParseMsgFromStream[strm, mle.textLen, WantThisField];
FOR mhL: WalnutParseMsg.MsgHeaders ← mh, mhL.rest UNTIL mhL=NIL DO
fieldName:
ROPE = mhL.first.fieldName;
SELECT
TRUE
FROM
fieldName.Equal[date,
FALSE] => mle.date ← Convert.TimeFromRope[mhL.first.value !
Convert.Error => {mle.date ← BasicTime.Now[]; CONTINUE } ];
fieldName.Equal[subject, FALSE] => mle.subject ← mhL.first.value;
fieldName.Equal[sender, FALSE] => mle.sender ← mhL.first.value;
fieldName.Equal[to, FALSE] => mle.to ← mhL.first.value;
fieldName.Equal[from,
FALSE] =>
IF mle.sender = NIL THEN mle.sender ← mhL.first.value;
ENDCASE => NULL;
ENDLOOP;
};
ScanForHeadersLen:
PROC[strm:
STREAM, mle: MsgLogEntry] = {
lastWasCR: BOOL ← FALSE;
hLen: INT ← 0;
WHILE hLen <= mle.textLen
DO
hLen ← hLen + 1;
IF strm.GetChar[] = '\n
THEN {
IF lastWasCR THEN { mle.headersLen ← hLen; RETURN };
lastWasCR ← TRUE;
}
ELSE lastWasCR ← FALSE;
ENDLOOP;
mle.headersLen ← mle.textLen; -- not found but don't cause error
};
ConstructMsgID:
PUBLIC
PROC[ts: GVBasics.Timestamp, gvSender: GVBasics.RName]
RETURNS[msgID:
ROPE] = {
tr: ROPE ← WalnutSendOps.RFC822Date[BasicTime.FromPupTime[ts.time]];
IF gvSender.Fetch[0] = '"
THEN {
pos: INT = gvSender.Find["\"", 1];
IF pos # -1
THEN
gvSender ←
Rope.Concat[Rope.Substr[gvSender, 1, pos - 1], Rope.Substr[gvSender, pos+1]];
};
msgID ←
IO.PutFR["%g $ %b#%b@%g",
[rope[gvSender]], [integer[ts.net]], [integer[ts.host]], [rope[tr]]];
};
for debugging
GetFileLength:
PROC[strm:
STREAM]
RETURNS[
INT] =
{ RETURN[strm.GetLength[]] };
END.