WalnutNewMailImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
Willie-Sue, August 26, 1985 12:25:02 pm PDT
Walnut New Mail Operations Implementation
Last Edited by: Willie-Sue, January 10, 1985 4:22:49 pm PST
Last Edited by: Wert, August 31, 1984 9:49:21 pm PDT
Last Edited by: Donahue, January 25, 1985 2:34:47 pm PST
(Changed idle procedure, simplified structure somewhat)
DIRECTORY
FS USING [Error, ErrorDesc, ErrorFromStream],
GVBasics USING [ItemHeader, ItemType, RName, Timestamp],
GVRetrieve USING [Failed, Handle, MBXState, ServerState,
Accept, Close, Create, GetItem, MailboxState, NextItem, NextMessage, NextServer, NewUser, ServerName, StartMessage],
Idle USING [IdleHandler, IdleRegistration, RegisterIdleHandler],
IO,
Process USING [Detach, Pause, SecondsToTicks],
RefText USING [page],
Rope,
UserCredentials USING [Get],
WalnutNewMail USING [],
WalnutDefs USING [Error, RetrieveState, ServerResponse],
WalnutOps USING [ EndNewMail],
WalnutRoot USING [FlushAndContinue],
WalnutSendOps USING [TiogaCTRL],
WalnutStream USING [createMsg, AbortStream, ConstructMsgID, WriteEntry];
WalnutNewMailImpl: CEDAR MONITOR
IMPORTS
FS, GVRetrieve, Idle, IO, Process, Rope, UserCredentials,
WalnutDefs, WalnutOps, WalnutRoot, WalnutStream
EXPORTS
WalnutNewMail
= BEGIN
Types
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
TiogaCTRL: GVBasics.ItemType = WalnutSendOps.TiogaCTRL;
GVHandle: TYPE = REF GVHandleObject;
GVHandleObject: TYPE =
RECORD[needsClosing, hasBeenClosed: BOOLFALSE, handle: GVRetrieve.Handle ← NIL];
Variables
copyBuffer: REF TEXTNEW[TEXT[RefText.page]];
procForReporting, procForProgress: PROC[ROPE];
notifyProc: PROC[ok: BOOL, someMail: BOOL]; -- if not NIL, then notify after mail is retrieved
registeredUser: GVBasics.RName ← NIL;
idled: BOOLFALSE; -- this is true if the machine has been put in the idle state
mailInProgress: BOOLFALSE; -- if this is true, then there is a process running attempting to fetch new mail; we don't start another one until the current one finished
tryForNewMail: BOOLFALSE;
currentGVH: GVHandle ← NIL;
msgPollingInterval: INT ← 300;  -- seconds between pollings
lastStateReported: GVRetrieve.MBXState ← unknown;
lastStatus: ROPENIL;
getMailLogProc: PROC RETURNS[STREAM] ← NIL;
recordMailInfoProc: PROC[logLen: INT, server: ROPE, num: INT] RETURNS[BOOL] ← NIL;
idleReg: Idle.IdleRegistration;
Procedures
Watch the mailbox & retrieve mail when available
EnableMailRetrieval: PUBLIC ENTRY PROC[
reportProc, progressProc: PROC[ROPE],
getMailLog: PROC RETURNS[STREAM],
recordMailInfo: PROC[logLen: INT, server: ROPE, num: INT] RETURNS[BOOL],
notifyWhenMailRetrieved: PROC[ok: BOOL, someMail: BOOL] ] = {
ENABLE UNWIND => NULL;
sameUser: BOOL = Rope.Equal[registeredUser, UserCredentials.Get[].name];
registeredUser ← UserCredentials.Get[].name;
procForReporting ← reportProc;
procForProgress ← progressProc;
tryForNewMail ← FALSE;
mailInProgress ← FALSE;
lastStatus ← "Checking for new mail ...";
getMailLogProc ← getMailLog;
recordMailInfoProc ← recordMailInfo;
notifyProc ← notifyWhenMailRetrieved;
lastStateReported ← unknown;
IF idleReg = NIL THEN idleReg ← Idle.RegisterIdleHandler[NewMailIdleProc];
make sure that only one idle proc is registered here!
IF sameUser THEN RETURN;
IF currentGVH # NIL THEN
IF mailInProgress THEN currentGVH.needsClosing ← TRUE
ELSE {
currentGVH.hasBeenClosed ← TRUE;
TRUSTED { Process.Detach[ FORK GVRetrieve.Close[currentGVH.handle] ] };
};
currentGVH ← NEW[GVHandleObject ←
[ handle: GVRetrieve.Create[ msgPollingInterval, WatchMailBox ]] ];
TRUSTED{ Process.Detach[
FORK GVRetrieve.NewUser[currentGVH.handle, registeredUser, UserCredentials.Get[].password] ] }
};
DisableMailRetrieval: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
registeredUser ← NIL;
procForReporting ← procForProgress ← NIL;
getMailLogProc ← NIL;
recordMailInfoProc ← NIL;
lastStateReported ← unknown;
IF currentGVH # NIL THEN
IF mailInProgress THEN currentGVH.needsClosing ← TRUE
ELSE {
currentGVH.hasBeenClosed ← TRUE;
TRUSTED { Process.Detach[ FORK GVRetrieve.Close[currentGVH.handle] ] }
};
currentGVH ← NIL
};
GetLastMailBoxStatus: PUBLIC ENTRY PROC
RETURNS[mbxState: GVRetrieve.MBXState, status: ROPE] =
{ ENABLE UNWIND => NULL; RETURN[lastStateReported, lastStatus] };
CheckGrapevine: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
IF mailInProgress THEN RETURN;
tryForNewMail ← FALSE;
mailInProgress ← TRUE;
TRUSTED { Process.Detach[FORK AutoNewMailProc[currentGVH]] }
};
CheckReport: PROC[r: ROPE] =
{ IF procForReporting # NIL THEN procForReporting[r] };
ProgressReport: PROC[r: ROPE] =
{ IF procForProgress # NIL THEN procForProgress[r] };
NewMailIdleProc: ENTRY Idle.IdleHandler = {
ENABLE UNWIND => NULL;
idled ← reason = becomingIdle;
IF tryForNewMail AND NOT idled THEN TRUSTED {
tryForNewMail ← FALSE;
mailInProgress ← TRUE;
Process.Detach[FORK AutoNewMailProc[currentGVH]] }
};
WatchMailBox: ENTRY PROC[newState: GVRetrieve.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;
IF newState = unknown THEN RETURN;
IF (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!";
};
lastStateReported ← newState;
lastStatus ← status;
tryForNewMail ← NOT mailInProgress AND (tryForNewMail OR newState = notEmpty);
IF tryForNewMail AND NOT idled THEN TRUSTED {
tryForNewMail ← FALSE;
mailInProgress ← TRUE;
Process.Detach[FORK AutoNewMailProc[currentGVH]] };
};
MailFetchDone: ENTRY PROC[gvH: GVHandle] = {
IF gvH.needsClosing AND ~gvH.hasBeenClosed THEN {
gvH.hasBeenClosed ← TRUE;
TRUSTED { Process.Detach[ FORK GVRetrieve.Close[gvH.handle] ]};
};
mailInProgress ← FALSE
};
AutoNewMailProc: PROC[gvH: GVHandle] = {
thisUser: ROPE = UserCredentials.Get[].name;
ok, someMail: BOOL;
IF ~Rope.Equal[thisUser, registeredUser, FALSE] THEN {
CheckReport[IO.PutFR["******%g may not retrieve %g's new mail", IO.rope[thisUser], IO.rope[registeredUser]]];
RETURN;
};
DO
[ok, someMail] ← DoNewMail[gvH];
IF ok THEN EXIT;
IF (notifyProc # NIL) THEN notifyProc[ok, someMail];
Process.Pause[Process.SecondsToTicks[60]];
ENDLOOP;
MailFetchDone[gvH];
IF (notifyProc # NIL) THEN notifyProc[ok, someMail];
};
DoNewMail: PROC[gvH: GVHandle] RETURNS[ok: BOOL, someMail: BOOL] = {
mailStream: STREAM;
serverKnown: BOOLFALSE;
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 opsError;
};
FS.Error => { errMsg ← error.explanation.Concat[err]; GOTO streamError};
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 streamError
};
END;
IF CheckForStopping[gvH] THEN RETURN[TRUE, FALSE];
IF getMailLogProc = NIL THEN RETURN[FALSE, FALSE]; -- had been shut down
IF (mailStream ← getMailLogProc[]) = NIL THEN {
ProgressReport[IO.PutFR["\nCouldn't get newMailLog @ %g", IO.time[]]];
RETURN[FALSE, FALSE]
};
IF CheckForStopping[gvH] THEN RETURN[TRUE, FALSE];
ProgressReport[IO.PutFR["\n ~~~ Retrieving mail @ %g\n", IO.time[]]];
mailStream.SetIndex[mailStream.GetLength[]];
SELECT gvH.handle.MailboxState[] FROM
badName, badPwd => GOTO credentialsError;
cantAuth => GOTO noServers;
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: GVRetrieve.ServerState; -- The state of the server.
serverName: ROPE;
BEGIN
ENABLE UNWIND => {
DO
[noMore, serverState] ← gvH.handle.NextServer[]; -- cycle thru servers
IF ~noMore THEN LOOP;
ENDLOOP;
};
-- Step through the servers.
IF CheckForStopping[gvH] THEN RETURN[TRUE, FALSE];
[noMore, serverState] ← gvH.handle.NextServer[];
IF noMore THEN EXIT;   -- Last server? Then done.
serverKnown ← TRUE;
serverName ← gvH.handle.ServerName[];
ProgressReport[serverName]; ProgressReport[ ": "];
SELECT serverState FROM
unknown => state ← $didNotRespond;
empty => state ← $OK;
notEmpty => {
curPos: INT ← mailStream.GetLength[];
[num, state] ← DrainServer[mailStream, gvH];
IF num = -1 THEN {
mailStream.SetLength[curPos];
WalnutRoot.FlushAndContinue[mailStream]
};
};
ENDCASE;
IF serverState = notEmpty AND state = $OK AND okToFlush THEN {
sr.state ← state;
sr.num ← num;
IF num # -1 THEN {
IF ~recordMailInfoProc[mailStream.GetLength[], serverName, num]
THEN { ReleaseNewMail[mailStream]; ok ← FALSE; RETURN };
now we can flush the server
gvH.handle.Accept[ ! GVRetrieve.Failed => {state ← $FlushFailed; CONTINUE}];
someMail ← TRUE;
};
};
IF procForProgress # NIL THEN
SELECT serverState FROM
unknown => ProgressReport[" did not respond\n"];
empty => ProgressReport[" empty\n"];
notEmpty => ProgressReport[IO.PutFR[" %g messages\n", IO.int[num]]];
ENDCASE;
END;
ENDLOOP; -- End of servers loop, exit.
ok ← TRUE;
WalnutOps.EndNewMail[];  -- release the newMailLog
IF NOT serverKnown THEN CheckReport["\nNoMailboxes"];
EXITS
opsError, streamError => {
CheckReport[errMsg];
IF procForProgress # NIL THEN {
ProgressReport["\n"];
ProgressReport[errMsg];
};
ReleaseNewMail[mailStream]; -- release the newMailLog
};
credentialsError => CheckReport["\nCredentialsError"];
noServers => CheckReport["\nNoServers"];
END;
};
ReleaseNewMail: PROC[strm: STREAM] = {
WalnutStream.AbortStream[strm ! IO.Error, FS.Error => CONTINUE]; -- ignore errors
WalnutOps.EndNewMail[ ! WalnutDefs.Error => CONTINUE];
};
okToFlush: BOOLTRUE;  -- for debugging
DrainServer: PROC[strm: STREAM, gvH: GVHandle]
RETURNS[num: INT, state: WalnutDefs.RetrieveState] = {
Reads mail from the next grapevine server via gvH.
ENABLE UNWIND => NULL;
msgState: MsgState;
num ← 0;
DO
[msgState, state] ← ReadMessageItems[strm, gvH];
SELECT msgState FROM
noMore => EXIT;
OK, wasDeleted => NULL;
retrieveFailed => {num ← -1; EXIT};
ENDCASE => ERROR;
IF NOT (msgState = wasDeleted) THEN {
num ← num + 1;
ProgressReport["."];  -- report progress
};
ENDLOOP;
};
MsgState: TYPE = {OK, retrieveFailed, noMore, wasDeleted};
ReadMessageItems: PROC[strm: STREAM, gvH: GVHandle]
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 GVRetrieve.Failed => {
state ← SELECT why FROM
communicationFailure => $communicationFailure,
noSuchServer => $noSuchServer,
connectionRejected => $connectionRejected,
badCredentials => $badCredentials,
unknownFailure =>  $unknownFailure,
ENDCASE =>   $unknownError;
GOTO gvFailed;
};
msgExists, archived, deleted, ok: BOOLEAN;
timeStamp: GVBasics.Timestamp;
gvSender: GVBasics.RName;
IF CheckForStopping[gvH] THEN {
ProgressReport[" stopping retrieval\n"];
RETURN[retrieveFailed, $OK];
};
msgState ← OK;
state ← $OK;
[msgExists, archived, deleted] ← GVRetrieve.NextMessage[gvH.handle];
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, gvSender, ] ← GVRetrieve.StartMessage[gvH.handle] ;
ok ← GVMsgToStream[strm, WalnutStream.ConstructMsgID[timeStamp, gvSender], gvH];
IF ~ok THEN msgState ← retrieveFailed;
EXITS
gvFailed => msgStateretrieveFailed;
};
CheckForStopping: ENTRY PROC[gvH: GVHandle] RETURNS[stop: BOOL] = {
IF (stop ← gvH.needsClosing) AND ~gvH.hasBeenClosed THEN {
gvH.hasBeenClosed ← TRUE;
TRUSTED { Process.Detach[ FORK GVRetrieve.Close[gvH.handle] ] };
};
};
Write a message on the newMailLog stream
GVMsgToStream: PROC[strm: STREAM, msg: ROPE, gvH: GVHandle]
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;
WalnutStream.createMsg.msg ← msg.ToRefText[];
-- other fields are set later & entry is re-written
curPos ← WalnutStream.WriteEntry[strm, WalnutStream.createMsg, -1];
[WalnutStream.createMsg.textLen, WalnutStream.createMsg.formatLen] ←
ReadGVMsg[strm, gvH.handle];
IF WalnutStream.createMsg.textLen = -1 THEN { strm.SetLength[curPos]; RETURN[FALSE]};
[] ← WalnutStream.WriteEntry[strm, WalnutStream.createMsg, curPos];
WalnutRoot.FlushAndContinue[strm];
RETURN[TRUE];
};
ReadGVMsg: PROC [strm: STREAM, handle: GVRetrieve.Handle]
RETURNS [textLen, formatLen: INT] = {
ENABLE GVRetrieve.Failed => GOTO gvFailed;
textLen ← 0;
formatLen ← 0;
DO
item: GVBasics.ItemHeader ← GVRetrieve.NextItem[handle];
SELECT item.type FROM
PostMark, Sender, ReturnTo => NULL;  -- ERROR;
Audio => NULL;  -- we may use this someday
Recipients, Capability, updateItem, reMail => NULL;
Text =>
{ textLen ← textLen + LOOPHOLE[item.length, INT];
StrmToStrmCopy[to: strm, from: GVRetrieve.GetItem[handle]];
};
TiogaCTRL =>
{ formatLen ← formatLen + LOOPHOLE[item.length, INT];
StrmToStrmCopy[to: strm, from: GVRetrieve.GetItem[handle]];
};
LastItem => EXIT;
ENDCASE => LOOP;
ENDLOOP;
strm.PutChar['\n];
RETURN;
EXITS
gvFailed => RETURN[-1, -1];
};
StrmToStrmCopy: PROC[to, from: STREAM] = {
DO
IF from.GetBlock[copyBuffer, 0, 512] = 0 THEN EXIT;
to.PutBlock[copyBuffer];
ENDLOOP
};
END.