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. (WalnutNewMailImpl.mesa Copyright c 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) Types Variables Procedures Watch the mailbox & retrieve mail when available make sure that only one idle proc is registered here! 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 Cycle through the servers, until you find one that has mail. now we can flush the server Reads mail from the next grapevine server via gvH. Write a message on the newMailLog stream 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 ÊS˜šÏb™Jšœ Ïmœ1™—JšŸœ ˜J˜—JšŸœ˜—J˜Jš ŸœŸœŸœŸœŸœ˜2J˜Jš ŸœŸœŸœŸœŸœŸœ ˜HšŸœ#ŸœŸœ˜/JšœŸœ)Ÿœ ˜FJšŸœŸœŸœ˜J˜—J˜Jš ŸœŸœŸœŸœŸœ˜2J˜JšœŸœ(Ÿœ ˜EJšœ,˜,J˜šŸœŸ˜%JšœŸœ˜)Jšœ Ÿœ ˜JšŸœ  ˜—J˜šŸœ ˜Jšœ ˜ JšœŸœ +˜;—J™šœ<™Jšœ˜J˜ šŸœ Ÿœ˜šŸœ=˜?JšŸœ$ŸœŸœ˜8—Jšœ™JšœAŸœ˜LJšœ Ÿœ˜J˜—J˜—J˜šŸœŸœŸ˜šŸœ Ÿ˜Jšœ0˜0Jšœ$˜$JšœŸœŸœ ˜DJšŸœ˜——JšŸœ˜—JšŸœ ˜&J˜JšœŸœ˜ Jšœ ˜2JšŸœŸœ Ÿœ˜5J˜šŸ˜šœ˜J˜šŸœŸœŸœ˜Jšœ˜Jšœ˜J˜—Jšœ ˜6J˜—Jšœ6˜6Jšœ(˜(—JšŸœ˜J˜—J˜š¡œŸœŸœ˜&Jšœ ŸœŸœ Ÿœ ˜QJšœ,Ÿœ˜6J˜—J˜Jšœ ŸœŸœ ˜)J˜š¡ œŸœŸœ˜.JšœŸœŸœ&˜7Jšœ2™2JšŸœŸœŸœ˜Jšœ˜Jšœ˜šŸ˜Jšœ0˜0šŸœ Ÿ˜Jšœ Ÿœ˜JšŸœŸœ˜JšœŸœ˜#JšŸœŸœ˜—šŸœŸœŸœ˜%Jšœ˜Jšœ ˜(J˜——JšŸœ˜J˜—J˜Jšœ ŸœŸœ&˜:J˜Jš¡œŸœŸœŸœ:˜uš Eœ %˜ršŸœ˜šœŸœŸ˜Jšœ.˜.Jšœ&˜&Jšœ,˜,Jšœ(˜(Jšœ&˜&JšŸœ˜"—JšŸœ ˜Jšœ˜—Jšœ"Ÿœ˜*Jšœ˜Jšœ˜J˜šŸœŸœ˜Jšœ)˜)JšŸœ˜J˜—J˜Jšœ Ÿœ˜Jšœ ˜ J˜JšœD˜DJ˜JšŸœ ŸœŸœ˜1JšŸœŸœ ŸœŸœ˜3—J˜Jš ~˜~˜Jšœ?˜?JšœP˜PJšŸœŸœ˜&J˜šŸ˜JšœŸœ˜&—J˜—J˜š ¡œŸœŸœŸœŸœ˜CšŸœŸœŸœ˜:JšœŸœ˜JšŸœŸœ"˜@J˜—J˜——Kš(™(˜š ¡ œŸœŸœŸœŸœŸœ˜QJ™¨JšœŸœ˜ Jšœ-˜-Jšœ 3˜4JšœC˜CšœD˜DJšœ˜—J˜JšŸœ%ŸœŸœŸœ˜UJšœC˜CJ˜"JšŸœŸœ˜ J˜—J˜š ¡ œŸœŸœŸœŸœ˜_JšŸœ ŸœŸ œ ˜*J˜Jšœ ˜ Jšœ˜šŸ˜Jšœ8˜8šŸœ Ÿ˜JšœŸœ  ˜.Jšœ Ÿœ ˜*Jšœ.Ÿœ˜3šœ˜šœŸœŸœ˜1Jšœ;˜;—J˜—šœ ˜ šœŸœŸœ˜5Jšœ;˜;—J˜—Jšœ Ÿœ˜JšŸœŸœ˜—JšŸœ˜—J˜J˜šŸœ˜J˜—šŸ˜Jšœ Ÿœ ˜—J˜J™——˜š¡œŸœ Ÿœ˜*šŸ˜JšŸœ'ŸœŸœ˜3Jšœ˜JšŸ˜—J˜——J™JšŸœ˜——…—3NHÉ