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];
}
};
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.