<> <> <> <> DIRECTORY Atom USING [GetPropFromList, PutPropOnList], -- needed by ViewerOps.AddProp, FetchProp BasicTime USING [FromPupTime, GMT, Now, Unpack, Unpacked], -- Now is needed by IO.time Convert, FS USING [Error, GetName, nullOpenFile, Open, OpenFile], GVBasics, GVRetrieve, Icons, IO USING [GetBlock, int, PutFR, rope, STREAM, time], MessageWindow, PeanutProfile USING [activeMailFile, automaticNewMail], PeanutRetrieve, PeanutSendMail, PeanutWindow, PrincOpsUtils, -- needed by INLINEs in Process Process, PutGet, Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, FromRefText, ROPE, Size, Substr], TEditDisplay, TEditDocument, TextEdit, TextNode, TiogaFileOps, TiogaMenuOps, TiogaOps, UserCredentials USING [Get], ViewerClasses USING [Viewer], ViewerEvents USING [RegisterEventProc, ViewerEvent], ViewerOps USING [AddProp, EnumerateViewers, EnumProc, FetchProp, OpenIcon, PaintViewer, SaveViewer, SetNewVersion], ViewerTools USING [MakeNewTextViewer]; PeanutRetrieveImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, Convert, FS, GVRetrieve, IO, MessageWindow, PeanutProfile, PeanutSendMail, PeanutWindow, PrincOpsUtils, Process, PutGet, Rope, TEditDisplay, TextEdit, TextNode, TiogaFileOps, TiogaMenuOps, TiogaOps, UserCredentials, ViewerEvents, ViewerOps, ViewerTools EXPORTS PeanutRetrieve = BEGIN <<************************************************************************>> ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; RName: TYPE = GVBasics.RName; TiogaCTRL: GVBasics.ItemType = LOOPHOLE[1013B]; -- 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]; }; 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] THEN { viewer _ v; RETURN[FALSE] }; }; ViewerOps.EnumerateViewers[Test]; }; GetMailViewer: PUBLIC ENTRY PROC[name: ROPE] RETURNS [mailViewer: Viewer] = { <> ENABLE UNWIND => NULL; mailViewer _ InternalGetMailViewer[name]; }; InternalGetMailViewer: INTERNAL PROC[name: ROPE] RETURNS [mailViewer: Viewer] = { mailViewer _ FindMailViewer[name]; IF mailViewer=NIL THEN { file: ROPE _ Rope.Concat[name, ".mail"]; fh: FS.OpenFile _ FS.nullOpenFile; mailDoc: TextNode.Ref _ NIL; fh _ FS.Open[file ! FS.Error => IF error.group=user THEN CONTINUE]; IF fh#FS.nullOpenFile THEN { <> file _ FS.GetName[fh].fullFName; mailDoc _ PutGet.FromFileC[fh]; } ELSE { <> <> styleProp: ROPE = "(mail) style"; mailDoc _ TextEdit.DocFromNode[TextEdit.FromRope[file]]; TRUSTED { TiogaOps.PutProp[LOOPHOLE[mailDoc], $Prefix, styleProp] }; }; mailViewer _ ViewerTools.MakeNewTextViewer[info: [ name: file, file: file, data: mailDoc, icon: PeanutWindow.messageSetIcon, iconic: TRUE ]]; SetMailFileName[mailViewer, name]; [] _ 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: TiogaFileOps.Ref; 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]; }; 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 TRUSTED { tdd: TEditDocument.TEditDocumentData _ NARROW[mailViewer.data]; TEditDisplay.EstablishLine[tdd, [LOOPHOLE[firstHeader],0]]; ViewerOps.PaintViewer[mailViewer, client] }; IF ~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] = { OPEN TiogaOps; sourceViewer, destViewer: Viewer; destDoc, sourceDoc, destLast, sourceLast, afterSource: Ref; start, end: Location; level: SelectionGrain; caretBefore, pendingDelete: BOOL; lockedPrimary, lockedSecondary, lockedDest, lockedSource: BOOL _ FALSE; TopParent: PROC [node, root: Ref] RETURNS [parent: Ref] = { DO parent _ Parent[node]; IF parent=root THEN RETURN [node]; node _ parent; ENDLOOP }; Cleanup: PROC = { IF lockedPrimary THEN UnlockSel[primary]; IF lockedSecondary THEN UnlockSel[secondary]; IF lockedDest THEN Unlock[destDoc]; IF lockedSource THEN Unlock[sourceDoc]; }; destViewer _ GetMailViewer[to]; LockSel[primary]; lockedPrimary _ TRUE; [sourceViewer, start, end, level, caretBefore, pendingDelete] _ TiogaOps.GetSelection[]; IF sourceViewer=NIL OR sourceViewer.class.flavor#$Text THEN { UnlockSel[primary]; ReportRope["\nSelect message(s)."]; RETURN }; LockSel[secondary]; lockedSecondary _ TRUE; destDoc _ ViewerDoc[destViewer]; Lock[destDoc]; lockedDest _ TRUE; sourceDoc _ ViewerDoc[sourceViewer]; IF sourceDoc#destDoc THEN { Lock[sourceDoc]; lockedSource _ TRUE }; destLast _ LastChild[destDoc]; sourceLast _ TopParent[end.node, sourceDoc]; afterSource _ Next[sourceLast]; SelectBranches[ -- source viewer: sourceViewer, level: branch, caretBefore: FALSE, pendingDelete: delete, which: primary, start: TopParent[start.node, sourceDoc], end: sourceLast]; SelectBranches[ -- destination viewer: destViewer, start: destLast, end: destLast, level: branch, caretBefore: FALSE, pendingDelete: FALSE, which: secondary]; ToSecondary[]; IF ~delete THEN -- restore original selection SetSelection[sourceViewer, start, end, level, caretBefore, pendingDelete, primary] ELSE IF afterSource # NIL THEN SelectPoint[sourceViewer, [afterSource,0], primary] ELSE CancelSelection[primary]; ReportRope[IF delete THEN "\nMoved to " ELSE "\nCopied to "]; ReportRope[to]; Cleanup[]; }; <<>> <<***********************************************************************>> 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] = { <> ActiveMailFileNotOpen: PROC RETURNS [BOOL] = { mailViewer: Viewer = FindMailViewer[PeanutProfile.activeMailFile]; RETURN [mailViewer=NIL OR mailViewer.iconic] }; showTime: BOOL_ TRUE; status: ROPE; IF newState = unknown THEN RETURN; IF (lastStateReported = notEmpty) AND (newState = someEmpty OR newState = allEmpty) THEN { status_ NIL; PeanutWindow.SetNewMail[FALSE] } ELSE SELECT newState FROM badName => {status_ "\nYour user name is invalid, please log in"; showTime_ FALSE}; badPwd => {status_ "\nYour password is invalid"; showTime_ FALSE}; cantAuth => {status_ "\nCan't check your credentials at this time"; showTime_ FALSE}; userOK => {status_ "\nYour credentials are OK"; showTime_ FALSE}; allDown => status_ "\nAll of the mail servers are down"; someEmpty => status_ "\nAll of the mail servers checked are empty"; allEmpty => {status_ NIL; PeanutWindow.SetNewMail[FALSE]}; notEmpty => {status_ NIL; PeanutWindow.SetNewMail[TRUE]; IF lastStateReported#notEmpty AND PeanutProfile.automaticNewMail AND ActiveMailFileNotOpen[] THEN TRUSTED {Process.Detach[FORK ReadMail[]]} }; ENDCASE => status_ "\nBad State!"; lastStateReported _ newState; IF status # NIL THEN ReportRope[IF showTime THEN status.Concat[IO.PutFR[" at %g", IO.time[]]] ELSE status]; }; ReadMail: ENTRY PROC = TRUSTED { ENABLE UNWIND => NULL; numRetrieved: INT; Process.SetPriority[Process.priorityBackground]; 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: TiogaFileOps.Ref] = { <> <> serverKnown: BOOLEAN _ FALSE; mailDoc: TiogaOps.Ref _ 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: %g", 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: TiogaFileOps.Ref; serverName: ROPE ; TRUSTED {headerNode _ LOOPHOLE[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. }; TiogaOps.Lock[mailDoc]; DO ENABLE UNWIND => TiogaOps.Unlock[mailDoc]; [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]] }; GetFieldContents: PROC [name: ROPE, maxLen: INT] RETURNS [contents: ROPE] = { start: INT _ Rope.Find[m, name]; IF start<0 THEN contents _ "?" ELSE { end: INT; start _ start + Rope.Size[name]; end _ Rope.Find[m, "\n", start]; IF end <= start THEN contents _ "?" ELSE IF end > start+maxLen THEN contents _ Rope.Concat[Rope.Substr[m, start, maxLen-3], "..."] ELSE contents _ Rope.Substr[m, start, end-start] }}; 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]]]; }; headerDate, headerName, headerSubject, header: ROPE; messageNode: TiogaFileOps.Ref; 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["\nTo: ", 15]] ELSE headerName _ gvSender; headerSubject _ GetFieldContents["\nSubject: ", 45]; header _ Rope.Cat["\t", headerDate, "\t", headerName, "\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: TextNode.Ref; messageRoot _ PutGet.FromRope[Rope.Cat[Rope.FromChar['\n],m,formatting]]; <> messageFirst _ TextNode.FirstChild[messageRoot]; TRUSTED { headerN _ LOOPHOLE[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; }; ENDLOOP ; -- Finished reading messages from this server. TiogaOps.Unlock[mailDoc]; <<>> <> 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"]; }; ReadMessageRecord: PROC RETURNS [messageState: MessageState, m, formatting: ROPE, timeStamp: GVBasics.Timestamp, gvSender: RName] = { <> <> ENABLE GVRetrieve.Failed => { ReportRope[ SELECT why FROM communicationFailure => "communication failure", noSuchServer => "no such server", connectionRejected => "server busy", badCredentials => "bad credentials", unknownFailure => "unknown Failure", ENDCASE => "unknown Error" ]; 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 => ERROR; Sender => ERROR; ReturnTo => ERROR; 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.