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
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: 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] ] };
};
};
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];
EXITS
gvFailed => RETURN[-1, -1];
};