PeanutRetrieveImpl.mesa
Copyright Ó 1984, 1985, 1986, 1989, 1990, 1992 Xerox Corporation. All rights reserved.
Created by Paxton, 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: Gasbarro June 16, 1986 5:03:42 pm PDT
Willie-Sue, December 14, 1989 3:08:25 pm PST
Bertrand Serlet August 30, 1988 10:58:11 am PDT
Pier, July 6, 1992 4:39 pm PDT
Last changed by Pavel on March 7, 1990 5:48 pm PST
Michael Plass, January 10, 1992 3:41 pm PST
Doug Wyatt, January 14, 1992 4:02 pm PST
Jules Bloomenthal July 1, 1992 1:55 pm PDT
Willie-s, October 26, 1992 1:54 pm PST
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];
Calling SetNewMail[FALSE] here is a crock, and not strictly accurate (if, for example, messages with attachments are not flushed), but it's correct more often than not, and the next poll will restore the truth. This problem should really be fixed in the MailRetrieve stuff (which should call WatchMailBox immediately when a mailbox has been flushed) but messing with XNSMailUser looked too risky to me. -- DKW
};
InternalGetNewMsgs: INTERNAL PROC [open: BOOL ¬ TRUE] RETURNS [numRetrieved: INT] =
reads any new mail
{
allOK: BOOL¬ TRUE;
mailViewer: Viewer;
firstHeader: RefTextNode;
StashNewMessages: PROC [retrieveOK: BOOL] RETURNS[doRemoteFlush: BOOL] =
{
saveOK: BOOL ~ ViewerOps.SaveViewer[mailViewer];
SaveViewer should return TRUE only if the save operation finished, and was successful; in other words, the retrieved messages have really been safely stored, and it's ok to tell the mail server to flush them.
allOK ¬ allOK AND retrieveOK;
don't do remote flush if NOT 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] = {
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 myRetrieveHandle # NIL THEN{
MailRetrieve.Close[myRetrieveHandle.mHandle]; myRetrieveHandle¬ NIL};
};
NewUser: PUBLIC PROC[user: LIST OF RName] = {
Establish a new user on this connection.
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] = {
This is called whenever the condition of the mailbox changes
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 {
Adjust new-mail flag; don't put it down if the active msg set is iconic and using automaticNewMail
IF newState = notEmpty OR NOT (PeanutProfile.automaticNewMail AND (mailViewer = NIL OR mailViewer.iconic)) THEN
PeanutWindow.SetNewMail[newState=notEmpty];
Report abnormal states to the user
IF status # NIL THEN {
PeanutWindow.OutputRope["\n"];
PeanutWindow.OutputRope[status];
};
Perform automatic mail retrieval if appropriate
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] = {
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: 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?
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: MailRetrieve.ServerState; -- The state of the server.
headerNode: RefTextNode;
serverName: MailBasics.RName;
headerNode ¬ TiogaOps.LastChild[mailDoc];
Step through the servers.
[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] = {
Returns a string of the form dd-mmm-yy (for example, 17-Nov-83)
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.
Flush the mailbox if desired, we've stashed the messages.
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] = {
PeanutWindow.OutputRope[IO.PutFR1["\nIsUser[\"%q\"] ...", [rope[this]]]];
FOR rL: MailBasics.RNameList ¬ userRNameList, rL.rest UNTIL rL = NIL DO
PeanutWindow.OutputRope[IO.PutFR1["\n\t\"%q\"", [rope[rL.first.name]]]];
IF this.Equal[rL.first.name, FALSE] THEN RETURN[TRUE];
ENDLOOP;
FOR rL: LIST OF ROPE ¬ simpleUserName, rL.rest UNTIL rL = NIL DO
PeanutWindow.OutputRope[IO.PutFR["\n\t\"%q\"", [rope[rL.first]]]];
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] = {
This routine reads the messages on this connection, returning messageState = noMore
when there aren't any more.
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.