WalnutNewMailImpl.mesa
Copyright Ó 1984, 1988, 1989, 1992 by Xerox Corporation. All rights reserved.
Willie-Sue, November 27, 1989 4:16:54 pm PST
Walnut New Mail Operations Implementation
Terry, August 30, 1990 11:40 am PDT
Willie-s, August 12, 1993 11:11 am PDT
DIRECTORY
BasicTime USING [Now],
FS,
MailBasics,
MailBasicsFileTypes,
MailBasicsItemTypes,
MailMessage USING [ReadOneMessage],
MailRetrieve USING [Failed, Handle, MBXState, ServerState, Accept, Close, Create, MailboxState, NextMessage, NextServer, NewUser, ServerName, StartMessage],
MailUtils USING [Credentials],
Idle USING [IdleHandler, IdleRegistration, RegisterIdleHandler],
IO,
Process USING [Detach, Pause, SecondsToTicks],
RefText,
Rope,
WalnutKernelDefs,
WalnutNewMail USING [],
WalnutDefs USING [CheckReportProc, Error, RetrieveState, ServerResponse, WalnutOpsHandle],
WalnutOps USING [ EndNewMail],
WalnutOpsInternal,
WalnutRoot USING [FlushMailStream, GetOpsHandleList, RootHandle, RootHandleRec],
WalnutSendOps USING [TiogaCTRL],
WalnutStream USING [AbortStream, IdOnFileWithSender, WriteEntry];
WalnutNewMailImpl: CEDAR MONITOR
IMPORTS BasicTime, FS, Idle, IO, MailMessage, MailRetrieve, Process, Rope, WalnutDefs, WalnutOps, WalnutRoot, WalnutStream
EXPORTS
WalnutDefs, WalnutNewMail
= BEGIN
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
CheckReportProc: TYPE = WalnutDefs.CheckReportProc;
WalnutOpsHandle: TYPE = WalnutDefs.WalnutOpsHandle;
RootHandle: TYPE = WalnutRoot.RootHandle;
RootHandleRec: PUBLIC TYPE = WalnutRoot.RootHandleRec;
KernelHandle: TYPE = WalnutOpsInternal.KernelHandle;
KernelHandleRec: PUBLIC TYPE = WalnutOpsInternal.KernelHandleRec;
TiogaCTRL: MailBasics.ItemType = WalnutSendOps.TiogaCTRL;
TiogaCTRL: MailBasics.ItemType = MailBasicsItemTypes.tioga1;
MailHandle: TYPE = WalnutOpsInternal.MailHandle;
MailHandleObject: PUBLIC TYPE = WalnutOpsInternal.MailHandleObject;
Variables
copyBuffer: REF TEXT ¬ NEW[TEXT[RefText.page]];
newMailCreateMsg: WalnutKernelDefs.MsgLogEntry ¬ NEW[CreateMsg WalnutKernelDefs.LogEntryObject];
idled: BOOL ¬ FALSE; -- this is true if the machine has been put in the idle state
idleReg: Idle.IdleRegistration;
msgPollingInterval: INT ¬ 300;  -- seconds between pollings
checkingRope: ROPE = "Checking for new mail ...";
Procedures
Watch the mailbox & retrieve mail when available
EnableMailRetrieval: PUBLIC ENTRY PROC[
opsHandle: WalnutOpsHandle,
registeredUsers: LIST OF MailUtils.Credentials,
reportProc, progressProc: CheckReportProc,
getMailLog: PROC[WalnutOpsHandle] RETURNS[STREAM],
recordMailInfo: PROC[opsHandle: WalnutOpsHandle, logLen: INT, server: ROPE, num: INT] RETURNS[BOOL],
notifyWhenMailRetrieved: PROC[opsHandle: WalnutOpsHandle, ok: BOOL, someMail: BOOL] ] = {
ENABLE UNWIND => NULL;
this: MailHandle;
IF idleReg = NIL THEN idleReg ¬ Idle.RegisterIdleHandler[NewMailIdleProc];
make sure that only one idle proc is registered here!
IF opsHandle.kernelHandle.mailHandle # NIL THEN RETURN;
this ¬ NEW[WalnutOpsInternal.MailHandleObject ¬ [
procForReporting: reportProc,
procForProgress: progressProc,
notifyProc: notifyWhenMailRetrieved,
registeredUsers: registeredUsers,
lastStatus: checkingRope,
getMailLogProc: getMailLog,
recordMailInfoProc: recordMailInfo,
mrHandle: MailRetrieve.Create[ msgPollingInterval, WatchMailBox ]
] ];
opsHandle.kernelHandle.mailHandle ¬ this;
TRUSTED{ Process.Detach[ FORK StartIt[this.mrHandle, this.registeredUsers] ] }
};
StartIt: PROC[mH: MailRetrieve.Handle, users: LIST OF MailUtils.Credentials] = {
FOR rnL: LIST OF MailUtils.Credentials ¬ users, rnL.rest UNTIL rnL = NIL DO
MailRetrieve.NewUser[mH, rnL.first.rName, rnL.first.password]
ENDLOOP;
};
DisableMailRetrieval: PUBLIC ENTRY PROC[opsHandle: WalnutOpsHandle] = {
ENABLE UNWIND => NULL;
this: MailHandle = opsHandle.kernelHandle.mailHandle;
opsHandle.kernelHandle.mailHandle ¬ NIL;
IF this # NIL THEN
IF this.mailInProgress THEN this.needsClosing ¬ TRUE
ELSE {
this.hasBeenClosed ¬ TRUE;
TRUSTED { Process.Detach[ FORK MailRetrieve.Close[this.mrHandle] ] }
};
};
GetLastMailBoxStatus: PUBLIC ENTRY PROC[opsHandle: WalnutOpsHandle]
RETURNS[mbxState: MailRetrieve.MBXState, status: ROPE] = {
ENABLE UNWIND => NULL;
this: MailHandle = opsHandle.kernelHandle.mailHandle;
IF this = NIL THEN RETURN[unknown, NIL];
RETURN[this.lastStateReported, this.lastStatus]
};
CheckMailBoxes: PUBLIC ENTRY PROC[opsHandle: WalnutOpsHandle] = {
ENABLE UNWIND => NULL;
this: MailHandle = opsHandle.kernelHandle.mailHandle;
IF ( this = NIL ) OR ( this.mailInProgress ) THEN RETURN;
this.tryForNewMail ¬ FALSE;
this.mailInProgress ¬ TRUE;
TRUSTED { Process.Detach[FORK AutoNewMailProc[opsHandle, this]] }
};
CheckMailReport: CheckReportProc = {
this: MailHandle = opsH.kernelHandle.mailHandle;
IF ( this # NIL ) AND ( this.procForReporting # NIL ) THEN this.procForReporting[opsH, format, v1, v2, v3];
};
ProgressReport: CheckReportProc = {
this: MailHandle = opsH.kernelHandle.mailHandle;
IF this = NIL THEN RETURN; -- race condition
IF this.procForProgress # NIL THEN this.procForProgress[opsH, format, v1, v2, v3];
};
NewMailIdleProc: ENTRY Idle.IdleHandler = {
ENABLE UNWIND => NULL;
idled ¬ reason = becomingIdle;
FOR wL: LIST OF WalnutOpsHandle ¬ WalnutRoot.GetOpsHandleList[], wL.rest UNTIL wL = NIL DO
this: MailHandle;
opsH: WalnutOpsHandle = wL.first;
IF ( opsH.kernelHandle = NIL ) OR ( ( this ¬ opsH.kernelHandle.mailHandle ) = NIL ) THEN LOOP;
IF this.tryForNewMail AND NOT idled THEN TRUSTED {
this.tryForNewMail ¬ FALSE;
this.mailInProgress ¬ TRUE;
Process.Detach[FORK AutoNewMailProc[wL.first, this]]
};
ENDLOOP;
};
WatchMailBox: ENTRY PROC[mrH: MailRetrieve.Handle, newState: MailRetrieve.MBXState] = {
This is called when the condition of the mailbox changes
If there is new mail, then tryForNewMail is true, unless idle is FALSE and the new mail retrieval process succeeds
ENABLE UNWIND => NULL;
status: ROPE;
this: MailHandle;
opsH: WalnutOpsHandle;
IF newState = unknown THEN RETURN;
opsH ¬ FindOpsHandle[mrH];
IF ( opsH = NIL ) OR ( this ¬ opsH.kernelHandle.mailHandle ) = NIL THEN RETURN;
IF ( this.lastStateReported = notEmpty ) AND
( ( newState = someEmpty ) OR ( newState = allEmpty ) ) THEN
status ¬ "New mail was read"
ELSE
{ SELECT newState FROM
badName => status ¬ "Your user name is invalid, please log in";
badPwd => status ¬ "Your password is invalid";
cantAuth => status ¬ "Can't check your credentials at this time";
userOK => status ¬ "Your credentials are OK";
allDown => status ¬ "All of the mail servers are down";
someEmpty => status ¬ "All of the mail servers checked are empty";
allEmpty => status ¬ "There is no new mail";
notEmpty => status ¬ "You have new mail";
ENDCASE => status ¬ "Bad State!";
};
this.lastStateReported ¬ newState;
this.lastStatus ¬ status;
this.tryForNewMail ¬ NOT this.mailInProgress AND
( ( this.tryForNewMail ) OR ( newState = notEmpty ) );
IF ( this.tryForNewMail ) AND ( NOT idled ) THEN TRUSTED {
this.tryForNewMail ¬ FALSE;
this.mailInProgress ¬ TRUE;
Process.Detach[FORK AutoNewMailProc[FindOpsHandle[mrH], this]] };
};
MailFetchDone: ENTRY PROC[this: MailHandle] = {
IF ( this.needsClosing ) AND ( NOT this.hasBeenClosed ) THEN {
this.hasBeenClosed ¬ TRUE;
TRUSTED { Process.Detach[ FORK MailRetrieve.Close[this.mrHandle] ]};
};
this.mailInProgress ¬ FALSE
};
AutoNewMailProc: PROC[opsH: WalnutOpsHandle, this: MailHandle] = {
ok, someMail: BOOL;
IF this.registeredUsers = NIL THEN RETURN;
DO
[ok, someMail] ¬ DoNewMail[opsH, this];
IF ok THEN EXIT;
IF ( this.notifyProc # NIL ) THEN this.notifyProc[opsH, ok, someMail];
Process.Pause[Process.SecondsToTicks[60]];
ENDLOOP;
MailFetchDone[this];
IF ( this.notifyProc # NIL ) THEN this.notifyProc[opsH, ok, someMail];
};
FindOpsHandle: PROC[mrH: MailRetrieve.Handle] RETURNS[opsH: WalnutOpsHandle] = {
FOR opsL: LIST OF WalnutOpsHandle ¬ WalnutRoot.GetOpsHandleList[], opsL.rest UNTIL opsL = NIL DO
IF ( opsL.first.kernelHandle.mailHandle # NIL ) AND
( opsL.first.kernelHandle.mailHandle.mrHandle = mrH ) THEN RETURN[opsL.first];
ENDLOOP;
RETURN[NIL];
};
DoNewMail: PROC[opsH: WalnutOpsHandle, mH: MailHandle] RETURNS[ok: BOOL, someMail: BOOL] = {
mailStream: STREAM;
serverKnown: BOOL ¬ FALSE;
sr: WalnutDefs.ServerResponse;
errMsg: ROPE;
err: ROPE = " during new mail; retrieval abandoned";
someMail ¬ FALSE;
ok ¬ FALSE;
BEGIN ENABLE BEGIN
WalnutDefs.Error => {
errMsg ¬ "\n WalnutDefs Error during New Mail; retrieval abandoned";
GOTO noGood;
};
FS.Error => { errMsg ¬ error.explanation.Concat[err]; GOTO noGood};
IO.Error => {
ed: FS.ErrorDesc;
<<ed ¬ FS.ErrorFromStream[stream ! IO.Error, FS.Error => CONTINUE];>>
IF ed.explanation # NIL THEN
errMsg ¬ ed.explanation.Concat[err]
ELSE errMsg ¬ "IO Error during new mail; retrieval abandoned";
GOTO noGood
};
END;
IF CheckForStopping[mH] THEN RETURN[TRUE, FALSE];
IF mH.getMailLogProc = NIL THEN RETURN[FALSE, FALSE]; -- had been shut down
IF ( mailStream ¬ mH.getMailLogProc[opsH] ) = NIL THEN {
ProgressReport[opsH, "\tCan't get newMailLog for %g @ %g\n", [rope[mH.registeredUsers.first.rName.name]], [time[BasicTime.Now[]]] ];
RETURN[FALSE, FALSE]
};
IF CheckForStopping[mH] THEN RETURN[TRUE, FALSE];
ProgressReport[opsH, "\n ~~~ Retrieving mail for %g @ %g\n", [rope[mH.registeredUsers.first.rName.name]], [time[BasicTime.Now[]]] ];
mailStream.SetIndex[mailStream.GetLength[]];
SELECT MailRetrieve.MailboxState[mH.mrHandle] FROM
badName => {
msg: ROPE ~ "\nSome mailbox reported badName - possibly no mailBox\n";
CheckMailReport[opsH, "%g\n", [rope[errMsg]] ];
ProgressReport[opsH, "%g\n", [rope[msg]] ];
};
badPwd => {
msg: ROPE ~ "\nSome mailbox reported badPwd\n";
CheckMailReport[opsH, "%g\n", [rope[errMsg]] ];
ProgressReport[opsH, "%g\n", [rope[msg]] ];
};
cantAuth => {
msg: ROPE ~ "\nSome server not found\n";
CheckMailReport[opsH, "%g\n", [rope[errMsg]] ];
ProgressReport[opsH, "%g\n", [rope[msg]] ];
};
ENDCASE; --ok to try
DO    -- Loops over servers.
state: WalnutDefs.RetrieveState;
num: INT ¬ 0;  -- the number of messages read from server.
Cycle through the servers, until you find one that has mail.
noMore: BOOLEAN;   -- TRUE if no more servers.
serverState: MailRetrieve.ServerState; -- The state of the server.
thisServer: MailBasics.RName;
BEGIN
ENABLE UNWIND => {
DO
[noMore, serverState] ¬ MailRetrieve.NextServer[mH.mrHandle]; -- cycle thru servers
IF ~noMore THEN LOOP;
ENDLOOP;
};
-- Step through the servers.
IF CheckForStopping[mH] THEN RETURN[TRUE, FALSE];
[noMore, serverState] ¬ MailRetrieve.NextServer[mH.mrHandle];
IF noMore THEN EXIT;   -- Last server? Then done.
serverKnown ¬ TRUE;
thisServer ¬ MailRetrieve.ServerName[mH.mrHandle];
ProgressReport[opsH, "(%g) %g: ", [atom[thisServer.ns]], [rope[thisServer.name]] ];
SELECT serverState FROM
unknown => state ¬ $didNotRespond;
empty => state ¬ $OK;
notEmpty => {
curPos: INT ¬ mailStream.GetLength[];
[num, state] ¬ DrainServer[mailStream, opsH, mH];
IF num = -1 THEN {
mailStream.SetLength[curPos];
WalnutRoot.FlushMailStream[opsH]
};
};
ENDCASE;
IF serverState = notEmpty AND state = $OK AND okToFlush THEN {
sr.state ¬ state;
sr.num ¬ num;
IF num # -1 THEN {
IF ~mH.recordMailInfoProc[opsH, mailStream.GetLength[], thisServer.name, num]
THEN { ReleaseNewMail[opsH, mailStream]; ok ¬ FALSE; RETURN };
now we can flush the server
MailRetrieve.Accept[mH.mrHandle ! MailRetrieve.Failed => {state ¬ $FlushFailed; CONTINUE}];
someMail ¬ TRUE;
};
};
IF mH.procForProgress # NIL THEN {
SELECT serverState FROM
unknown => ProgressReport[opsH, " did not respond\n"];
empty => ProgressReport[opsH, " empty\n"];
notEmpty =>
IF num = -1 THEN ProgressReport[opsH, " retrieval abandoned\n"]
ELSE ProgressReport[opsH, " %g messages\n", [integer[num]] ];
ENDCASE;
IF state = $FlushFailed THEN
ProgressReport[opsH, "\n Flush of (%g) %g failed; you may get some messages again\n", [atom[thisServer.ns]], [rope[thisServer.name]] ];
};
END;
ENDLOOP; -- End of servers loop, exit.
ok ¬ TRUE;
WalnutOps.EndNewMail[opsH];  -- release the newMailLog
IF NOT serverKnown THEN CheckMailReport[opsH, "NoMailboxes\n"];
EXITS
noGood => {
CheckMailReport[opsH, "%g\n", [rope[errMsg]] ];
ProgressReport[opsH, "%g\n", [rope[errMsg]] ];
ReleaseNewMail[opsH, mailStream]; -- release the newMailLog
};
END;
};
ReleaseNewMail: PROC[opsH: WalnutOpsHandle, strm: STREAM] = {
IF strm # NIL THEN
WalnutStream.AbortStream[strm ! IO.Error, FS.Error => CONTINUE]; -- ignore errors
WalnutOps.EndNewMail[opsH ! WalnutDefs.Error => CONTINUE];
};
okToFlush: BOOL ¬ TRUE;  -- for debugging
DrainServer: PROC[strm: STREAM, opsH: WalnutOpsHandle, mH: MailHandle]
RETURNS[num: INT, state: WalnutDefs.RetrieveState] = {
Reads mail from the next grapevine server via mH.
ENABLE UNWIND => NULL;
msgState: MsgState;
num ¬ 0;
DO
[msgState, state] ¬ ReadMessageItems[strm, opsH, mH];
SELECT msgState FROM
noMore => EXIT;
OK, wasDeleted => NULL;
retrieveFailed => {num ¬ -1; EXIT};
ENDCASE => ERROR;
IF NOT (msgState = wasDeleted) THEN {
IF (num ¬ num + 1) MOD 10 = 0 THEN ProgressReport[opsH, "! "]
ELSE ProgressReport[opsH, "."];  -- report progress
};
ENDLOOP;
};
MsgState: TYPE = {OK, retrieveFailed, noMore, wasDeleted};
ReadMessageItems: PROC[strm: STREAM, opsH: WalnutOpsHandle, mH: MailHandle]
RETURNS [msgState: MsgState, state: WalnutDefs.RetrieveState] = {
-- This routine reads the next message on this connection, returning MsgState = noMore when there aren't any more.
ENABLE MailRetrieve.Failed => {
state ¬ why;
GOTO thisFailed;
};
msgExists, archived, deleted, ok: BOOLEAN;
timeStamp: MailBasics.Timestamp;
sender: MailBasics.RName;
IF CheckForStopping[mH] THEN {
ProgressReport[opsH, " stopping retrieval\n"];
RETURN[retrieveFailed, $OK];
};
msgState ¬ OK;
state ¬ $OK;
[msgExists, archived, deleted] ¬ MailRetrieve.NextMessage[mH.mrHandle];
IF deleted THEN { msgState ¬ wasDeleted; RETURN};
IF NOT msgExists THEN { msgState ¬ noMore; RETURN};
-- Now read all the items in the message, terminating on the LastItem, and skipping the ones that we're not yet interested in.
[timeStamp, sender, ] ¬ MailRetrieve.StartMessage[mH.mrHandle] ;
ok ¬ MsgToStream[strm, timeStamp, sender, opsH, mH];
IF ~ok THEN msgState ¬ retrieveFailed;
EXITS
thisFailed => msgState ¬ retrieveFailed;
};
CheckForStopping: ENTRY PROC[mH: MailHandle] RETURNS[stop: BOOL] = {
IF (stop ¬ mH.needsClosing) AND ~mH.hasBeenClosed THEN {
mH.hasBeenClosed ¬ TRUE;
TRUSTED { Process.Detach[ FORK MailRetrieve.Close[mH.mrHandle] ] };
};
};
Write a message on the newMailLog stream
MsgToStream: PROC[strm: STREAM, timeStamp: MailBasics.Timestamp, sender: MailBasics.RName, opsH: WalnutOpsHandle, mH: MailHandle]
RETURNS[ok: BOOL] = {
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
curPos: INT;
msg: ROPE = WalnutStream.IdOnFileWithSender[timeStamp, sender];
newMailCreateMsg.msg ¬ msg;
-- other fields are set later & entry is re-written
curPos ¬ WalnutStream.WriteEntry[strm, newMailCreateMsg, -1];
[ , , newMailCreateMsg.textLen, newMailCreateMsg.formatLen, ] ¬
MailMessage.ReadOneMessage[mH.mrHandle, timeStamp, sender.name, strm];
IF newMailCreateMsg.textLen = -1 THEN { strm.SetLength[curPos]; RETURN[FALSE]};
[] ¬ WalnutStream.WriteEntry[strm, newMailCreateMsg, curPos];
WalnutRoot.FlushMailStream[opsH];
RETURN[TRUE];
};
END.