Variables
copyBuffer: REF TEXT ← NEW[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: BOOL ← FALSE; -- this is true if the machine has been put in the idle state
mailInProgress: BOOL ← FALSE; -- 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: BOOL ← FALSE;
currentGVH: GVHandle ← NIL;
msgPollingInterval: INT ← 300; -- seconds between pollings
lastStateReported: GVRetrieve.MBXState ← unknown;
lastStatus: ROPE ← NIL;
getMailLogProc: PROC RETURNS[STREAM] ← NIL;
recordMailInfoProc: PROC[logLen: INT, server: ROPE, num: INT] RETURNS[BOOL] ← NIL;
idleReg: Idle.IdleRegistration;
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 PROCRETURNS[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: 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 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: BOOL ← TRUE; -- 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 => msgState ← retrieveFailed;
};
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] ] };
};
};