-- AccessOp.mesa
-- edited by Schroeder, May 4, 1981 3:09 PM.
-- edited by Brotz, March 4, 1983 9:54 AM
-- edited by Levin, February 24, 1981 4:51 PM.
-- edited by Taft, May 17, 1983 2:43 PM

DIRECTORY
AltoFile USING [DiskFull],
BodyDefs USING [ItemHeader],
csD: FROM "CoreStreamDefs" USING [Checkpoint, Destroy, EndOfStream, GetLength,
GetPosition, MapPageByteToPosition, MapPositionToPageByte, Open, Position, Read,
Reset, SetPosition, StreamHandle, Write, WriteBlock],
exD: FROM "exceptionDefs" USING [AppendExceptionToExceptionLine,
AppendStringToExceptionLine, badCredentialsSemi, badServerNameSemi, busySemi,
cantConnect, continuationHeader, DisplayExceptionLine, dots, dotsDidntRespond,
dotsEmpty, emptySemi, Exception, ExceptionLineOverflow, failedSemi,
FlashExceptionsRegion, GetExceptionString, loginTryAgain, newMailOverfill, nil,
noMailboxes, noNewMail, overflow, semi, SysBug, tocOverflow, unknownErrorSemi],
Inline USING [LowHalf],
MailParseDefs USING [endOfInput],
mfD: FROM "MailFormatDefs" USING [CreateStamp, ParseHeaderForTOC, ParseStamp],
opD: FROM "OperationsDefs" USING [maxTOCStringLength],
Process USING [Yield],
RetrieveDefs USING [AccessProcs, Failed, FailureReason, Handle, MailboxState,
NextServer, ServerName, ServerState],
String USING [AppendNumber, EquivalentString],
vmD: FROM "VirtualMgrDefs" USING [CheckpointDisplayMessage, DMList, ExtendTOC,
FirstFreeTOCIndex, GetTOCFixedPart, TOCFixedPart, TOCFixedPartPtr, TOCHandle,
TOCOverflow],
VMDefs USING [AllocatePage, Error, Page, pageByteSize, Release];


AccessOp: PROGRAM
IMPORTS AltoFile, csD, exD, Inline, mfD, Process, RetrieveDefs, String, vmD, VMDefs
EXPORTS opD =

BEGIN


AccessNewMailOperation: PUBLIC PROCEDURE
[toc: vmD.TOCHandle, key: CARDINAL, retrieveHandle: RetrieveDefs.Handle,
registry: STRING, ignoreUserFeedback: BOOLEAN ← FALSE,
deleteEachMessage: BOOLEAN ← FALSE]
RETURNS [messagesRead: BOOLEAN] =
-- Gets new messages from the user’s mailbox(es) through "retrieveHandle", appends them
-- to the mail file "toc", and updates the TOC. If "deleteEachMessage" is FALSE, then
-- each mailbox in turn is flushed only after successfully retrieveing all messages in
-- that mailbox. If "deleteEachMessage" is TRUE, then each message is deleted from its
-- mailbox after successful retrieval of that message.
BEGIN
breakBuffers: [0 .. 177777B / VMDefs.pageByteSize) = 125;
buffer: VMDefs.Page ← NIL;
mailFileSH: csD.StreamHandle ← NIL;
flashWanted: BOOLEAN ← FALSE;
nextCharLimit: CARDINAL;
charCount: CARDINAL;

BoxNextChar: PROCEDURE RETURNS [lastChar: CHARACTER] =
BEGIN
IF charCount >= nextCharLimit THEN lastChar ← MailParseDefs.endOfInput
ELSE lastChar ← csD.Read[mailFileSH ! csD.EndOfStream =>
{nextCharLimit ← charCount; lastChar ← MailParseDefs.endOfInput; CONTINUE}];
charCount ← charCount + 1;
END; -- of BoxNextChar --

BoxPutChar: PROCEDURE [c: CHARACTER] = {csD.Write[mailFileSH, c]};

BoxPositionNextChar: PROCEDURE [page, byte, limit: CARDINAL] =
BEGIN
nextCharLimit ← limit;
charCount ← 0;
csD.SetPosition[mailFileSH, csD.MapPageByteToPosition[page, byte]];
END; -- of BoxPositionNextChar --

lastLineUsed: [0 .. 2] ← 0;

ReportProgressString: PROCEDURE [s: STRING] =
BEGIN
IF ignoreUserFeedback THEN RETURN;
lastLineUsed ← 1;
exD.AppendStringToExceptionLine[s, 1
! exD.ExceptionLineOverflow => CONTINUE ];
END; -- of ReportProgressString --

ReportProgress: PROCEDURE [e: exD.Exception] =
BEGIN
IF ignoreUserFeedback THEN RETURN;
lastLineUsed ← 1;
exD.AppendExceptionToExceptionLine[e, 1
! exD.ExceptionLineOverflow => CONTINUE ];
END; -- of ReportProgress --

lastException: exD.Exception ← exD.nil;

ReportException: PROCEDURE [e: exD.Exception,
f: {flash, dontFlash} ← dontFlash] =
BEGIN
IF ignoreUserFeedback THEN RETURN;
IF lastLineUsed = 2
THEN exD.DisplayExceptionLine[lastException, 1]
ELSE lastLineUsed ← lastLineUsed + 1;
exD.DisplayExceptionLine[e, lastLineUsed];
lastException ← e;
IF f = flash THEN flashWanted ← TRUE;
END; -- of ReportException --


-- Code for AccessNewMailOperation

messagesRead ← FALSE;

BEGIN -- for EXITS --

serverKnown: BOOLEAN ← FALSE;
utilityString: STRING = [opD.maxTOCStringLength];
-- [MAX[opD.maxTOCStringLength, BodyDefs.maxRNameLength, 60]]
tOCEntry: vmD.TOCFixedPart;
tb: vmD.TOCFixedPartPtr = @tOCEntry;

tb.deleted ← tb.changed ← tb.seen ← FALSE;
tb.mark ← ’ ;
tb.offsetToHeader ← 0;

SELECT RetrieveDefs.MailboxState[retrieveHandle] FROM
badName, badPwd => GOTO credentialsError;
cantAuth => GOTO noServers;
ENDCASE; --ok to try

-- Flush buffers on the mail file held by all displayed messages.
FOR dmList: vmD.DMList ← toc.dmList, dmList.next UNTIL dmList = NIL DO
IF dmList.dm # NIL THEN vmD.CheckpointDisplayMessage[dmList.dm, key];
ENDLOOP;

mailFileSH ← csD.Open[fh: toc.mailFile, type: byte, mode: append];
buffer ← VMDefs.AllocatePage[];

DO -- next server
ENABLE
BEGIN
VMDefs.Error =>
BEGIN
UNTIL RetrieveDefs.NextServer[retrieveHandle].noMore DO ENDLOOP;
IF reason = resources THEN GO TO FullDisk;
END;
AltoFile.DiskFull =>
BEGIN
UNTIL RetrieveDefs.NextServer[retrieveHandle].noMore DO ENDLOOP;
GO TO FullDisk;
END;
END; -- ENABLE --

messages: CARDINAL ← 0;
connected, archivedReported: BOOLEAN ← FALSE;
retProcs: RetrieveDefs.AccessProcs;
whyFailed: RetrieveDefs.FailureReason;

noMore: BOOLEAN;
serverState: RetrieveDefs.ServerState;
Process.Yield[];
[noMore, serverState, retProcs] ← RetrieveDefs.NextServer[retrieveHandle];
IF noMore THEN EXIT;
serverKnown ← TRUE;
RetrieveDefs.ServerName[retrieveHandle, utilityString];
ReportProgressString[utilityString];
IF String.EquivalentString[utilityString, registry]
THEN ReportProgressString[" inbox server "L];
IF serverState # notEmpty THEN
BEGIN
ReportProgress
[IF serverState = empty THEN exD.dotsEmpty ELSE exD.dotsDidntRespond];
LOOP;
END;

DO -- next message
firstChunk: BOOLEAN ← TRUE;
msgExists, archived, deleted: BOOLEAN;
item: BodyDefs.ItemHeader;

[msgExists, archived, deleted] ← retProcs.nextMessage[retrieveHandle
! RetrieveDefs.Failed => {whyFailed ← why; GOTO retrieveError} ];
IF NOT connected THEN {connected ← TRUE; ReportProgress[exD.dots]};
IF NOT msgExists THEN EXIT;
IF archived AND NOT archivedReported THEN
{archivedReported ← TRUE; ReportProgressString["archived messages .. "L]};
IF deleted THEN LOOP;
DO
item ← retProcs.nextItem[retrieveHandle
! RetrieveDefs.Failed => {whyFailed ← why; GOTO retrieveError} ];
IF item.type = Text OR item.type = LastItem THEN EXIT;
ENDLOOP;
IF item.type = LastItem THEN LOOP;
Process.Yield[];

UNTIL item.length = 0 DO -- message chunks
breakBytes: CARDINAL = breakBuffers * VMDefs.pageByteSize;
tb.textLength ← IF item.length <= breakBytes
THEN Inline.LowHalf[item.length] ELSE breakBytes;
item.length ← item.length - tb.textLength;
IF firstChunk THEN mfD.CreateStamp[tb, BoxPutChar]
ELSE BEGIN
exD.GetExceptionString[exD.continuationHeader, utilityString];
tb.textLength ← tb.textLength + utilityString.length;
mfD.CreateStamp[tb, BoxPutChar];
csD.WriteBlock[mailFileSH, @utilityString.text, 0, utilityString.length];
END;
firstChunk ← FALSE;
FOR blockCounter: CARDINAL IN [0 .. breakBuffers) DO -- next block
bytes: CARDINAL = retProcs.nextBlock
[retrieveHandle, DESCRIPTOR[@buffer.chars, VMDefs.pageByteSize]
! RetrieveDefs.Failed => {whyFailed ← why; GOTO retrieveError}];
IF bytes = 0 THEN EXIT;
csD.WriteBlock[mailFileSH, buffer, 0, bytes];
ENDLOOP; -- next block
ENDLOOP; -- message chunk

IF deleteEachMessage THEN
BEGIN
csD.Checkpoint[mailFileSH];
messagesRead ← TRUE;
WITH gvRetProcs: retProcs SELECT FROM
GV => gvRetProcs.deleteMessage[retrieveHandle];
ENDCASE;
END;
messages ← messages + 1;

REPEAT
retrieveError =>
BEGIN
IF NOT connected THEN ReportProgress[exD.dots];
ReportProgress[ SELECT whyFailed FROM
communicationFailure => exD.failedSemi,
noSuchServer => exD.badServerNameSemi,
connectionRejected => exD.busySemi,
badCredentials => exD.badCredentialsSemi,
ENDCASE => exD.unknownErrorSemi ];
csD.Reset[mailFileSH];
csD.Checkpoint[mailFileSH];
LOOP; -- goes to start of next server loop
END;
ENDLOOP; -- next message

csD.Checkpoint[mailFileSH];
retProcs.accept[retrieveHandle ! RetrieveDefs.Failed => CONTINUE ];
IF messages = 0 THEN ReportProgress[exD.emptySemi]
ELSE BEGIN
numString: STRING = [6];
String.AppendNumber[numString,messages,10];
ReportProgressString[numString];
ReportProgress[exD.semi];
messagesRead ← TRUE;
END;
REPEAT
FullDisk =>
BEGIN
ReportProgress[exD.overflow];
csD.Reset[mailFileSH];
csD.Checkpoint[mailFileSH];
ReportException[exD.newMailOverfill];
END;
ENDLOOP; -- next server

VMDefs.Release[buffer];
buffer ← NIL;
IF NOT serverKnown THEN GOTO noMailboxes;

IF messagesRead THEN
BEGIN -- extend toc to include new messages
p: csD.Position;
vmD.GetTOCFixedPart[toc, key, vmD.FirstFreeTOCIndex[toc, key] - 1, tb];
BoxPositionNextChar[tb.firstPage, tb.firstByte + tb.offsetToHeader + tb.textLength, 24];
WHILE (p ← csD.GetPosition[mailFileSH]) < csD.GetLength[mailFileSH] DO
[tb.firstPage, tb.firstByte] ← csD.MapPositionToPageByte[p];
IF ~mfD.ParseStamp[BoxNextChar, tb] THEN exD.SysBug[];
BoxPositionNextChar[tb.firstPage, tb.firstByte + tb.offsetToHeader, tb.textLength];
mfD.ParseHeaderForTOC[utilityString, BoxNextChar];
vmD.ExtendTOC[toc, key, tb, utilityString
! vmD.TOCOverflow => GO TO tocError;
VMDefs.Error => IF reason = resources THEN GO TO tocError;
AltoFile.DiskFull => GO TO tocError];
BoxPositionNextChar[tb.firstPage, tb.firstByte+tb.offsetToHeader + tb.textLength, 24];
ENDLOOP;
END
ELSE ReportException[exD.noNewMail];

EXITS
noMailboxes => ReportException[exD.noMailboxes, flash];
credentialsError => ReportException[exD.loginTryAgain, flash];
noServers => ReportException[exD.cantConnect, flash];
tocError => ReportException[exD.tocOverflow, flash];
END; -- of EXITS block

IF mailFileSH # NIL THEN csD.Destroy[mailFileSH];
IF buffer # NIL THEN VMDefs.Release[buffer];
IF flashWanted AND ~ignoreUserFeedback THEN exD.FlashExceptionsRegion[];
END; -- of AccessNewMailOperation --


END. -- of AccessOp --