<> <> <> <> <> <> <> <> <> <> <> <> <> <> DIRECTORY Atom, BasicTime, CedarProcess, Commander, Convert, EditSpan, IO, MailBasics, MailBasicsFileTypes, MailMessage, MailRetrieve, MailSend, MailUtils, MessageWindow, PeanutCredentials, PeanutProfile, PeanutRetrieve, PeanutWindow, Process, Rope, TEditDisplay, TEditDocument, TextEdit, TextNode, Tioga, TiogaFileOps, TiogaIO, TiogaOps, TiogaOpsDefs, XNSCredentials, UserProfile, ViewerClasses, ViewerOps, XNSAuth; PeanutRetrieveImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, CedarProcess, Commander, Convert, EditSpan, IO, MailMessage, MailRetrieve, MailUtils, MessageWindow, PeanutProfile, PeanutWindow, Process, TiogaIO, Rope, TEditDisplay, TextEdit, TextNode, TiogaFileOps, TiogaOps, XNSCredentials, UserProfile, ViewerOps, XNSAuth EXPORTS PeanutCredentials, PeanutRetrieve, TiogaFileOps = BEGIN <<************************************************************************>> ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; RName: TYPE = MailBasics.RName; RefTextNode: TYPE = REF NodeBody; NodeBody: PUBLIC TYPE = Tioga.NodeRep; flushMsgs: BOOL ¬ TRUE; questionName: ROPE = "?"; ReportRope: PROCEDURE [r: ROPE] = { PeanutWindow.OutputRope[r] }; GetNewMsgs: PUBLIC ENTRY PROC [open: BOOL ¬ TRUE] = { ENABLE UNWIND => NULL; [] ¬ InternalGetNewMsgs[open]; IF flushMsgs THEN PeanutWindow.SetNewMail[FALSE]; <> }; InternalGetNewMsgs: INTERNAL PROC [open: BOOL ¬ TRUE] RETURNS [numRetrieved: INT] = <> { allOK: BOOL¬ TRUE; mailViewer: Viewer; firstHeader: RefTextNode; StashNewMessages: PROC [retrieveOK: BOOL] RETURNS[doRemoteFlush: BOOL] = { saveOK: BOOL ~ ViewerOps.SaveViewer[mailViewer]; <> allOK ¬ allOK AND retrieveOK; <> RETURN[saveOK AND retrieveOK AND flushRemoteMail AND mailViewer#NIL AND NOT mailViewer.destroyed]; }; mailViewer ¬ PeanutWindow.InternalGetMailViewer[PeanutProfile.activeMailFile]; [numRetrieved, firstHeader] ¬ AddNewMessages[StashNewMessages, mailViewer]; IF numRetrieved = 0 THEN { ReportRope["\nNo messages were retrieved"]; RETURN}; IF mailViewer.iconic AND PeanutProfile.automaticNewMail THEN NULL ELSE { WITH mailViewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { TEditDisplay.EstablishLine[tdd, [firstHeader,0]]; ViewerOps.PaintViewer[mailViewer, client]; }; ENDCASE => NULL; }; IF NOT allOK THEN ReportRope["\nSome messages may not have been retrieved"]; IF open AND mailViewer.iconic THEN ViewerOps.OpenIcon[mailViewer]; }; <<***********************************************************************>> msgPollingInterval: INT¬ 300; -- Number of seconds between mailbox polling. flushRemoteMail: BOOLEAN¬ TRUE; myRetrieveHandle: RetrieveHandle; -- cookie for receiving messages. RetrieveHandle: TYPE = REF RetrieveHandleRec; RetrieveHandleRec: TYPE = RECORD[ creds: LIST OF MailUtils.Credentials, mHandle: MailRetrieve.Handle ]; OpenConnection: PUBLIC PROC[user: LIST OF RName] = { <> CloseConnection[]; NewUser[user] ; }; CloseConnection: PUBLIC PROC[] = { <> IF myRetrieveHandle # NIL THEN{ MailRetrieve.Close[myRetrieveHandle.mHandle]; myRetrieveHandle¬ NIL}; }; NewUser: PUBLIC PROC[user: LIST OF RName] = { <> IF myRetrieveHandle = NIL THEN NewHandle[user]; } ; userRNameList: PUBLIC MailBasics.RNameList ¬ NIL; sendingCredentials: PUBLIC MailSend.SendingCredentialsList ¬ NIL; simpleUserName: PUBLIC LIST OF ROPE ¬ NIL; -- user name without registry NewHandle: PROC[user: LIST OF RName] = { doGV: BOOL ¬ UserProfile.Boolean["Peanut.gvMail", TRUE]; doXNS: BOOL ¬ UserProfile.Boolean["Peanut.xnsMail", TRUE]; creds: LIST OF MailUtils.Credentials; SELECT TRUE FROM ( doGV AND doXNS ) => creds ¬ MailUtils.GetUserCredentials[]; doGV => creds ¬ MailUtils.GetUserCredentials[$gv]; ENDCASE => creds ¬ MailUtils.GetUserCredentials[$xns]; myRetrieveHandle ¬ NEW[RetrieveHandleRec]; myRetrieveHandle.mHandle ¬ MailRetrieve.Create[ msgPollingInterval, WatchMailBox ]; myRetrieveHandle.creds ¬ creds; userRNameList ¬ NIL; simpleUserName ¬ NIL; sendingCredentials ¬ NIL; FOR cL: LIST OF MailUtils.Credentials _ creds, cL.rest UNTIL cL = NIL DO loggedInName: ROPE; newRName: MailBasics.RName; thisCred: MailUtils.Credentials _ cL.first; thisrName: MailBasics.RName _ thisCred.rName; MailRetrieve.NewUser[myRetrieveHandle.mHandle, thisrName, thisCred.password]; loggedInName _ MailUtils.GetLoggedInUser[thisrName.ns]; newRName _ [thisrName.ns, loggedInName]; userRNameList _ CONS[thisrName, userRNameList]; userRNameList _ CONS[newRName, userRNameList]; simpleUserName _ CONS[MailUtils.LocalNameFromRName[thisrName], simpleUserName]; simpleUserName _ CONS[MailUtils.LocalNameFromRName[newRName], simpleUserName]; sendingCredentials _ CONS[NEW[MailSend.SendingCredentialsRec _ [thisCred, TRUE, thisrName]], sendingCredentials]; sendingCredentials _ CONS[NEW[MailSend.SendingCredentialsRec _ [thisCred, TRUE, newRName]], sendingCredentials]; ENDLOOP; }; lastStateReported: MailRetrieve.MBXState ¬ unknown; WatchMailBox: PROC[mHandle: MailRetrieve.Handle, newState: MailRetrieve.MBXState] = { <> status: ROPE ~ SELECT newState FROM badName => "Your user name is invalid, please log in", badPwd => "Your password is invalid", cantAuth => "Can't check your credentials at this time", userOK => "Your credentials are OK", allDown, someEmpty, allEmpty, notEmpty => NIL, ENDCASE => "Bad MBXState!"; mailViewer: Viewer ~ PeanutWindow.FindMailViewer[PeanutProfile.activeMailFile]; IF newState # unknown THEN { <> IF newState = notEmpty OR NOT (PeanutProfile.automaticNewMail AND (mailViewer = NIL OR mailViewer.iconic)) THEN PeanutWindow.SetNewMail[newState=notEmpty]; <> IF status # NIL THEN { PeanutWindow.OutputRope["\n"]; PeanutWindow.OutputRope[status]; }; <> IF newState = notEmpty AND lastStateReported # notEmpty AND PeanutProfile.automaticNewMail THEN { mailViewer: Viewer ~ PeanutWindow.FindMailViewer[PeanutProfile.activeMailFile]; IF mailViewer = NIL OR mailViewer.iconic THEN Process.Detach[FORK ReadMail[]]; }; }; }; ReadMail: ENTRY PROC = { ENABLE UNWIND => NULL; numRetrieved: INT; CedarProcess.SetPriority[background]; IF (numRetrieved ¬ InternalGetNewMsgs[FALSE])=0 THEN RETURN; MessageWindow.Append[IO.PutFR1["New mail retrieved: %g", IO.time[]], TRUE]; }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> MessageState: TYPE = { noMore, wasArchived, wasDeleted, OK, retrieveFailed } ; AddNewMessages: PUBLIC PROC[ FinishedWithServer: PROC[BOOL] RETURNS[BOOL], mailViewer: Viewer] RETURNS[numRetrieved: INT, firstHeader: RefTextNode] = { <> <> serverKnown: BOOLEAN ¬ FALSE; mailDoc: RefTextNode ¬ TiogaOps.ViewerDoc[mailViewer]; xnsName: XNSAuth.Name ~ XNSAuth.GetIdentityDetails[XNSCredentials.GetIdentity[]].name; IF myRetrieveHandle = NIL THEN NewHandle[NIL]; -- Open the connection if it's closed. IF myRetrieveHandle.creds = NIL THEN { ReportRope["\n You need to do a login - quit out of Peanut, do xnslogin and restart\n"]; RETURN[0, NIL]; }; SELECT MailRetrieve.MailboxState[myRetrieveHandle.mHandle] FROM badName => ReportRope["\nSome mailbox reported badName - possibly no mailBox\n"]; badPwd => ReportRope["\nSome mailbox reported badPwd\n"]; cantAuth => ReportRope["\nSome server not found\n"]; ENDCASE; --ok to try numRetrieved ¬ 0; ReportRope[IO.PutFR["\nCheck for new mail for %g, %g", [rope[myRetrieveHandle.creds.first.rName.name]], [time[BasicTime.Now[]]]] ]; DO -- Loops over servers. m, formatting: ROPE; messageState: MessageState; timeStamp: MailBasics.Timestamp; sender: RName; messages: CARDINAL ¬ 0; -- the number of messages read from server. archivedReported: BOOLEAN ¬ FALSE; -- seen an archived message? <> <> noMore: BOOLEAN; -- TRUE if no more servers. serverState: MailRetrieve.ServerState; -- The state of the server. headerNode: RefTextNode; serverName: MailBasics.RName; headerNode ¬ TiogaOps.LastChild[mailDoc]; <> [noMore, serverState] ¬ MailRetrieve.NextServer[myRetrieveHandle.mHandle]; IF noMore THEN EXIT; -- Last server? Then done. serverKnown ¬ TRUE; serverName ¬ MailRetrieve.ServerName[myRetrieveHandle.mHandle]; ReportRope[IO.PutFR["\n(%g) %g: ", [atom[serverName.ns]], [rope[serverName.name]] ] ]; IF serverState # notEmpty THEN { IF serverState = empty THEN ReportRope["empty"] ELSE ReportRope["didn't respond"] ; LOOP; -- Skip to the next server. }; DO [messageState, m, formatting, timeStamp, sender] ¬ ReadMessageRecord[] ; SELECT messageState FROM noMore => EXIT ; wasArchived => IF NOT archivedReported THEN { archivedReported ¬ TRUE; ReportRope["(archived messages exist)"]}; OK => NULL; wasDeleted => NULL; retrieveFailed => EXIT; ENDCASE => ERROR; IF NOT (messageState = wasDeleted) THEN { DeleteTrailingCRs: PROC [m: ROPE] RETURNS [ROPE] = { len: INT ¬ Rope.Size[m]; WHILE len > 0 AND Rope.Fetch[m, len-1] = '\r DO len ¬ len-1; ENDLOOP; RETURN [Rope.Substr[m,0,len]] }; GetField: PROC[fieldName: ROPE] RETURNS[contents: ROPE] = { firstCH: CHAR ~ Rope.Fetch[m, 0]; start: INT ¬ IF ( firstCH = '\r ) OR ( firstCH = '\l ) THEN 1 ELSE 0; DO cr: INT ~ Rope.Index[s1: m, pos1: start, s2: "\r"]; IF cr=start THEN EXIT; IF Rope.Find[s1: m, s2: fieldName, pos1: start, case: FALSE]=start THEN { i: INT ¬ start+Rope.Length[fieldName]; IF Rope.Fetch[m, i]=': THEN { i ¬ i+1; IF Rope.Fetch[m, i]=' THEN i ¬ i+1; RETURN[Rope.Substr[m, i, cr-i]]; }; }; start ¬ cr+1; ENDLOOP; RETURN[NIL]; }; Truncate: PROC [contents: ROPE, maxLen: INT] RETURNS [ROPE] = { len: INT ~ Rope.Length[contents]; IF len=0 THEN RETURN[questionName] ELSE IF len<=maxLen THEN RETURN[contents] ELSE RETURN[Rope.Concat[Rope.Substr[base: contents, len: maxLen-3], "..."]] }; GetDateFromGMT: PROC[time: BasicTime.GMT] RETURNS[ROPE] = { <> unpacked: BasicTime.Unpacked = BasicTime.Unpack[time]; day: NAT = unpacked.day; month: ROPE = Convert.RopeFromUnpackedTime[unpacked, months, months]; year: NAT = unpacked.year; RETURN[IO.PutFR["%2g-%g-%02g", IO.int[day], IO.rope[month.Substr[len: 3]], IO.int[year MOD 100]]]; }; AppendNextMessage: PROC = { StripTail: PROC [tail: ROPE] RETURNS [found: BOOL] ~ { i: INT ~ headerName.Length[] - tail.Length[]; IF i > 0 AND headerName.Fetch[i - 1] = ': AND Rope.EqualSubstrs[s1: headerName, start1: i, s2: tail, case: FALSE] THEN { headerName ¬ headerName.Substr[len: i - 1]; RETURN [TRUE]; } ELSE RETURN [FALSE]; }; headerDate, headerName, headerSubject, header: ROPE; headerNode ¬ TiogaFileOps.InsertNode[headerNode, FALSE]; IF firstHeader=NIL THEN firstHeader ¬ headerNode; headerDate ¬ GetDateFromGMT[MailUtils.GetTimeFromPostmark[timeStamp]]; IF IsUser[sender.name] THEN { headerName ¬ Rope.Concat["To: ", GetField["To"]]; TiogaFileOps.SetFormat[headerNode, "sentHeader"]; } ELSE { headerName ¬ GetField["From"]; IF headerName.IsEmpty[] THEN headerName ¬ GetField["Sender"]; TiogaFileOps.SetFormat[headerNode, "receivedHeader"]; }; IF StripTail[xnsName.organization] THEN [] ¬ StripTail[xnsName.domain]; headerName ¬ Truncate[headerName, 25]; headerSubject ¬ GetField["Subject"]; header ¬ Rope.Cat[ Rope.Concat["\t", headerDate], Rope.Concat["\t", headerName], Rope.Concat["\t", headerSubject] ]; TiogaFileOps.SetContents[headerNode, header]; IF PeanutProfile.commentNewHeaders THEN TextEdit.PutComment[headerNode, TRUE]; IF formatting=NIL THEN { messageNode: RefTextNode ~ TiogaFileOps.InsertNode[headerNode, TRUE]; TiogaFileOps.SetContents[messageNode, DeleteTrailingCRs[m]]; TiogaFileOps.SetFormat[messageNode, Atom.GetPName[PeanutProfile.plainTextMessageFormat]]; } ELSE { messageRoot: RefTextNode ¬ NIL; messageRoot ¬ TiogaIO.FromPair[[m,formatting] ! UNCAUGHT => CONTINUE]; IF messageRoot=NIL THEN messageRoot ¬ TiogaIO.FromRope[Rope.Concat[m, "\r*** WARNING: Tioga formatting was corrupted - reverting to plaintext (PeanutRetrieveImpl.AppendNextMessage) ***"]]; [] ¬ EditSpan.Move[ destRoot: TextNode.Root[headerNode], sourceRoot: messageRoot, dest: TextNode.MakeNodeLoc[headerNode], source: TextNode.MakeNodeSpan[TextNode.FirstChild[messageRoot], TextNode.LastWithin[messageRoot]], where: after, nesting: 1]; -- Should this use an event?? }; ViewerOps.SetNewVersion[mailViewer]; IF ( messages ¬ messages + 1) MOD 10 = 0 THEN ReportRope[IO.PutFR1["%g ", [integer[messages]]] ] ELSE ReportRope["."]; }; TiogaOps.Lock[mailDoc]; AppendNextMessage[ ! UNWIND => TiogaOps.Unlock[mailDoc] ]; TiogaOps.Unlock[mailDoc]; }; ENDLOOP ; -- Finished reading messages from this server. <<>> <> IF FinishedWithServer[messageState#retrieveFailed] THEN { IF flushMsgs THEN MailRetrieve.Accept[myRetrieveHandle.mHandle ! MailRetrieve.Failed => { ReportRope["\nFlush of remote messages failed; you may get these messages again"]; CONTINUE }] ELSE ReportRope["\n Messages not flushed - flushMsgs is FALSE"]; }; IF messageState#retrieveFailed THEN ReportRope[ IO.PutFR1[": retrieved %g messages.", [integer[messages]] ]]; numRetrieved ¬ numRetrieved + messages; ENDLOOP ; -- End of servers loop, exit. IF NOT serverKnown THEN ReportRope[" No mail boxes"]; }; IsUser: PROC[this: ROPE] RETURNS[yes: BOOL] = { <> FOR rL: MailBasics.RNameList ¬ userRNameList, rL.rest UNTIL rL = NIL DO <> IF this.Equal[rL.first.name, FALSE] THEN RETURN[TRUE]; ENDLOOP; FOR rL: LIST OF ROPE ¬ simpleUserName, rL.rest UNTIL rL = NIL DO <> IF this.Equal[rL.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE] }; bogusItems: INT ¬ 0; ReadMessageRecord: PROC RETURNS[ messageState: MessageState, m, formatting: ROPE ¬ NIL, timeStamp: MailBasics.Timestamp, sender: RName] = { <> <> ENABLE MailRetrieve.Failed --[why: FailureReason, text: ROPE]-- => { ReportRope[SELECT why FROM $communicationFailure => "Communication failure", $noSuchServer => "No such server", $connectionRejected => "Connection rejected", $badCredentials => "Bad credentials", $unknownFailure => "Unknown failure", ENDCASE => "Undefined error"]; IF text.Size[]>0 THEN { ReportRope[" -- "]; ReportRope[text] }; GOTO thisFailed; }; msgExists, archived, deleted: BOOLEAN; sysMessage, header, formattedHeader, bodies: ROPE ¬ NIL; attachments, unknowns: ROPE ¬ NIL; [msgExists, archived, deleted] ¬ MailRetrieve.NextMessage[ myRetrieveHandle.mHandle ]; IF archived THEN messageState ¬ wasArchived ELSE messageState¬ OK; IF deleted THEN { messageState ¬ wasDeleted; RETURN}; IF NOT msgExists THEN { messageState ¬ noMore; RETURN}; [timeStamp, sender, ] ¬ MailRetrieve.StartMessage[myRetrieveHandle.mHandle]; [m, formatting, , , ] ¬ MailMessage.ReadOneMessageX[myRetrieveHandle.mHandle, timeStamp, sender.name]; EXITS thisFailed => messageState ¬ retrieveFailed; }; PeanutEnableFlushMsgs: Commander.CommandProc = { flushMsgs ¬ TRUE }; PeanutDisableFlushMsgs: Commander.CommandProc = { flushMsgs ¬ FALSE }; Commander.Register["PeanutEnableFlushMsgs", PeanutEnableFlushMsgs]; Commander.Register["PeanutFlushMsgs", PeanutEnableFlushMsgs]; Commander.Register["PeanutDisableFlushMsgs", PeanutDisableFlushMsgs]; Commander.Register["PeanutDontFlushMsgs", PeanutDisableFlushMsgs]; END.