BlackCherryMailImpl.mesa
Copyright Ó 1987, 1988, 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved.
adapted from BlackWalnut.mesa of 07-Jun-89 18:02:54 PDT
Bill Jackson (bj) July 19, 1989 6:38:17 pm PDT
Willie-Sue, April 13, 1990 10:26:04 am PDT
Willie-s, July 20, 1993 3:44 pm PDT
DIRECTORY
BasicTime USING [GMT, nullGMT, Now, Period],
BlackCherry,
BlackCherryInternal,
BlackCherrySidedoor,
Convert USING [Error, RopeFromTimeRFC822, TimeFromRope],
IO,
MailBasics,
MailBasicsFileTypes,
MailMessage USING [ReadOneMessageX],
MailParse USING [endOfInput, FinalizeParse, GetFieldBody, GetFieldName, InitializeParse, ParseError, ParseHandle],
MailRetrieve USING [Accept, Failed, FailureReason, Handle, MailboxState, MBXState, NextMessage, NextServer, ServerName, ServerState, ServerType, StartMessage],
MailUtils USING [Credentials, GetLoggedInUser, GetUserCredentials, LocalNameFromRName],
Process USING [Detach],
RedBlackTree,
RefText,
Rope,
RuntimeError USING [BoundsFault],
UserProfile USING [Boolean],
VFonts USING [CharWidth, StringWidth];
BlackCherryMailImpl: CEDAR MONITOR
IMPORTS BasicTime, BlackCherry, BlackCherryInternal, Convert, IO, MailMessage, MailParse, MailRetrieve, MailUtils, Process, RedBlackTree, Rope, RuntimeError, UserProfile, VFonts
EXPORTS BlackCherryInternal, BlackCherrySidedoor ~ BEGIN OPEN BlackCherry, BlackCherryInternal, BlackCherrySidedoor;
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
CantParse: SIGNAL ~ CODE;
Types
RetrieveState: TYPE ~ ATOM;
{ OK, communicationFailure, noSuchServer, connectionRejected, badCredentials, didNotRespond, noMailboxes, noServers, unknownFailure, unknownError };
Fundamental constants
blanks: ROPE ~ " "; -- lotsa blanks
blankWidth: INT ~ VFonts.CharWidth[' ]; -- in default font
Header/Attribute names
date: ROPE ~ "Date";
subject: ROPE ~ "Subject";
from: ROPE ~ "From";
sender: ROPE ~ "Sender";
to: ROPE ~ "To";
Current User
userRNameList: PUBLIC MailBasics.RNameList;
simpleUserNameList: PUBLIC LIST OF ROPE;
localRNameList: LIST OF ROPE;
credentials: PUBLIC LIST OF MailUtils.Credentials;
UpdateUser: PUBLIC ENTRY PROC[msiData: MsiData] ~ { UpdateUserInternal[msiData] };
UpdateUserInternal: INTERNAL PROC[msiData: MsiData] ~ {
ENABLE BEGIN
UNWIND => NULL;
MailRetrieve.Failed => {
Report[
"MailRetrieve.Failed. Why: %g, text: %g\n", [atom[why]], [rope[text]] ];
GOTO nogood;
};
END;
gvMail: BOOL ~ UserProfile.Boolean["BlackCherry.gvMail", TRUE];
xnsMail: BOOL ~ UserProfile.Boolean["BlackCherry.xnsMail", TRUE];
CloseRetrieveHandle[];
userRNameList ¬ NIL;
simpleUserNameList ¬ localRNameList ¬ NIL;
credentials ¬ MailUtils.GetUserCredentials[
SELECT TRUE FROM ( gvMail AND xnsMail ) => NIL,
gvMail => $gv, xnsMail => $xns, ENDCASE => $xns];
MakeAndSetNewHandle[credentials, msiData];
FOR ucL: LIST OF MailUtils.Credentials ¬ credentials, ucL.rest UNTIL ucL = NIL DO
this: ROPE;
userRNameList ¬ CONS[ucL.first.rName, userRNameList];
simpleUserNameList ¬ CONS[this ¬ MailUtils.GetLoggedInUser[ucL.first.rName.ns], simpleUserNameList];
localRNameList ¬ CONS[this ¬ MailUtils.LocalNameFromRName[[ucL.first.rName.ns, this]], localRNameList];
ENDLOOP;
EXITS nogood => {};
};
IsCurrentUser: PUBLIC PROC [ sender: ROPE ] RETURNS [ yes: BOOL ¬ TRUE ] ~ {
FOR rnL: MailBasics.RNameList ¬ userRNameList, rnL.rest UNTIL rnL = NIL DO
IF sender.Equal[rnL.first.name, FALSE] THEN RETURN;
ENDLOOP;
FOR rnL: LIST OF ROPE ¬ localRNameList, rnL.rest UNTIL rnL = NIL DO
IF sender.Equal[rnL.first, FALSE] THEN RETURN;
ENDLOOP;
FOR rnL: LIST OF ROPE ¬ simpleUserNameList, rnL.rest UNTIL rnL = NIL DO
IF sender.Equal[rnL.first, FALSE] THEN RETURN;
ENDLOOP;
RETURN[FALSE];
};
Transfer msgs to database
WriteMsgs: PUBLIC PROC [msInfo: MsgSetInfo, mailStrm: IO.STREAM]
RETURNS [ numMsgs: INT ¬ 0, newList, newLast: MsgHandle ¬ NIL ] ~ {
reader: IO.STREAM ~ msInfo.fileData.readStream;
writer: IO.STREAM ~ mailStrm;
segmentStart: INT ~ writer.GetLength[]; -- append to end of stream/log!
writer.SetIndex[segmentStart];
FOR msgH: MsgHandle ¬ msInfo.first, msgH.next WHILE ( msgH # NIL ) DO
IF ( msgH.deleted ) THEN LOOP;
BEGIN
entryStart: INT ~ msgH.entryStart;
entryLen: INT ~ msgH.entryLen;
writeStart: INT ~ writer.GetLength[];
textLen: INT ~ msgH.textLen;
headersPos: INT ~ writeStart + ( msgH.headersPos - msgH.entryStart );
formatPos: INT ~ headersPos + textLen;
cell: MsgHandle ~ NEW[MsgHandleRec ¬
[gvID: msgH.gvID, unRead: msgH.unRead, headersPos: headersPos, textLen: textLen, formatPos: formatPos, formatLen: msgH.formatLen, entryStart: writeStart, entryLen: entryLen, next: NIL] ];
put lots more info here by moving code out of WriteTOC - bj
reader.SetIndex[entryStart];
CopyBytes[to: writer, from: reader, num: entryLen];
writer.Flush[];
IF newLast = NIL THEN newList ¬ cell ELSE newLast.next ¬ cell;
newLast ¬ cell;
IF ( numMsgs ¬ numMsgs.SUCC ) MOD 10 = 0 THEN {
IF numMsgs MOD 100 = 0 THEN Report["(%g) ", [integer[numMsgs]] ]
ELSE Report["."];
};
END;
ENDLOOP;
writer.Flush[];
IF ( reader # writer ) THEN reader.Flush[];
};
WriteTOC: PUBLIC PROC [ reader, writer: STREAM, numMsgs, segmentStart: INT, msgH: MsgHandle, useFromFieldInTOC: BOOL]
RETURNS [ last: MsgHandle ¬ NIL ] ~ {
posForEntry: INT ~ writer.GetLength[]; -- where the toc entries get written
writer.SetIndex[posForEntry];
writer.PutF[relTocEntriesEntryTemplate, [integer[0]], [integer[numMsgs]] ];
FOR thisH: MsgHandle ¬ msgH, thisH.next WHILE ( thisH # NIL ) DO
mostOfToc: ROPE;
reader.SetIndex[thisH.headersPos];
mostOfToc ¬ TOCFromStream[reader, thisH.textLen, useFromFieldInTOC].toc;
thisH.toc ¬ Rope.Concat[" ", mostOfToc];
writer.Flush[];
writer.SetIndex[writer.GetLength[]]; -- set at end for sure
thisH.charPos ¬ writer.GetIndex[];
IF thisH.unRead THEN writer.PutRope[" ? "] ELSE writer.PutRope[" "];
writer.PutRope[mostOfToc];
writer.PutFL["\r%g %g %g %g\r",
LIST[[integer[thisH.headersPos-segmentStart]], [integer[thisH.textLen]],
[integer[IF thisH.formatPos = 0 THEN 0 ELSE thisH.formatPos-segmentStart]], [integer[thisH.formatLen]] ]];
writer.PutF["%g %g\r", [integer[thisH.entryStart-segmentStart]], [integer[thisH.entryLen]] ];
last ¬ thisH;
ENDLOOP;
{
now: INT ~ writer.GetLength[];
entryLen: INT ~ now-posForEntry;
writer.SetIndex[posForEntry];
writer.PutF[relTocEntriesEntryTemplate, [integer[entryLen]], [integer[numMsgs]] ];
};
writer.Flush[];
IF ( reader # writer ) THEN reader.Flush[];
};
Message/Log Parsing
tocPointerEntryTemplate: ROPE ~ "*entry*%10g\rTOCPointer %10g\r";
relTocPointerEntryTemplate: PUBLIC ROPE ¬ "*entry*%10g\rRelTOCPointer %10g\r";
tocEntriesEntryTemplate: ROPE ~ "*entry*%10g\rTOCEntries %5g\r";
relTocEntriesEntryTemplate: PUBLIC ROPE ¬ "*entry*%10g\rRelTOCEntries %5g\r";
headerEntryTemplate: PUBLIC ROPE ¬ "*entry* %10g\r";
createMsgTemplate: PUBLIC ROPE ¬ "CreateMsg\r%g\r%10g %10g\r";
check that the file is positioned at a TOCPointer entry
RelTOCPointerRope: ROPE = "RelTOCPointer";
TOCPointerRope: ROPE = "TOCPointer";
RelTOCEntriesRope: ROPE = "RelTOCEntries";
TOCEntriesRope: ROPE = "TOCEntries";
IsEntryFound: PROC [line: ROPE, start: INT] RETURNS [ok: BOOL] ~ {
IF ( ok ¬ ( line.Find["*entry*", 0] = 0 ) ) THEN RETURN;
BadEntryReport[line, "*entry*", start];
};
BadEntryReport: PROC[line, which: ROPE, where: INT] = {
Report["***\n Found %g instead of %g at file pos %g - some messages may not be available\n", [rope[line]], [rope[which]], [integer[where]] ];
};
IsIdentFound: PROC [ident, which: ROPE, start: INT, failOK: BOOL ¬ FALSE]
RETURNS [ok: BOOL] ~ {
IF ( ok ¬ ( ident.Equal[which] ) ) THEN RETURN;
IF NOT failOK THEN BadEntryReport[ident, which, start];
};
ReadBWFile: PUBLIC PROC [msInfo: MsgSetInfo] RETURNS [numUndel, totalMsgs: INT ¬ 0, ok: BOOL ¬ FALSE] ~ {
fileName: ROPE ~ msInfo.fileName;
stream: IO.STREAM ~ msInfo.fileData.readStream; -- can only read from a readStream
streamLen: INT ~ stream.GetLength[];
thisRead: INT;
BEGIN ENABLE BEGIN
IO.EndOfStream => {
Report["\n *** IO.EndOfStream reading mailLog, file index at %g - some messages may not be available\n", [integer[stream.GetIndex[]]] ];
GOTO notgood;
};
UNWIND => {
Report["\n *** UNWIND reading mailLog, file index at %g - some messages may not be available\n", [integer[stream.GetIndex[]]] ];
GOTO notgood;
};
END;
DO
first: MsgHandle ¬ NIL;
current: MsgHandle ¬ NIL;
start: INT ~ stream.GetIndex[];
isRelToc, isRelEntries: BOOL ¬ TRUE;
IF ( start = streamLen ) THEN RETURN[numUndel, totalMsgs, TRUE];
{
entryRope: ROPE ~ stream.GetLineRope[];
IF ( NOT IsEntryFound[entryRope, start] ) THEN RETURN;
};
{
identRope: ROPE ~ stream.GetTokenRope[].token;
startPos: INT ~ stream.GetIndex[];
IF ( NOT IsIdentFound[identRope, RelTOCPointerRope, startPos, TRUE] ) THEN {
IF NOT IsIdentFound[identRope, TOCPointerRope, startPos] THEN RETURN;
isRelToc ¬ FALSE; -- not relative tocPointer
};
[] ¬ stream.GetChar[];
};
{
tocStart: INT ¬ stream.GetInt[]; -- int is position for TOCEntries entry
IF isRelToc THEN tocStart ¬ tocStart + start;
IF ( tocStart >= streamLen ) OR ( tocStart < start ) THEN {
Report["\n *** Bad TOCEntries pointer (%g) in entry at pos %g - some messages may not be available\n",
[integer[tocStart]], [integer[start]] ];
RETURN;
};
stream.SetIndex[tocStart];
};
{
entryRope: ROPE ~ stream.GetLineRope[];
IF ( NOT IsEntryFound[entryRope, start] ) THEN RETURN;
};
{
identRope: ROPE ~ stream.GetTokenRope[].token;
startPos: INT ~ stream.GetIndex[];
IF ( NOT IsIdentFound[identRope, RelTOCEntriesRope, startPos, TRUE] ) THEN {
IF ( NOT IsIdentFound[identRope, TOCEntriesRope, startPos] ) THEN RETURN;
isRelEntries ¬ FALSE;
};
[] ¬ stream.GetChar[];
};
thisRead ¬ stream.GetInt[];
[] ¬ stream.GetChar[];
totalMsgs ¬ totalMsgs + thisRead;
FOR i: INT IN [0..thisRead) DO
weAreHere: INT;
this: MsgHandle ~ NEW[MsgHandleRec];
this.charPos ¬ stream.GetIndex[]; -- where the del char goes
this.deleted ¬ stream.GetChar[] = '*; -- first char in loc line
this.unRead ¬ stream.GetChar[] = '?; -- second char in loc line
this.toc ¬ stream.GetLineRope[];
this.headersPos ¬ stream.GetInt[];
this.textLen ¬ stream.GetInt[];
this.formatPos ¬ stream.GetInt[];
this.formatLen ¬ stream.GetInt[];
this.entryStart ¬ stream.GetInt[];
this.entryLen ¬ stream.GetInt[];
[] ¬ stream.GetChar[];  -- trailing CR
IF isRelEntries THEN {
this.headersPos ¬ this.headersPos + start;
IF this.formatPos # 0 THEN this.formatPos ¬ this.formatPos + start;
this.entryStart ¬ this.entryStart + start;
};
weAreHere ¬ stream.GetIndex[]; -- GetMsgID will change the position
this.gvID ¬ GetMsgID[msInfo, this];
stream.SetIndex[weAreHere];
IF NOT this.deleted THEN numUndel ¬ numUndel + 1;
IF ( first = NIL )
THEN
{ current ¬ first ¬ this }
ELSE
{ current.next ¬ this; current ¬ current.next };
ENDLOOP;
IF customProcs#NIL AND customProcs.insertMsgs#NIL
THEN customProcs.insertMsgs[msInfo, first]
ELSE AppendMsgs[msInfo, first, current];
ENDLOOP;
EXITS notgood => NULL;
END;
};
DoCopyOrExpunge: PUBLIC PROC [msInfo: MsgSetInfo, logName: ROPE, altFileData: BCFileData, sortem: BOOL]
RETURNS[newList, newLast: MsgHandle ¬ NIL]~ {
msiData: MsiData ~ NARROW[msInfo.data];
numMsgs: INT;
msgStream, mailStrm: IO.STREAM;
segmentStart, tocStart, endMarker: INT;
entryLen, delta: INT;
msgStream ¬ altFileData.readStream;
mailStrm ¬ altFileData.writeStream;
BEGIN ENABLE IO.EndOfStream => {
Report["\n***IO.EndOfStream during CopyOrExpunge - quitting\n" ];
mailStrm.Close[];
IF msgStream # mailStrm THEN msgStream.Close[];
GOTO exit
};
segmentStart ¬ mailStrm.GetLength[];
mailStrm.SetIndex[segmentStart];
mailStrm.PutF[relTocPointerEntryTemplate, [integer[0]], [integer[0]] ];
msgStream.Flush[]; -- need to flush read stream when write stream changes
entryLen ¬ mailStrm.GetIndex[] - segmentStart;
IF sortem THEN SortByDate[msInfo, msiData.useFromFieldInTOC]; -- JKF
[numMsgs, newList] ¬ WriteMsgs[msInfo, mailStrm];
tocStart ¬ mailStrm.GetLength[];
msgStream.Flush[];
newLast ¬ WriteTOC[msgStream, mailStrm, numMsgs, segmentStart, newList, msiData.useFromFieldInTOC];
endMarker ¬ mailStrm.GetLength[];
delta ¬ endMarker - segmentStart;
mailStrm.SetIndex[segmentStart];
mailStrm.PutF[relTocPointerEntryTemplate,
[integer[entryLen]], [integer[tocStart-segmentStart]] ];
msgStream.Close[];
IF mailStrm # msgStream THEN mailStrm.Close[];
EXITS
exit => RETURN
END;
Report["\n%g messages written to %g\n", [integer[numMsgs]], [rope[logName]] ];
};
DoAppendMsg: PUBLIC PROC[msInfo: MsgSetInfo, msgID, plainText, formatting: ROPE] = {
writer: IO.STREAM ~ msInfo.fileData.writeStream;
msiData: MsiData ~ NARROW[msInfo.data];
posForEntry: INT ¬ writer.GetLength[];
entryLen, posForMsg, msgTemplatePos, headersPos, textLen, formatPos, formatLen: INT;
initialCR: BOOL ¬ FALSE;
msgH, last: MsgHandle;
writer.SetIndex[posForEntry];
writer.PutF[relTocPointerEntryTemplate, [integer[0]], [integer[0]] ];
entryLen ¬ writer.GetIndex[] - posForEntry;
posForMsg ¬ writer.GetLength[];
writer.PutF1[headerEntryTemplate, [integer[0]] ];
msgTemplatePos ¬ writer.GetIndex[];
writer.PutF[createMsgTemplate, [rope[msgID]], [integer[0]], [integer[0]] ];
headersPos ¬ writer.GetIndex[];
IF ( formatting # NIL ) AND ( plainText.Length[] > 0 ) THEN {
firstChar: CHAR ~ plainText.Fetch[0];
IF ( initialCR ¬ ( firstChar = '\r )) THEN plainText ¬ plainText.Substr[1];
};
textLen ¬ plainText.Length[];
writer.PutRope[plainText];
IF formatting # NIL THEN {
formatPos ¬ writer.GetLength[];
formatLen ¬ formatting.Length[];
}
ELSE formatPos ¬ formatLen ¬ 0;
writer.PutRope[formatting];
writer.PutChar['\r];
IF ( NOT initialCR ) AND ( formatting # NIL ) THEN {
textLen ¬ textLen - 1;
headersPos ¬ headersPos + 1;
};
writer.SetIndex[msgTemplatePos];
writer.PutF[createMsgTemplate, [rope[msgID]], [integer[textLen]], [integer[formatLen]] ];
StrmFlush[writer, msiData];
msgH ¬ NEW[MsgHandleRec ¬ [gvID: msgID, headersPos: headersPos, textLen: textLen, formatPos: formatPos, formatLen: formatLen] ];
writer.SetIndex[posForMsg];
msgH.entryStart ¬ posForMsg;
msgH.entryLen ¬ writer.GetLength[] - posForMsg;
writer.PutF1[headerEntryTemplate, [integer[msgH.entryLen]] ];
msgH.unRead ¬ FALSE;
{
now: INT ~ writer.GetLength[];
entryLen: INT ~ posForMsg-posForEntry;
writer.SetIndex[posForEntry];
writer.PutF[relTocPointerEntryTemplate, [integer[entryLen]], [integer[now - posForEntry]] ];
last ¬ WriteTOC[msInfo.fileData.readStream, msInfo.fileData.writeStream, 1, posForEntry, msgH, msiData.useFromFieldInTOC];
CloseAndOpen[msInfo];
};
IF customProcs#NIL AND customProcs.newMail#NIL
THEN customProcs.newMail[msInfo, msgH];
IF customProcs#NIL AND customProcs.insertMsgs#NIL
THEN customProcs.insertMsgs[msInfo, msgH]
ELSE AppendMsgs[msInfo, msgH, last];
};
AppendMsgs: PUBLIC PROC [msInfo: MsgSetInfo, first: MsgHandle, last: MsgHandle] ~ {
SELECT TRUE FROM
( first = NIL ) => { NULL }; -- no messages in this chunk
( msInfo.last = NIL ) => { -- splice onto front of msInfo
msInfo.first ¬ first;
msInfo.last ¬ last;
};
ENDCASE => { -- splice onto tail of msInfo
msInfo.last.next ¬ first;
msInfo.last ¬ last;
};
};
DateObj: TYPE = RECORD [
date: BasicTime.GMT,
mh: MsgHandle];
SortByDate: PROC [msInfo: MsgSetInfo, useFromFieldInTOC: BOOL] = {
EachNode: RedBlackTree.EachNode --PROC [data: UserData] RETURNS [stop: BOOL ¬ FALSE]-- = {
ro: REF DateObj ¬ NARROW[data];
mh: MsgHandle ¬ ro.mh;
mh.next ¬ NIL;
IF first = NIL THEN first ¬ mh ELSE last.next ¬ mh;
last ¬ mh};
nullMsg: MsgHandleRec = [NIL, NIL, FALSE, TRUE, 0, 0, 0, 0, 0, 0, 0, NIL, NIL, NIL, NIL];
first: MsgHandle ¬ NIL;
last: MsgHandle ¬ NIL;
reader: STREAM ¬ msInfo.fileData.readStream;
table: RedBlackTree.Table ¬ RedBlackTree.Create[GetKey, Compare];
FOR msgH: MsgHandle ¬ msInfo.first, msgH.next WHILE ( msgH # NIL ) DO
do: REF DateObj ¬ NEW[DateObj ¬ [BasicTime.nullGMT, msgH]];
reader.SetIndex[msgH.headersPos];
[, do.date] ¬ TOCFromStream[reader, msgH.textLen, useFromFieldInTOC];
just throw away duplicate entries:
RedBlackTree.Insert[table, do, do ! RedBlackTree.DuplicateKey => {
savednext: MsgHandle ¬ msgH.next;
IF msgH.tocButton # NIL THEN TiogaButtons.DeleteButton[msgH.tocButton];
msgH­ ¬ nullMsg;
msgH.next ¬ savednext;
CONTINUE}];
ENDLOOP;
RedBlackTree.EnumerateIncreasing[table, EachNode];
msInfo.first ¬ first;
msInfo.last ¬ last;
RedBlackTree.DestroyTable[table];
};
GetKey: RedBlackTree.GetKey --PROC [data: UserData] RETURNS [Key]-- = {
rr: REF DateObj ¬ NARROW[data];
RETURN[rr]
};
Compare: RedBlackTree.Compare --PROC [k: Key, data: UserData] RETURNS [Basics.Comparison]-- = {
lo: REF DateObj ¬ NARROW[data];
ro: REF DateObj ¬ NARROW[k];
period: INT ¬ BasicTime.Period[lo.date, ro.date];
SELECT TRUE FROM
period < 0 => RETURN[less];
period > 0 => RETURN[greater];
ENDCASE => RETURN[equal]
};
xxx
ReadAnyNewMail: PUBLIC ENTRY PROC [msInfo: MsgSetInfo] ~ {
msiData: MsiData ~ NARROW[msInfo.data];
rh: MailRetrieve.Handle ~ GetRetrieveHandle[].mHandle;
posForMsgs: INT;
numMsgs: INT; msgH: MsgHandle;
last: MsgHandle;
IF rh = NIL THEN RETURN;
IF userRNameList = NIL THEN {
UpdateUserInternal[msiData];
IF userRNameList = NIL THEN { -- still NIL, tell user to login
Report["\n*****You must login - no mail retrieved\n"];
RETURN
};
};
Report["\n ~~~ Retrieving mail for %g @ %g\n", [rope[userRNameList.first.name]], [time[BasicTime.Now[]]] ];
DO   -- for each server
reader: IO.STREAM ~ msInfo.fileData.readStream; -- cf CloseAndOpen below
writer: IO.STREAM ~ msInfo.fileData.writeStream;
posForEntry: INT ¬ writer.GetLength[];
noMore, retrieveOK: BOOL ¬ FALSE;
writer.SetIndex[posForEntry];
[numMsgs, msgH, posForMsgs, noMore, retrieveOK] ¬ DoOneServer[msInfo, msiData, writer];
IF noMore THEN {
TRUSTED { Process.Detach[FORK ForceStateOff[]] };
RETURN;
}
ELSE IF NOT retrieveOK THEN {
IF ( writer.GetLength[] > posForEntry ) THEN
SetFileLength[msInfo, posForEntry];
LOOP;
};
now we go back and genereate the toc entries
IF numMsgs > 0 THEN {
now: INT ~ writer.GetLength[];
entryLen: INT ~ posForMsgs-posForEntry;
writer.SetIndex[posForEntry];
writer.PutF[relTocPointerEntryTemplate, [integer[entryLen]], [integer[now - posForEntry]] ];
};
last ¬ WriteTOC[msInfo.fileData.readStream, msInfo.fileData.writeStream, numMsgs, posForEntry, msgH, msiData.useFromFieldInTOC];
CloseAndOpen[msInfo];
IF msiData.okToFlushMail THEN -- now we can flush the server
MailRetrieve.Accept[rh ! MailRetrieve.Failed => {
Report["\n Messages not flushed: why=%g, msg=%g\n", [atom[FailedWhyToAtom[why]]], [rope[text]] ];
CONTINUE }]
ELSE Report["\n Messages not flushed - okToFlushMail is FALSE\n"];
IF customProcs#NIL AND customProcs.newMail#NIL
THEN customProcs.newMail[msInfo, msgH];
IF customProcs#NIL AND customProcs.insertMsgs#NIL
THEN customProcs.insertMsgs[msInfo, msgH]
ELSE AppendMsgs[msInfo, msgH, last];
ENDLOOP;
};
TOCFromStream: PROC [stream: STREAM, len: INT, useFromFieldInTOC: BOOL]
RETURNS [toc: ROPE, mDate: BasicTime.GMT ¬ BasicTime.nullGMT] ~ {
mPos: INT ¬ 0;
NextChar: PROC RETURNS [ ch: CHAR ] ~ {
IF ( mPos > len )
THEN ch ¬ MailParse.endOfInput
ELSE ch ¬ stream.GetChar[ ! IO.EndOfStream =>
{ mPos ¬ len; ch ¬ MailParse.endOfInput; CONTINUE } ];
mPos ¬ mPos.SUCC;
};
pH: MailParse.ParseHandle ¬ MailParse.InitializeParse[];
mFrom, mTo, mSubject, mSender: ROPE;
savedFrom, tocx, rDate: ROPE;
ch: CHAR ~ stream.PeekChar[];
IF ( ch = '\n ) OR ( ch = '\r ) THEN { [] ¬ stream.GetChar[]; mPos ¬ 1 };
ignore initial CR (tioga formatting nonsense)
{
ENABLE MailParse.ParseError => GOTO parseErrorExit;
fieldName: ROPE ¬ NIL;
wantThisField, continue: BOOL ¬ TRUE;
DO
fieldName ¬ MailParse.GetFieldName[pH, NextChar];
SELECT TRUE FROM
( fieldName = NIL ) => { EXIT };
( fieldName.Equal[date, FALSE] ) => {
body: ROPE ~ MailParse.GetFieldBody[pH, NextChar];
mDate ¬ Convert.TimeFromRope[body ! Convert.Error => CONTINUE ];
};
( fieldName.Equal[subject, FALSE] ) => {
mSubject ¬ MailParse.GetFieldBody[pH, NextChar];
};
( fieldName.Equal[sender, FALSE] ) => {
savedFrom ¬ mSender;
mSender ¬ MailParse.GetFieldBody[pH, NextChar];
};
( fieldName.Equal[to, FALSE] ) => {
mTo ¬ MailParse.GetFieldBody[pH, NextChar];
};
( fieldName.Equal[from, FALSE] ) => {
body: ROPE = MailParse.GetFieldBody[pH, NextChar];
IF mSender = NIL THEN mSender ¬ body ELSE savedFrom ¬ body;
};
ENDCASE => {
[] ¬ MailParse.GetFieldBody[pH, NextChar, TRUE];
};
ENDLOOP;
MailParse.FinalizeParse[pH];
EXITS
parseErrorExit => { MailParse.FinalizeParse[pH]};
};
IF ( mDate = BasicTime.nullGMT ) THEN mDate ¬ BasicTime.Now[];
IF ( ( useFromFieldInTOC ) AND ( savedFrom # NIL ) ) THEN mSender ¬ savedFrom;
mFrom ¬ IF ( IsCurrentUser[mSender] ) THEN Rope.Concat["To: ", mTo] ELSE mSender;
rDate ¬ Rope.Substr[Convert.RopeFromTimeRFC822[mDate, FALSE], 0, 9];
tocx ¬ rDate.Cat[" ", mFrom];
tocx ¬ SquashRopeIntoWidth[tocx, 165];
toc ¬ tocx.Concat[mSubject];
};
DoOneServer: PROC [msInfo: MsgSetInfo, msiData: MsiData, stream: IO.STREAM]
RETURNS [numMsgs: INT ¬ 0, msgHandle: MsgHandle, posForMsgs: INT ¬ 0, noMore: BOOL ¬ TRUE, retrieveOK: BOOL ¬ FALSE] ~ {
ENABLE UNWIND => GOTO unwinding;
rh: MailRetrieve.Handle ¬ GetRetrieveHandle[].mHandle;
lastMsgHandle: MsgHandle;
serverKnown: BOOL ¬ FALSE;
IF userRNameList = NIL THEN {
Report["\n*****You must login - no mail retrieved\n"];
RETURN
};
stream.SetIndex[stream.GetLength[]];
SELECT MailRetrieve.MailboxState[rh] FROM
badName => Report["\nSome mailbox reported badName - possibly no mailBox\n"];
badPwd => Report["\nSome mailbox reported badPwd\n"];
cantAuth => Report["\nSome server not found\n"];
ENDCASE => { NULL }; -- ok to try
{ -- do one server.
state: RetrieveState;
Cycle through the servers, until you find one that has mail.
serverState: MailRetrieve.ServerState; -- The state of the server.
serverName: MailBasics.RName;
serverType: MailRetrieve.ServerType;
thisMsgHandle, thisLastMsgHandle: MsgHandle;
{
ENABLE UNWIND => {
DO
[noMore, serverState, serverType] ¬ MailRetrieve.NextServer[rh]; -- cycle thru servers
IF ( NOT noMore ) THEN LOOP;
ENDLOOP;
};
[noMore, serverState] ¬ MailRetrieve.NextServer[rh];
IF ( noMore ) THEN RETURN; -- Last server? Then done.
serverKnown ¬ TRUE;
serverName ¬ MailRetrieve.ServerName[rh];
Report["%g: ", [rope[serverName.name]] ];
SELECT serverState FROM
unknown => { Report[" did not respond\n"] };
empty => { Report[" empty\n"] };
notEmpty => {
curPos: INT ¬ stream.GetLength[];
stream.PutF[relTocPointerEntryTemplate, [integer[0]], [integer[0]] ];
posForMsgs ¬ stream.GetLength[];
[numMsgs, state, msgHandle, lastMsgHandle] ¬ DrainServer[stream, rh, msiData];
IF ( numMsgs = -1 ) THEN {
stream.SetLength[curPos];
StrmFlush[stream, msiData];
Report[" no messages (%g) retrieved?\n", [integer[numMsgs]] ]
}
ELSE {
Report[" %g messages\n", [integer[numMsgs]] ];
retrieveOK ¬ TRUE;
};
};
ENDCASE => { NULL };
};
}; -- End of servers loop, exit.
IF ( NOT serverKnown ) THEN Report["\nNoMailboxes"];
EXITS
unwinding => Report["UNWIND during newMail\n" ];
};
DrainServer: PROC [stream: IO.STREAM, rh: MailRetrieve.Handle, msiData: MsiData]
RETURNS [num: INT, state: RetrieveState, msgH, lastMsgH: MsgHandle] ~ {
Reads mail from the next grapevine server via gvH.
ENABLE UNWIND => NULL;
msgState: MsgState;
thisMsgH: MsgHandle;
num ¬ 0;
DO
[msgState, state, thisMsgH] ¬ ReadMessageItems[stream, rh, msiData];
SELECT msgState FROM
noMore => { EXIT };
OK, wasDeleted => { NULL };
readButNotFlushed => { NULL }; -- will do something different eventually
retrieveFailed => { num ¬ -1; EXIT };
ENDCASE => { ERROR };
IF ( NOT ( msgState = wasDeleted ) ) THEN {
IF ( num ¬ num.SUCC ) MOD 10 = 0 THEN Report ["! "] ELSE Report["."];
};
IF ( msgH = NIL )
THEN msgH ¬ lastMsgH ¬ thisMsgH
ELSE IF ( thisMsgH # NIL )
THEN { lastMsgH.next ¬ thisMsgH; lastMsgH ¬ thisMsgH };
ENDLOOP;
};
MsgState: TYPE ~ { OK, retrieveFailed, noMore, wasDeleted, readButNotFlushed };
errorRope: ROPE ~ " ... %g error, exp: %g\n";
ReadMessageItems: PROC [stream: IO.STREAM, rh: MailRetrieve.Handle, msiData: MsiData]
RETURNS [msgState: MsgState, state: RetrieveState, msgH: MsgHandle] ~ {
This routine reads the next message on this connection,
returning MsgState = noMore when there aren't any more.
ENABLE BEGIN
MailRetrieve.Failed => {
state ¬ FailedWhyToAtom[why];
Report[errorRope, [atom[state]], [rope[text]] ];
GOTO retFailed;
};
IO.Error => {
state ¬ IO.AtomFromErrorCode[ec];
Report[errorRope, [atom[state]], [rope[msg]] ];
GOTO retFailed;
};
END;
msgExists, archived, deleted, read: BOOL;
msgState ¬ OK;
state ¬ $OK;
[msgExists, archived, deleted, read] ¬ MailRetrieve.NextMessage[rh];
SELECT TRUE FROM
( deleted ) => { msgState ¬ wasDeleted; RETURN };
( NOT msgExists ) => { msgState ¬ noMore; RETURN };
( read ) => msgState ¬ readButNotFlushed; -- more on this later
ENDCASE => { NULL };
{
Now read all the items in the message, terminating on the LastItem,
and skipping the ones that we're not yet interested in.
ok: BOOL;
[ok, msgH] ¬ MsgToStream[rh, stream, msiData];
IF ( NOT ok ) THEN msgState ¬ retrieveFailed;
};
EXITS
retFailed => { msgState ¬ retrieveFailed };
};
FailedWhyToAtom: PROC[why: MailRetrieve.FailureReason] RETURNS[a: ATOM] ~ {
a ¬ SELECT why FROM
$communicationFailure => $communicationFailure,
$noSuchServer => $noSuchServer,
$connectionRejected => $connectionRejected,
$badCredentials => $badCredentials,
$unknownFailure => $unknownFailure,
ENDCASE =>  $unknownError;
};
IdOnFileWithSender: PROC [ts: MailBasics.Timestamp, sender: MailBasics.RName]
RETURNS [idOnFile: ROPE] ~ {
msgNameWithSenderFormat: ROPE ~ "%g %g";
thisSender: ROPE ¬ sender.name;
IF ( thisSender.Fetch[0] = '" ) THEN {
pos: INT ~ thisSender.Find["\"", 1];
IF ( pos # -1 ) THEN thisSender ¬ Rope.Concat[thisSender.Substr[1, pos.PRED], thisSender.Substr[pos.SUCC]];
};
RETURN[IO.PutFR[msgNameWithSenderFormat, [rope[thisSender]], [rope[ts]]] ];
};
MsgToStream: PROC [rh: MailRetrieve.Handle, stream: STREAM, msiData: MsiData]
RETURNS [ok: BOOL ¬ FALSE, msgH: MsgHandle ¬ NIL] ~ {
writes a createMsg entry, then copies the message from grapevine to the stream, returns ok if there were no errors; flushes the stream; catches errors and returns FALSE
entryPos: INT ~ stream.GetLength[];
{
ENABLE UNWIND => { stream.SetLength[entryPos] }; -- might this fail if out of space?
msgTemplatePos: INT;
headersPos, textLen: INT;
formatPos, formatLen: INT ¬ 0;
initialCR: BOOL ¬ FALSE;
timeStamp: MailBasics.Timestamp; sender: MailBasics.RName;
msg, plainText, formatting: ROPE;
[timeStamp, sender, ] ¬ MailRetrieve.StartMessage[rh];
msg ¬ IdOnFileWithSender[timeStamp, sender];
stream.SetIndex[entryPos]; -- make sure at end
other fields are set later & entry is re-written
stream.PutF1[headerEntryTemplate, [integer[0]] ];
msgTemplatePos ¬ stream.GetIndex[];
stream.PutF[createMsgTemplate, [rope[msg]], [integer[0]], [integer[0]] ];
headersPos ¬ stream.GetIndex[];
[plainText, formatting, textLen, , ] ¬
MailMessage.ReadOneMessageX[rh, timeStamp, sender.name];
IF ( textLen = -1 ) THEN {
stream.SetLength[entryPos];
StrmFlush[stream, msiData];
RETURN
};
IF ( formatting # NIL ) AND ( plainText.Length[] > 0 ) THEN {
firstChar: CHAR ~ plainText.Fetch[0];
IF ( initialCR ¬ ( firstChar = '\r )) THEN plainText ¬ plainText.Substr[1];
};
textLen ¬ plainText.Length[];
stream.PutRope[plainText];
IF formatting # NIL THEN {
formatPos ¬ stream.GetLength[];
formatLen ¬ formatting.Length[];
};
stream.PutRope[formatting];
stream.PutChar['\r];
{ endPos: INT ~ stream.GetLength[]; }; -- ASSERT end-header=text+format?
IF ( NOT initialCR ) AND ( formatting # NIL ) THEN {
textLen ¬ textLen - 1;
headersPos ¬ headersPos + 1;
};
stream.SetIndex[msgTemplatePos];
stream.PutF[createMsgTemplate, [rope[msg]], [integer[textLen]], [integer[formatLen]] ];
StrmFlush[stream, msiData];
msgH ¬ NEW[MsgHandleRec ¬ [headersPos: headersPos, textLen: textLen, formatPos: formatPos, formatLen: formatLen] ];
stream.SetIndex[entryPos];
msgH.entryStart ¬ entryPos;
msgH.entryLen ¬ stream.GetLength[] - entryPos;
stream.PutF1[headerEntryTemplate, [integer[msgH.entryLen]] ];
ok ¬ TRUE;
};
};
SquashRopeIntoWidth: PROC [s: ROPE, colWidth: INT] RETURNS [ROPE] ~ {
Truncates s with "..." or expands it with blanks, so that it is about colWidth characters wide. Not exact, uses a few heuristics here...
width: INT;
{
ENABLE RuntimeError.BoundsFault => { GOTO doItTheHardWay };
width ¬ VFonts.StringWidth[s];
DO
IF ( width <= colWidth ) THEN EXIT;
-- truncate
{
guessLength: INT ¬ s.Length[] * colWidth / width;
s ¬ Rope.Concat[s.Substr[0, MAX[0, guessLength-4]], "..."];
width ¬ VFonts.StringWidth[s];
};
ENDLOOP;
EXITS
doItTheHardWay => { [width, s]¬ DoItTheHardWay[s, colWidth] };
};
{
At this point s is shorter than colWidth and we want to extend it with blanks
blankCount: INT ~ ( (colWidth - width) / blankWidth ) + 1; -- force at least one blank
some: INT ~ MIN[blankCount, blanks.Length[]];
white: ROPE ~ blanks.Substr[len: some];
s ¬ s.Concat[white];
RETURN[s]
};
};
DoItTheHardWay: PROC [s: ROPE, colWidth: INT] RETURNS [width: INT, s1: ROPE] ~ {
thisWidth: INTEGER;
dots: ROPE = "...";
nullWidth: INTEGER = VFonts.CharWidth['\000];
width¬ VFonts.StringWidth[dots];
FOR i: INT IN [0 .. s.Length[]) DO
thisWidth¬ VFonts.CharWidth[s.Fetch[i]
! RuntimeError.BoundsFault => thisWidth ¬ nullWidth];
width ¬ width + thisWidth;
IF ( width > colWidth ) THEN {
width ¬ width - thisWidth;
s1¬ Rope.Concat[s.Substr[0, MAX[0, i.PRED]], dots];
RETURN
};
ENDLOOP;
s1¬ s.Concat[dots];
};
RemoveComments: PROC [name: ROPE] RETURNS [shortName: ROPE] ~ {
start, end: INT;
name ¬ name.Concat[" "];
first remove any "< . . .>" in the name
start ¬ name.Find["<"];
IF ( start > 0 ) THEN {
end ¬ name.Find[">", start.SUCC];
IF ( end > 0 ) THEN name ¬ name.Replace[start, end-start+1];
};
then do the same for any ( . . . ) in the name
start ¬ name.Find["("];
IF ( start > 0 ) THEN {
end ¬ name.Find[")", start.SUCC];
IF ( end > 0 ) THEN name ¬ name.Replace[start, end-start+1];
};
shortName ¬ name.Substr[len: name.Length[].PRED]
};
Buffered Stream Copy
bufsiz: NAT ~ RefText.page;
copyBuffer: REF TEXT ¬ NEW[TEXT[bufsiz]];
StrmToStrmCopy: PROC [to, from: IO.STREAM] ~ {
DO
IF ( from.GetBlock[copyBuffer, 0, bufsiz] = 0 ) THEN EXIT;
to.PutBlock[copyBuffer];
ENDLOOP
};
CopyBytes: PROC [to, from: IO.STREAM, num: INT] ~ {
bytes: INT ¬ num;
WHILE ( bytes >= bufsiz ) DO
[] ¬ from.GetBlock[copyBuffer, 0, bufsiz];
to.PutBlock[copyBuffer];
bytes ¬ bytes - bufsiz;
ENDLOOP;
IF ( bytes # 0 ) THEN {
[] ¬ from.GetBlock[copyBuffer, 0, bytes];
to.PutBlock[copyBuffer];
};
};
Mail polling
END.