File: PeanutRetrieveImpl.mesa April 1, 1983 2:17 pm
Last edited by Bill Paxton, May 12, 1983 4:11 pm
Last Edited by: Pausch, July 14, 1983 3:26 pm
Last edited by Doug Wyatt, October 13, 1983 2:35 pm
DIRECTORY
GVRetrieve,
GVBasics,
IO,
MessageWindow,
Process,
Rope,
RopeIO,
CIFS,
Time USING [Current],
UserCredentials USING [GetUserCredentials],
PeanutRetrieve,
PeanutSendMail,
PeanutWindow,
ViewerClasses,
ViewerEvents,
ViewerOps,
ViewerTools,
VirtualDesktops,
TextEdit,
TextNode,
PutGet,
TEditDisplay,
TEditDocument,
TiogaFileOps,
TiogaMenuOps,
TiogaOps,
UserProfile,
Icons;
PeanutRetrieveImpl: CEDAR MONITOR
IMPORTS GVRetrieve, IO, CIFS, Time, MessageWindow, UserCredentials, PeanutSendMail, PeanutWindow, VirtualDesktops, Process, PutGet, TextNode, TEditDisplay, TiogaFileOps, TiogaMenuOps, TiogaOps, TextEdit, UserProfile, ViewerEvents, ViewerOps, ViewerTools, Rope
EXPORTS PeanutRetrieve =
BEGIN OPEN IO, PeanutRetrieve;
************************************************************************
Viewer: TYPE = ViewerClasses.Viewer;
TiogaCTRL: GVBasics.ItemType;
ReportRope: PROCEDURE [r: ROPE] = { PeanutWindow.OutputRope[r] };
GetNewMsgs: PUBLIC ENTRY PROC [open: BOOLTRUE] =
BEGIN ENABLE UNWIND => NULL; [] ← InternalGetNewMsgs[open]; END;
tiogaContents: ViewerTools.TiogaContents ← NEW[ViewerTools.TiogaContentsRec];
GetMailViewer: PUBLIC ENTRY PROC [name: ROPE] RETURNS [mailViewer: Viewer] = {
Make this an entry so don't have two Get's at once for same mail file
ENABLE UNWIND => NULL;
mailViewer ← InternalGetMailViewer[name];
};
InternalGetMailViewer: INTERNAL PROC [name: ROPE] RETURNS [mailViewer: Viewer] = {
Make this an entry so don't have two Get's at once for same mail file
ENABLE UNWIND => NULL;
style: ROPE;
mailDoc: TextNode.Ref;
newFile: BOOLFALSE;
mailViewer ← VirtualDesktops.FindViewer[name].viewer;
IF mailViewer#NIL THEN RETURN;
mailDoc ← PutGet.FromFile[name ! CIFS.Error => {
newFile ← TRUE;
mailDoc ← TextEdit.DocFromNode[TextEdit.FromRope[name]];
TRUSTED { TiogaOps.PutProp[LOOPHOLE[mailDoc], $Prefix, style ← "(mail) style"] };
CONTINUE }];
mailViewer ← ViewerTools.MakeNewTextViewer[info: [icon: PeanutWindow.messageSetIcon, iconic: TRUE, name: name]];
[] ← ViewerEvents.RegisterEventProc[proc: MessageSetHasBeenEdited, event: edit, filter: mailViewer, before: TRUE];
[] ← ViewerEvents.RegisterEventProc[proc: MessageSetHasBeenSaved, event: save, filter: mailViewer, before: FALSE];
mailViewer.file ← name;
mailViewer.class.set[mailViewer, mailDoc, TRUE, $TiogaDocument];
TiogaMenuOps.DefaultMenus[mailViewer];
TiogaMenuOps.FirstLevelOnly[mailViewer];
};
InternalGetNewMsgs: INTERNAL PROC [open: BOOLTRUE] RETURNS [numRetrieved: INT] =
reads any new mail
BEGIN
allOK: BOOLTRUE;
mailViewer: Viewer;
firstHeader: TiogaFileOps.Ref;
StashNewMessages: PROC [retrieveOK: BOOL] RETURNS[doRemoteFlush: BOOL] =
BEGIN
allOK ← allOK AND retrieveOK;
ViewerOps.SaveViewer[mailViewer]; -- doesn't return until the save is complete
don't do remote flush if ~retrieveOK
RETURN[retrieveOK AND flushRemoteMail];
END;
mailViewer ← InternalGetMailViewer[UserProfile.Token[key:"Peanut.ActiveMailFile",default:"Active.mail"]];
[numRetrieved, firstHeader] ← AddNewMessages[StashNewMessages, mailViewer];
IF numRetrieved = 0 THEN {
ReportRope["\nNo messages were retrieved"]; RETURN};
IF mailViewer.iconic AND UserProfile.Boolean["Peanut.AutomaticNewMail", FALSE] 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];
END;
***********************************************************************
MessageSetHasBeenEdited: PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOLFALSE] = {
IF before THEN {
viewer.icon ← PeanutWindow.dirtyMessageSetIcon;
IF viewer.iconic THEN ViewerOps.PaintViewer[viewer: viewer, hint: all]; };
};
MessageSetHasBeenSaved: PROC [viewer: ViewerClasses.Viewer, event: ViewerEvents.ViewerEvent, before: BOOL] RETURNS[abort: BOOLFALSE] = {
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: BOOLFALSE;
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: BOOLEANTRUE;
gvRetrieveHandle: GVRetrieve.Handle← NIL; -- cookie for receiving messages.
OpenConnection: PUBLIC PROC[user: RName] = {
This establishes a retrieve connection, and sets up a Mail Polling proc
CloseConnection[];
NewUser[user] ;
};
CloseConnection: PUBLIC PROC[] = {
This closes the connection, and invalidates the connection handle.
IF gvRetrieveHandle # NIL THEN{
GVRetrieve.Close[gvRetrieveHandle]; gvRetrieveHandle← NIL};
};
NewUser: PUBLIC PROC[user: RName] = {
Establish a new user on this connection.
IF gvRetrieveHandle = NIL THEN
gvRetrieveHandle ← GVRetrieve.Create[ msgPollingInterval, WatchMailBox ];
GVRetrieve.NewUser[gvRetrieveHandle, user, UserCredentials.GetUserCredentials[].password ] ;
} ;
lastStateReported: GVRetrieve.MBXState← unknown;
WatchMailBox: PROC[newState: GVRetrieve.MBXState] =
This is called when the condition of the mailbox changes
BEGIN
ActiveMailFileNotOpen: PROCEDURE RETURNS [BOOL] = {
mailViewer: Viewer = VirtualDesktops.FindViewer[ UserProfile.Token[key:"Peanut.ActiveMailFile",default:"Active.mail"]].viewer;
RETURN [mailViewer=NIL OR mailViewer.iconic] };
showTime: BOOLTRUE;
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 UserProfile.Boolean["Peanut.AutomaticNewMail", FALSE] AND
ActiveMailFileNotOpen[] THEN TRUSTED {Process.Detach[FORK ReadMail[]]}};
ENDCASE => status← "\nBad State!";
lastStateReported ← newState;
IF status # NIL THEN
ReportRope[IF showTime THEN Rope.Concat[status, IO.PutFR[" at %g", time[]]] ELSE status];
END;
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", 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] = {
This is the routine that actually reads the mail & makes the log entry.
It calls FinishedWithServer to commit the log before it flushes any particular mail server.
serverKnown: BOOLEANFALSE;
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.GetUserCredentials[].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", 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: BOOLEANFALSE;  -- seen an archived message?
Cycle through the servers, until you find one that has mail.
If it has mail, then go for it.
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]]};
Step through the servers.
[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] }};
fullDate, headerDate, headerName, headerSubject, header: ROPE;
messageNode: TiogaFileOps.Ref;
headerNode ← TiogaFileOps.InsertNode[headerNode, FALSE];
IF firstHeader=NIL THEN firstHeader ← headerNode;
TiogaFileOps.SetFormat[headerNode, "header"];
fullDate ← IO.PutFR[NIL, IO.time[LOOPHOLE[timeStamp.time]]];
headerDate ← Rope.Substr[fullDate, 0, Rope.Find[fullDate, " ", 2]];
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: ", 50];
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]];
Restore leading CR. Get back a root node.
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;
Clear out the links from the messageRoot to help garbage collection.
};
ViewerOps.SetNewVersion[mailViewer];
ReportRope["."];
messages ← messages + 1;
};
ENDLOOP ; -- Finished reading messages from this server.
TiogaOps.Unlock[mailDoc];
Flush the mailbox if desired, we've stashed the messages.
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[PutFR[": retrieved %g messages.", 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] = {
This routine reads the messages on this connection, returning messageState = noMore
when there aren't any more.
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;
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};
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[ gvRetrieveHandle ] ;
DO
item ← GVRetrieve.NextItem[gvRetrieveHandle];
SELECT item.type FROM
PostMark => ERROR;
Sender => ERROR;
ReturnTo => ERROR;
Recipients => NULL;
Text => m ← GVRetrieve.ReadItem[gvRetrieveHandle];
TiogaCTRL => formatting ← GVRetrieve.ReadItem[gvRetrieveHandle];
Capability => NULL;
Audio => NULL;
updateItem => NULL;
reMail => NULL;
LastItem => EXIT;
ENDCASE => LOOP;
ENDLOOP;
EXITS
gvFailed => messageState← retrieveFailed;
} ;
TRUSTED { TiogaCTRL ← LOOPHOLE[1013B] };
END.