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,
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:
BOOL ←
TRUE] =
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: BOOL ← FALSE;
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:
BOOL ←
TRUE]
RETURNS [numRetrieved:
INT] =
reads any new mail
BEGIN
allOK: BOOL← TRUE;
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:
BOOL ←
FALSE] = {
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:
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] = {
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: 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 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: 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.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: BOOLEAN ← FALSE; -- 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.