<> <> <> <<>> <> <<>> <> <> <> <<(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 <<>> <<>> <> ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TiogaCTRL: GVBasics.ItemType = WalnutSendOps.TiogaCTRL; GVHandle: TYPE = REF GVHandleObject; GVHandleObject: TYPE = RECORD[needsClosing, hasBeenClosed: BOOL _ FALSE, handle: GVRetrieve.Handle _ NIL]; <<>> <> <<>> 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; <> <> 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]; <> 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] = { <> <> 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. <<>> <> 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 }; <> 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] = { <> 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] = { <> 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.