<> <> <> <> <> <> <> <> <> DIRECTORY BasicTime USING [FromPupTime, GMT, Now, Unpack, Unpacked], CedarProcess USING [SetPriority], Convert USING [RopeFromUnpackedTime], FS USING [Error, ExpandName, GetName, nullOpenFile, Open, OpenFile], GVBasics USING [ItemHeader, ItemType, RName, Timestamp], GVRetrieve USING [Accept, Close, Create, Failed, GetItem, Handle, MailboxState, MBXState, NewUser, NextItem, NextMessage, NextServer, ServerName, ServerState, StartMessage], IO USING [GetBlock, int, PutFR, rope, STREAM, time], MessageWindow USING [Append, Blink], PeanutProfile USING [activeMailFile, automaticNewMail, workingDirectory], PeanutRetrieve USING [], PeanutSendMail USING [simpleUserName, userRName], PeanutWindow USING [dirtyMessageSetIcon, messageSetIcon, OutputRope, SetNewMail], Process USING [Detach], PutGet USING [FromFileC, FromRope], Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, FromRefText, Index, Length, ROPE, Size, Substr], TEditDisplay USING [EstablishLine], TEditDocument USING [TEditDocumentData], TextEdit USING [DocFromNode, FromRope], TextNode USING [Body, FirstChild, LastSibling], TiogaFileOps USING [InsertNode, SetContents, SetFormat], TiogaMenuOps USING [DefaultMenus, FirstLevelOnly], TiogaOps USING [CancelSelection, GetSelection, LastChild, Location, Lock, LockSel, Next, Parent, PutProp, SelectBranches, SelectionGrain, SelectPoint, SetSelection, ToSecondary, Unlock, UnlockSel, ViewerDoc], TiogaOpsDefs USING [], UserCredentials USING [Get], ViewerClasses USING [Lock, Viewer], ViewerEvents USING [RegisterEventProc, ViewerEvent], ViewerOps USING [AddProp, EnumerateViewers, EnumProc, FetchProp, OpenIcon, PaintViewer, SaveViewer, SetNewVersion], ViewerTools USING [MakeNewTextViewer]; PeanutRetrieveImpl: CEDAR MONITOR IMPORTS BasicTime, CedarProcess, Convert, FS, GVRetrieve, IO, MessageWindow, PeanutProfile, PeanutSendMail, PeanutWindow, Process, PutGet, Rope, TEditDisplay, TextEdit, TextNode, TiogaFileOps, TiogaMenuOps, TiogaOps, UserCredentials, ViewerEvents, ViewerOps, ViewerTools EXPORTS PeanutRetrieve, TiogaFileOps, TiogaOpsDefs = BEGIN <<************************************************************************>> ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; RName: TYPE = GVBasics.RName; RefTextNode: TYPE = REF NodeBody; NodeBody: PUBLIC TYPE = TextNode.Body; TiogaCTRL: GVBasics.ItemType = Tioga1; -- an item containing tioga formatting ReportRope: PROCEDURE [r: ROPE] = { PeanutWindow.OutputRope[r] }; GetNewMsgs: PUBLIC ENTRY PROC [open: BOOL _ TRUE] = { ENABLE UNWIND => NULL; [] _ InternalGetNewMsgs[open]; }; peanutProp: ATOM = $PeanutMailFileName; SetMailFileName: PROC[v: Viewer, name: ROPE] = { ViewerOps.AddProp[viewer: v, prop: peanutProp, val: name]; ViewerOps.AddProp[viewer: v, prop: $IconLabel, val: v.label]; }; GetMailFileName: PROC[v: Viewer] RETURNS[ROPE] = { WITH ViewerOps.FetchProp[viewer: v, prop: peanutProp] SELECT FROM rope: ROPE => RETURN[rope]; ENDCASE => RETURN[NIL]; }; FindMailViewer: PUBLIC PROC[name: ROPE] RETURNS[viewer: Viewer _ NIL] = { <> Test: ViewerOps.EnumProc -- PROC[v: Viewer] RETURNS[continue: BOOL _ TRUE] -- = { rope: ROPE = GetMailFileName[v]; IF rope#NIL AND Rope.Equal[rope, name, FALSE] THEN { viewer _ v; RETURN[FALSE] }; }; ViewerOps.EnumerateViewers[Test]; }; GetMailViewer: PUBLIC ENTRY PROC[name: ROPE] RETURNS [mailViewer: Viewer] = { <> ENABLE UNWIND => NULL; mailViewer _ InternalGetMailViewer[name]; }; MakeMailDoc: PROC[fileName: ROPE] RETURNS [mailDoc: RefTextNode] = { styleProp: ROPE = "(mail) style"; mailDoc _ TextEdit.DocFromNode[TextEdit.FromRope[fileName]]; TiogaOps.PutProp[mailDoc, $Prefix, styleProp]; }; InternalGetMailViewer: INTERNAL PROC[name: ROPE] RETURNS[mailViewer: Viewer] = { mailViewer _ FindMailViewer[name]; IF mailViewer=NIL THEN { wDir: ROPE _ PeanutProfile.workingDirectory; shortName: ROPE _ name.Concat[".mail"]; longName: ROPE _ FS.ExpandName[name: shortName, wDir: wDir].fullFName; fileName: ROPE _ longName; mailDoc: RefTextNode _ NIL; file: FS.OpenFile _ FS.nullOpenFile; file _ FS.Open[longName ! FS.Error => IF error.code=$unknownFile THEN CONTINUE]; IF file=FS.nullOpenFile THEN { styleProp: ROPE = "(mail) style"; mailDoc _ TextEdit.DocFromNode[TextEdit.FromRope[shortName]]; TiogaOps.PutProp[mailDoc, $Prefix, styleProp]; } ELSE { mailDoc _ PutGet.FromFileC[file]; fileName _ FS.GetName[file].fullFName; }; mailViewer _ ViewerTools.MakeNewTextViewer[info: [ name: longName, file: fileName, label: name, data: mailDoc, icon: PeanutWindow.messageSetIcon, iconic: TRUE], paint: FALSE]; SetMailFileName[mailViewer, name]; ViewerOps.PaintViewer[mailViewer, all]; [] _ ViewerEvents.RegisterEventProc[proc: MessageSetHasBeenEdited, event: edit, filter: mailViewer, before: TRUE]; [] _ ViewerEvents.RegisterEventProc[proc: MessageSetHasBeenSaved, event: save, filter: mailViewer, before: FALSE]; TiogaMenuOps.DefaultMenus[mailViewer]; TiogaMenuOps.FirstLevelOnly[mailViewer]; }; }; InternalGetNewMsgs: INTERNAL PROC [open: BOOL _ TRUE] RETURNS [numRetrieved: INT] = <> { allOK: BOOL_ TRUE; mailViewer: Viewer; firstHeader: RefTextNode; StashNewMessages: PROC [retrieveOK: BOOL] RETURNS[doRemoteFlush: BOOL] = { allOK _ allOK AND retrieveOK; ViewerOps.SaveViewer[mailViewer]; -- doesn't return until the save is complete <> RETURN[retrieveOK AND flushRemoteMail AND mailViewer#NIL AND NOT mailViewer.destroyed]; }; mailViewer _ 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]; }; <<***********************************************************************>> MessageSetHasBeenEdited: PROC[viewer: Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] = { IF before THEN { viewer.icon _ PeanutWindow.dirtyMessageSetIcon; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; }; }; MessageSetHasBeenSaved: PROC [viewer: Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] = { IF NOT before THEN { viewer.icon _ PeanutWindow.messageSetIcon; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; }; }; CopyMessages: PUBLIC PROC [to: ROPE, delete: BOOL] = { sourceViewer, destViewer: Viewer; destDoc, sourceDoc, destLast, sourceLast, afterSource: RefTextNode; start, end: TiogaOps.Location; level: TiogaOps.SelectionGrain; caretBefore, pendingDelete: BOOL; lockedPrimary, lockedSecondary, lockedDest, lockedSource: BOOL _ FALSE; TopParent: PROC [node, root: RefTextNode] RETURNS [parent: RefTextNode] = { DO parent _ TiogaOps.Parent[node]; IF parent=root THEN RETURN [node]; node _ parent; ENDLOOP }; Cleanup: PROC = { IF lockedPrimary THEN TiogaOps.UnlockSel[primary]; IF lockedSecondary THEN TiogaOps.UnlockSel[secondary]; IF lockedDest THEN TiogaOps.Unlock[destDoc]; IF lockedSource THEN TiogaOps.Unlock[sourceDoc]; }; destViewer _ GetMailViewer[to]; TiogaOps.LockSel[primary]; lockedPrimary _ TRUE; [sourceViewer, start, end, level, caretBefore, pendingDelete] _ TiogaOps.GetSelection[]; IF sourceViewer=NIL OR sourceViewer.class.flavor#$Text THEN { TiogaOps.UnlockSel[primary]; ReportRope["\nSelect message(s)."]; RETURN }; TiogaOps.LockSel[secondary]; lockedSecondary _ TRUE; destDoc _ TiogaOps.ViewerDoc[destViewer]; TiogaOps.Lock[destDoc]; lockedDest _ TRUE; sourceDoc _ TiogaOps.ViewerDoc[sourceViewer]; IF sourceDoc#destDoc THEN { TiogaOps.Lock[sourceDoc]; lockedSource _ TRUE }; destLast _ TiogaOps.LastChild[destDoc]; sourceLast _ TopParent[end.node, sourceDoc]; afterSource _ TiogaOps.Next[sourceLast]; TiogaOps.SelectBranches[ -- source viewer: sourceViewer, level: branch, caretBefore: FALSE, pendingDelete: delete, which: primary, start: TopParent[start.node, sourceDoc], end: sourceLast]; TiogaOps.SelectBranches[ -- destination viewer: destViewer, start: destLast, end: destLast, level: branch, caretBefore: FALSE, pendingDelete: FALSE, which: secondary]; TiogaOps.ToSecondary[]; IF NOT delete THEN -- restore original selection TiogaOps.SetSelection[sourceViewer, start, end, level, caretBefore, pendingDelete, primary] ELSE IF afterSource # NIL THEN TiogaOps.SelectPoint[sourceViewer, [afterSource,0], primary] ELSE TiogaOps.CancelSelection[primary]; Cleanup[]; ReportRope[IF delete THEN "\nMoved to " ELSE "\nCopied to "]; ReportRope[to]; }; <<>> <<***********************************************************************>> msgPollingInterval: INT_ 300; -- Number of seconds between mailbox polling. flushRemoteMail: BOOLEAN_ TRUE; gvRetrieveHandle: GVRetrieve.Handle_ NIL; -- cookie for receiving messages. OpenConnection: PUBLIC PROC[user: RName] = { <> CloseConnection[]; NewUser[user] ; }; CloseConnection: PUBLIC PROC[] = { <> IF gvRetrieveHandle # NIL THEN{ GVRetrieve.Close[gvRetrieveHandle]; gvRetrieveHandle_ NIL}; }; NewUser: PUBLIC PROC[user: RName] = { <> IF gvRetrieveHandle = NIL THEN gvRetrieveHandle _ GVRetrieve.Create[ msgPollingInterval, WatchMailBox ]; GVRetrieve.NewUser[gvRetrieveHandle, user, UserCredentials.Get[].password ] ; } ; lastStateReported: GVRetrieve.MBXState _ unknown; WatchMailBox: PROC[newState: GVRetrieve.MBXState] = { <> status: ROPE _ NIL; IF newState = unknown THEN RETURN; PeanutWindow.SetNewMail[newState=notEmpty]; 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, someEmpty, allEmpty, notEmpty => NULL; ENDCASE => status _ "Bad MBXState!"; IF status#NIL THEN { PeanutWindow.OutputRope["\n"]; PeanutWindow.OutputRope[status]; }; IF newState=notEmpty AND lastStateReported#notEmpty AND PeanutProfile.automaticNewMail THEN { mailViewer: Viewer = FindMailViewer[PeanutProfile.activeMailFile]; IF mailViewer=NIL OR mailViewer.iconic THEN TRUSTED{Process.Detach[FORK ReadMail[]]}; }; }; <> <> <> <> <> <<};>> <> <> <> <> <<{ status_ NIL; PeanutWindow.SetNewMail[FALSE] }>> <> < {status_ "\nYour user name is invalid, please log in"; showTime_ FALSE};>> < {status_ "\nYour password is invalid"; showTime_ FALSE};>> < {status_ "\nCan't check your credentials at this time"; showTime_ FALSE};>> < {status_ "\nYour credentials are OK"; showTime_ FALSE};>> < status_ "\nAll of the mail servers are down";>> < status_ "\nAll of the mail servers checked are empty";>> < {status_ NIL; PeanutWindow.SetNewMail[FALSE]};>> < {status_ NIL; PeanutWindow.SetNewMail[TRUE];>> <> <> <<};>> < status_ "\nBad State!";>> <> <> <> <> <<};>> <<};>> <<>> ReadMail: ENTRY PROC = { ENABLE UNWIND => NULL; numRetrieved: INT; CedarProcess.SetPriority[background]; IF (numRetrieved _ InternalGetNewMsgs[FALSE])=0 THEN RETURN; MessageWindow.Append[IO.PutFR["New mail retrieved: %g", IO.time[]], TRUE]; MessageWindow.Blink[] }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *>> 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]; IF gvRetrieveHandle = NIL THEN { -- Open the connection if it's closed. gvRetrieveHandle _ GVRetrieve.Create[ msgPollingInterval, WatchMailBox ] ; GVRetrieve.NewUser[gvRetrieveHandle, PeanutSendMail.userRName, UserCredentials.Get[].password]; } ; SELECT gvRetrieveHandle.MailboxState[] FROM badName, badPwd => GOTO credentialsError; cantAuth => GOTO noServers; ENDCASE; --ok to try numRetrieved_ 0; ReportRope[IO.PutFR["\nCheck for new mail for %g: %g", IO.rope[PeanutSendMail.userRName], IO.time[]]]; DO -- Loops over servers. m, formatting: ROPE; messageState: MessageState; timeStamp: GVBasics.Timestamp; gvSender: 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: GVRetrieve.ServerState; -- The state of the server. headerNode: RefTextNode; serverName: ROPE ; headerNode _ TiogaOps.LastChild[mailDoc]; <> [noMore, serverState] _ gvRetrieveHandle.NextServer[]; IF noMore THEN EXIT; -- Last server? Then done. serverKnown _ TRUE; serverName _ gvRetrieveHandle.ServerName[]; ReportRope["\n"]; ReportRope[serverName]; ReportRope[": "]; 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, gvSender] _ 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] = '\n DO len _ len-1; ENDLOOP; RETURN [Rope.Substr[m,0,len]] }; GetField: PROC[message, fieldName: ROPE] RETURNS[contents: ROPE] = { start: INT _ 0; DO cr: INT ~ Rope.Index[s1: message, pos1: start, s2: "\n"]; IF cr=start THEN EXIT; IF Rope.Find[s1: message, s2: fieldName, pos1: start, case: FALSE]=start THEN { i: INT _ start+Rope.Length[fieldName]; IF Rope.Fetch[message, i]=': THEN { i _ i+1; IF Rope.Fetch[message, i]=' THEN i _ i+1; RETURN[Rope.Substr[message, i, cr-i]]; }; }; start _ cr+1; ENDLOOP; RETURN[NIL]; }; GetFieldContents: PROC [name: ROPE, maxLen: INT] RETURNS [ROPE] = { contents: ROPE ~ GetField[m, name]; len: INT ~ Rope.Length[contents]; IF len=0 THEN RETURN["?"] 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 = { headerDate, headerName, headerSubject, header: ROPE; messageNode: RefTextNode; headerNode _ TiogaFileOps.InsertNode[headerNode, FALSE]; IF firstHeader=NIL THEN firstHeader _ headerNode; TiogaFileOps.SetFormat[headerNode, "header"]; headerDate _ GetDateFromGMT[BasicTime.FromPupTime[timeStamp.time]]; IF Rope.Equal[gvSender, PeanutSendMail.userRName, FALSE] OR Rope.Equal[gvSender, PeanutSendMail.simpleUserName, FALSE] THEN headerName _ Rope.Concat["To: ", GetFieldContents["To", 15]] ELSE headerName _ GetFieldContents["From", 32]; headerSubject _ GetFieldContents["Subject", 45]; header _ Rope.Cat[ Rope.Concat["\t", headerDate], Rope.Concat["\t", headerName], Rope.Concat["\t", headerSubject] ]; TiogaFileOps.SetContents[headerNode, header]; IF formatting=NIL THEN { messageNode _ TiogaFileOps.InsertNode[headerNode, TRUE]; TiogaFileOps.SetContents[messageNode, DeleteTrailingCRs[m]] } ELSE { messageRoot, messageFirst, messageLast, headerN: RefTextNode; messageRoot _ PutGet.FromRope[Rope.Cat[Rope.FromChar['\n],m,formatting]]; <> messageFirst _ TextNode.FirstChild[messageRoot]; headerN _ headerNode; headerN.child _ messageFirst; messageLast _ TextNode.LastSibling[messageFirst]; messageLast.last _ TRUE; messageLast.next _ headerN; messageRoot.child _ NIL; messageRoot.props _ NIL; <> }; ViewerOps.SetNewVersion[mailViewer]; ReportRope["."]; messages _ messages + 1; }; TiogaOps.Lock[mailDoc]; AppendNextMessage[ ! UNWIND => TiogaOps.Unlock[mailDoc] ]; TiogaOps.Unlock[mailDoc]; }; ENDLOOP ; -- Finished reading messages from this server. <<>> <> IF FinishedWithServer[messageState#retrieveFailed] THEN gvRetrieveHandle.Accept[ ! GVRetrieve.Failed => {ReportRope["\nFlush of remote messages failed; you may get these messages again"]; CONTINUE}]; IF messageState#retrieveFailed THEN ReportRope[ IO.PutFR[": retrieved %g messages.", IO.int[messages] ]]; numRetrieved_ numRetrieved + messages; ENDLOOP ; -- End of servers loop, exit. IF NOT serverKnown THEN GOTO noMailboxes; EXITS -- The error reporter for this routine. noMailboxes => ReportRope[" No mail boxes"]; credentialsError => ReportRope[" Credentials error"]; noServers => ReportRope[" No servers responding"]; }; bogusItems: INT _ 0; ReadMessageRecord: PROC RETURNS [messageState: MessageState, m, formatting: ROPE, timeStamp: GVBasics.Timestamp, gvSender: RName] = { <> <> ENABLE GVRetrieve.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 gvFailed; }; msgExists, archived, deleted: BOOLEAN; item: GVBasics.ItemHeader; blockSize: NAT = 500; block: REF TEXT _ NIL; m _ NIL; [msgExists, archived, deleted] _ GVRetrieve.NextMessage[ gvRetrieveHandle ]; IF archived THEN messageState _ wasArchived ELSE messageState_ OK; IF deleted THEN { messageState _ wasDeleted; RETURN}; IF NOT msgExists THEN { messageState _ noMore; RETURN}; <> <> [timeStamp, gvSender, ] _ GVRetrieve.StartMessage[ gvRetrieveHandle ] ; DO ReadItem: PROC[h: GVRetrieve.Handle] RETURNS[rope: ROPE _ NIL] = { stream: IO.STREAM = GVRetrieve.GetItem[h]; IF block=NIL THEN block _ NEW[TEXT[blockSize]]; WHILE stream.GetBlock[block]>0 DO rope _ rope.Concat[Rope.FromRefText[block]]; ENDLOOP; }; item _ GVRetrieve.NextItem[gvRetrieveHandle]; SELECT item.type FROM PostMark, Sender, ReturnTo => bogusItems _ bogusItems+1; Recipients => NULL; Text => m _ ReadItem[gvRetrieveHandle]; TiogaCTRL => formatting _ ReadItem[gvRetrieveHandle]; Capability => NULL; Audio => NULL; updateItem => NULL; reMail => NULL; LastItem => EXIT; ENDCASE => LOOP; ENDLOOP; EXITS gvFailed => messageState_ retrieveFailed; } ; END.