-- Grapevine: Lily: interface to mail server retrieval

-- [Juniper]<Lily>LilyAccess.mesa

-- Andrew Birrell   4-Jan-82 15:24:07
-- Michael Schroeder  December 4, 1981  1:40 PM

DIRECTORY
BodyDefs	USING[ ItemHeader, maxRemarkLength, maxRNameLength,
		       oldestTime, RName, Timestamp ],
LilyAccessDefs	USING[ FailureReason, lastChar, MBXState ],
LilyIODefs	USING[ LogAction ],
RetrieveDefs,
String		USING[ AppendString ],
Storage		USING[ Node, String, Free, FreeString ];

LilyAccess: PROGRAM
IMPORTS LilyIODefs, RetrieveDefs, String, Storage
EXPORTS LilyAccessDefs =

BEGIN

Handle: PUBLIC TYPE = POINTER TO Object;

Object: TYPE = RECORD [
   rHandle: RetrieveDefs.Handle,
   connection: CARDINAL, -- for logging only --
   state: RetrieveDefs.ServerState, -- state of current mailbox (if any) --
   procs: RetrieveDefs.AccessProcs, -- for accessing current mailbox --
   msg: CARDINAL, -- number of current message (from 1) --
   allPrevMsgDeleted: BOOLEAN, -- all msg previous to current position are deleted --
   nextMessageOK: BOOLEAN, --can call nextMessage for this inbox--
   deleted: BOOLEAN, -- current mesage is deleted --
   archived: BOOLEAN, -- current mesage is archived --
   tocKnown: BOOLEAN, -- current mesage has a toc --
   toc: STRING, -- TOC entry for current message (if known) --
   sender: STRING, -- sender of current message (if any) --
   next: CARDINAL, -- number of next server (from 0) --
   unseen: CARDINAL, -- first server we haven't looked at --
   boxCount: CARDINAL, -- number of mailbox servers --
   server: SEQUENCE dummy: CARDINAL OF
      RECORD[allDeleted: BOOLEAN, firstMsg: CARDINAL]
   ];

interval: CARDINAL = 300 -- seconds for mail polling --;
maxTocLength: CARDINAL = BodyDefs.maxRemarkLength;

Create: PUBLIC PROC[ user, password: STRING, connection: CARDINAL]
          RETURNS[ state: LilyAccessDefs.MBXState, handle: Handle ] =
   BEGIN
   rHandle: RetrieveDefs.Handle = RetrieveDefs.Create[interval];
   RetrieveDefs.NewUser[rHandle, user, password];
   state ← SELECT RetrieveDefs.MailboxState[rHandle] FROM
      badName => badName,
      badPwd => badPwd,
      cantAuth => cantAuth,
      allDown => allDown,
      someEmpty => someEmpty,
      allEmpty => allEmpty,
      notEmpty => notEmpty,
     ENDCASE => ERROR;
   IF state IN [allDown..notEmpty]
   THEN BEGIN
      servers: CARDINAL ← 0;
      DO noMore: BOOLEAN;
         -- avoid compiler bug with long result records --
         [noMore,,] ← RetrieveDefs.NextServer[rHandle];
         IF noMore THEN EXIT;
         servers ← servers+1;
      ENDLOOP;
      handle ← Storage.Node[SIZE[Object[servers+1]]];
      handle.boxCount ← servers;
      END
   ELSE BEGIN
      handle ← Storage.Node[SIZE[Object[0+1]]];
      handle.boxCount ← 0;
      END;
   handle.rHandle ← rHandle;
   handle.connection ← connection;
   handle.msg ← 0;
   handle.toc ← Storage.String[maxTocLength];
   handle.sender ← Storage.String[BodyDefs.maxRNameLength];
   handle.next ← handle.unseen ← 0;
   END; --Create--

Destroy: PUBLIC PROC[handle: Handle] =
   BEGIN
   TryToFlush[handle ! RetrieveDefs.Failed => CONTINUE];
   RetrieveDefs.Destroy[handle.rHandle];
   IF handle.toc # NIL THEN Storage.FreeString[handle.toc];
   IF handle.sender # NIL THEN Storage.FreeString[handle.sender];
   Storage.Free[handle];
   END; --Destroy--

Failed: PUBLIC ERROR[why: LilyAccessDefs.FailureReason] = CODE;

Fail: PROC[handle: Handle, why: RetrieveDefs.FailureReason] =
   BEGIN
   handle.msg ← handle.server[handle.next].firstMsg-1;
   handle.tocKnown ← FALSE;
   handle.nextMessageOK ← FALSE;
   handle.allPrevMsgDeleted ← FALSE;
   ERROR Failed[communications];
   END; --Fail--

TryToFlush: PROC[handle: Handle] =
   BEGIN --flush the current inbox if it contains only deleted messages--
   IF handle.next # 0 --not in initial state--
   AND handle.state = notEmpty --this server had some messages--
   AND NOT handle.server[handle.next-1].allDeleted --connection was opened--
   AND handle.allPrevMsgDeleted --no previous message is undeleted--
   AND (IF handle.nextMessageOK
        THEN handle.deleted --current message is deleted too--
        ELSE TRUE -- there isn't a current message --)
   THEN WHILE handle.nextMessageOK DO
      [handle.nextMessageOK, ,handle.deleted] ←
            handle.procs.nextMessage[handle.rHandle];
      IF handle.nextMessageOK AND NOT handle.deleted THEN EXIT;
      REPEAT FINISHED => BEGIN
         handle.procs.accept[handle.rHandle];
         handle.server[handle.next-1].allDeleted ← TRUE;
         LilyIODefs.LogAction[handle.connection, "Inbox flushed"L];
         END;
      ENDLOOP;
   END; --TryToFlush--

Position: PUBLIC PROC[ handle: Handle, msg: CARDINAL]
      RETURNS[ archived, deleted: BOOLEAN] =
   BEGIN
   ENABLE RetrieveDefs.Failed => Fail[handle, why];
   IF msg = 0 THEN ERROR Failed[notFound];
   IF msg <= handle.msg
   THEN BEGIN -- skip to end of server list --
      DO noMore: BOOLEAN;
         -- avoid compiler bug with long result records --
         [noMore, , ] ← RetrieveDefs.NextServer[handle.rHandle];
         IF noMore THEN EXIT;
         ENDLOOP;
      handle.next ← 0; handle.msg ← 0;
      END;
   -- now we're at or before the required server, and the required place --
   WHILE msg > handle.msg
   DO
      doNextServer: BOOLEAN;
      IF handle.next = 0
      OR msg >= handle.server[handle.next].firstMsg
      OR handle.state # notEmpty
      THEN doNextServer ← TRUE -- go on to next server--
      ELSE IF handle.server[handle.next-1].allDeleted
         THEN BEGIN --fake a deleted message--
            handle.nextMessageOK ← handle.deleted ← TRUE;
            doNextServer ← handle.archived ← FALSE
            END
         ELSE BEGIN
            IF NOT handle.deleted THEN handle.allPrevMsgDeleted ← FALSE; 
            [handle.nextMessageOK, handle.archived, handle.deleted] ←
                  handle.procs.nextMessage[handle.rHandle];
            doNextServer ← NOT handle.nextMessageOK
            END;
      IF doNextServer
      THEN BEGIN
         noMore: BOOLEAN;
         TryToFlush[handle];
         IF handle.next >= handle.boxCount THEN EXIT;
         [noMore, handle.state, handle.procs] ←
               RetrieveDefs.NextServer[handle.rHandle];
         IF noMore
         THEN BEGIN
            handle.boxCount ← handle.next;
            handle.next ← 0;
            handle.msg ← 0;
            EXIT
            END
         ELSE BEGIN
            handle.allPrevMsgDeleted ← TRUE;
            handle.nextMessageOK ← TRUE;
            handle.deleted ← TRUE; -- make msg -1 in this inbox appear to be deleted--
            IF handle.next = handle.unseen
            THEN BEGIN
               handle.server[handle.next] ← [allDeleted:FALSE, firstMsg:handle.msg+1];
               handle.unseen ← handle.unseen+1;
               handle.server[handle.unseen].firstMsg ← LAST[CARDINAL];
               END
            ELSE handle.msg ← handle.server[handle.next].firstMsg-1;
            handle.next ← handle.next+1
            END;
         END
      ELSE handle.msg ← handle.msg + 1;
   ENDLOOP;
   IF msg # handle.msg THEN ERROR Failed[notFound];
   handle.tocKnown ← FALSE;
   RETURN[handle.archived, handle.deleted]
   END; --Position--

ReadTOC: PUBLIC PROC[handle: Handle] RETURNS[ toc: STRING ] =
   BEGIN
   ENABLE RetrieveDefs.Failed => Fail[handle, why];
   IF NOT handle.tocKnown
   THEN WITH p: handle.procs SELECT FROM
      GV => p.readTOC[handle.rHandle, handle.toc];
      ENDCASE => BEGIN
         handle.toc.length ← 0 ;
         String.AppendString[handle.toc,
               "[Lily doesn't work for mailboxes on IFS or MAXC]"L];
         END;
   handle.tocKnown ← TRUE;
   RETURN[handle.toc]
   END; --REadTOC--

IllegalBackup: ERROR = CODE;

Read: PUBLIC PROC[handle: Handle,
                  reader: PROC[postmark: BodyDefs.Timestamp,
                               sender:   BodyDefs.RName,
                               readChar: PROC RETURNS[CHARACTER],
                               backup: PROC] ] =
   BEGIN
   ENABLE RetrieveDefs.Failed => Fail[handle, why];
   stamp: BodyDefs.Timestamp ← BodyDefs.oldestTime;
   bLength: CARDINAL = 404; -- assumed to be multiple of 4 --
   buffer: PACKED ARRAY [0..bLength) OF CHARACTER;
   rPos, wPos: [0..bLength] ← 0; -- next char in buffer --
   ReadChar: PROC RETURNS[ c: CHARACTER ] =
      BEGIN
      IF rPos = wPos
      THEN BEGIN
         IF header.length = 0 THEN RETURN[ LilyAccessDefs.lastChar ];
         IF wPos = 0
         THEN BEGIN
            length: CARDINAL =
            handle.procs.nextBlock[handle.rHandle,
                  DESCRIPTOR[@buffer, bLength/2] ];
            header.length ← header.length - length;
            wPos ← wPos + length;
            END
         ELSE BEGIN
            length: CARDINAL =
            handle.procs.nextBlock[handle.rHandle,
                  DESCRIPTOR[@buffer+bLength/4, bLength/2] ];
            header.length ← header.length - length;
            IF wPos # bLength/2 THEN ERROR;
            wPos ← wPos + length;
            END;
         IF wPos = bLength THEN wPos ← 0;
         END;
      c ← buffer[rPos];
      rPos ← IF rPos = bLength-1 THEN 0 ELSE rPos+1;
      END; --ReadChar--
   Backup: PROC =
      BEGIN
      rPos ← IF rPos = 0 THEN bLength-1 ELSE rPos-1;
      IF rPos = wPos THEN ERROR IllegalBackup[];
      END; --Backup--
   header: BodyDefs.ItemHeader;
   handle.sender.length←0;
   WITH p: handle.procs SELECT FROM
      GV => p.startMessage[handle: handle.rHandle,
                        sender: handle.sender,
                        postmark: @stamp];
   ENDCASE => NULL;
   -- skip to text item --
   DO header ← handle.procs.nextItem[handle.rHandle];
      IF header.type = Text OR header.type = LastItem THEN EXIT;
      ENDLOOP;
   reader[stamp, handle.sender, ReadChar, Backup];
   UNTIL header.type = LastItem
   DO header ← handle.procs.nextItem[handle.rHandle]; ENDLOOP;
   END; --Read--
    
WriteTOC: PUBLIC PROC[handle: Handle] =
   BEGIN
   ENABLE RetrieveDefs.Failed => Fail[handle, why];
   handle.tocKnown ← TRUE;
   WITH p: handle.procs SELECT FROM
      GV => p.writeTOC[handle.rHandle, handle.toc];
      ENDCASE => NULL;
   END; --WriteTOC--

Delete: PUBLIC PROC[handle: Handle] =
   BEGIN
   ENABLE RetrieveDefs.Failed => Fail[handle, why];
   IF NOT handle.deleted
   THEN WITH p: handle.procs SELECT FROM
      GV => {p.deleteMessage[handle.rHandle]; handle.deleted ← TRUE};
      ENDCASE => NULL;
   END; --Delete--

END. --LilyAccess--