DIRECTORY
Convert USING [RopeFromInt],
EFTPDefs USING [EFTPAbortSending, EFTPFinishSending, EFTPOpenForSending, EFTPSendBlock, EFTPTimeOut, EFTPTroubleSending],
FS USING [Error, StreamOpen],
IO USING [Close, GetChar, GetLength, SetIndex, STREAM, UnsafeBlock, UnsafeGetBlock],
PressFormat USING [DDV, PressPasswd],
PressPrinter USING [Handle, PressPrinterRec, ProgressProc, State],
Process USING [Pause, SecondsToTicks],
PupDefs USING [GetPupAddress, PupPackageDestroy, PupPackageMake],
PupStream USING [PupNameTrouble],
PupTypes USING [eftpReceiveSoc, PupAddress],
Rope USING [Cat, Concat, Fetch, Length, ROPE];
PressPrinterImpl:
CEDAR
MONITOR
IMPORTS Convert, EFTPDefs, FS, IO, Process, PupDefs, PupStream, Rope
EXPORTS PressPrinter = BEGIN
STREAM: TYPE = IO.STREAM;
ROPE: TYPE = Rope.ROPE;
State: TYPE = PressPrinter.State;
wordsPerPressRecord: CARDINAL = 256;
bytesPerPressRecord: CARDINAL = 512;
Handle: PUBLIC TYPE = REF PressPrinterRec;
PressPrinterRec:
PUBLIC
TYPE =
RECORD [
currentState: State ← queued,
currentStateMessage: ROPE ← NIL,
totalNumberOfPages: INT ← 0,
currentPage: INT ← 0,
progressProc: PressPrinter.ProgressProc
];
CurrentState:
PUBLIC
PROCEDURE [handle: Handle]
RETURNS [State] = {
RETURN[handle.currentState];
};
CurrentStateMessage:
PUBLIC
PROCEDURE [handle: Handle]
RETURNS [
ROPE] = {
RETURN[handle.currentStateMessage];
};
CurrentProgress:
PUBLIC
PROCEDURE [handle: Handle]
RETURNS [fractionOfFileTransmitted:
REAL] = {
fractionOfFileTransmitted ←
IF handle.totalNumberOfPages = 0
THEN 1.0
ELSE handle.currentPage/(handle.totalNumberOfPages + 0.0)
};
Abort:
PUBLIC
PROCEDURE [handle: Handle] = {
handle.currentState ← aborted;
};
SetState:
PROCEDURE [handle: Handle, state: State, stateMessage:
ROPE] =
INLINE {
IF handle.currentState = aborted THEN ERROR ABORTED;
handle.currentState ← state;
handle.currentStateMessage ← stateMessage;
};
Progress:
PROCEDURE [handle: Handle, state: State, stateMessage:
ROPE ←
NIL] = {
SetState[handle, state, stateMessage];
IF handle.progressProc # NIL THEN handle.progressProc[handle];
IF handle.currentState = aborted
THEN {
handle.currentStateMessage ← "Transmission aborted";
handle.progressProc[handle];
ERROR ABORTED;
};
};
Byte: TYPE = [0..256);
Block: TYPE = PACKED ARRAY [0..bytesPerPressRecord] OF Byte; -- oversize for overrun check
IsAPressFile:
PUBLIC
PROCEDURE [fileName:
ROPE]
RETURNS [
BOOLEAN] =
TRUSTED {
buffer: REF Block ← NEW[Block];
unsafeBlock: IO.UnsafeBlock ← [base: NIL, startIndex: 0, count: bytesPerPressRecord];
ReadABlock:
UNSAFE PROCEDURE = {
nBytesRead: INT;
buffer[bytesPerPressRecord] ← 123;
nBytesRead ← stream.UnsafeGetBlock[unsafeBlock];
IF buffer[bytesPerPressRecord] # 123 THEN ERROR; -- it went too far!
IF nBytesRead # bytesPerPressRecord THEN ERROR; -- should always be reading full blocks
};
lengthInBytes: INT;
stream: STREAM;
documentDirectory: LONG POINTER TO PressFormat.DDV;
unsafeBlock.base ← LOOPHOLE[buffer];
documentDirectory ← LOOPHOLE[buffer];
stream ← FS.StreamOpen[fileName !
FS.Error => TRUSTED {GOTO Quit}
];
lengthInBytes ← stream.GetLength[];
IF lengthInBytes > 22 AND stream.GetChar = VAL[0AAH] AND stream.GetChar = VAL[0AAH] THEN {IO.Close[stream]; RETURN[TRUE]}; -- it looks like a PD file
IF lengthInBytes = 0 OR (lengthInBytes MOD bytesPerPressRecord # 0) THEN {IO.Close[stream]; RETURN[FALSE]};
stream.SetIndex[lengthInBytes-bytesPerPressRecord];
ReadABlock[];
IO.Close[stream];
IF documentDirectory.Passwd # PressFormat.PressPasswd THEN RETURN[FALSE];
IF documentDirectory.nRecs # lengthInBytes/bytesPerPressRecord THEN RETURN[FALSE];
RETURN[TRUE];
EXITS Quit => {RETURN[FALSE]}
};
pauseTime: INT ← 4;
SendPressFile:
PUBLIC
ENTRY
PROCEDURE [
fileName: ROPE,
server: ROPE,
copies: INT ← 1,
progressProc: PressPrinter.ProgressProc ← NIL,
userName: ROPE ← NIL
] RETURNS [handle: Handle] = TRUSTED {
ENABLE {UNWIND => EFTPDefs.EFTPAbortSending[""]};
pages: INT ← -1;
sendingMessage: ROPE ← Rope.Cat["Sending ", fileName, " to ", server];
buffer: REF Block ← NEW[Block];
unsafeBlock: IO.UnsafeBlock ← [base: NIL, startIndex: 0, count: bytesPerPressRecord];
ReadABlock:
UNSAFE PROCEDURE = {
nBytesRead: INT;
buffer[bytesPerPressRecord] ← 123;
nBytesRead ← stream.UnsafeGetBlock[unsafeBlock];
IF buffer[bytesPerPressRecord] # 123 THEN ERROR; -- it went too far!
IF NOT aPDFile AND nBytesRead # bytesPerPressRecord THEN ERROR; -- should always be reading full blocks for a press file
IF aPDFile
THEN {
IF nBytesRead # bytesPerPressRecord AND handle.currentPage # handle.totalNumberOfPages-1 THEN ERROR;
WHILE nBytesRead < bytesPerPressRecord
DO
buffer[nBytesRead] ← 0; nBytesRead ← nBytesRead + 1;
ENDLOOP;
IF aPDFile
AND handle.currentPage = 0
THEN {
What a hack!
copiesFieldOffset: CARDINAL = 10; -- see the PD file description.
pdCopies: LONG POINTER TO CARDINAL = LOOPHOLE[buffer, LONG POINTER] + copiesFieldOffset*SIZE[CARDINAL];
pdCopies^ ← MIN[MAX[copies, 0], LAST[NAT]];
};
};
};
lengthInBytes: INT;
stream: STREAM ← NIL;
Oops:
PROCEDURE [state: State, message:
ROPE] =
TRUSTED {
Progress[handle, state, fileName.Concat[message]];
IF stream # NIL THEN stream.Close[];
};
pupHandle: PupHandle;
documentDirectory: LONG POINTER TO PressFormat.DDV;
aPDFile: BOOLEAN ← FALSE;
maybePDFile: BOOLEAN ← TRUE;
maybePressFile: BOOLEAN ← TRUE;
unsafeBlock.base ← LOOPHOLE[buffer];
documentDirectory ← LOOPHOLE[buffer];
handle ← NEW[PressPrinterRec];
handle.progressProc ← progressProc;
stream ← FS.StreamOpen[fileName, read !
FS.Error => TRUSTED {Oops[unableToOpenFile, ": unable to open file"]; CONTINUE}
];
IF stream #
NIL
THEN {
ENABLE {ABORTED => {IO.Close[stream]; GOTO Quit}; UNWIND => IO.Close[stream]};
lengthInBytes ← stream.GetLength[];
IF lengthInBytes <= 22 THEN maybePDFile ← FALSE;
IF lengthInBytes < bytesPerPressRecord THEN maybePressFile ← FALSE;
maybePDFile
← maybePDFile
AND
stream.GetChar = VAL[0AAH] AND
stream.GetChar = VAL[0AAH];
maybePressFile ← maybePressFile AND lengthInBytes MOD bytesPerPressRecord = 0;
handle.totalNumberOfPages ← (lengthInBytes+(bytesPerPressRecord-1)) / bytesPerPressRecord;
IF maybePressFile
THEN {
stream.SetIndex[lengthInBytes-bytesPerPressRecord];
ReadABlock[];
maybePressFile ← maybePressFile AND documentDirectory.Passwd = PressFormat.PressPasswd AND documentDirectory.nRecs = handle.totalNumberOfPages;
};
IF maybePressFile
AND maybePDFile
THEN {
Oops[invalidPressFile, ": Press / PD ambiguity"]; IO.Close[stream]; RETURN
};
IF maybePressFile OR maybePDFile THEN aPDFile ← maybePDFile
ELSE {
Oops[invalidPressFile, ": not a valid Press or PD file"]; IO.Close[stream]; RETURN
};
stream.SetIndex[0];
Progress[handle, opening, Rope.Concat["Opening connection with ", server]];
pupHandle ← CreatePupHandle[];
UNTIL OpenConnection[pupHandle, server]
DO
SELECT pupHandle.trouble
FROM
busy => Progress[handle, serverBusy, "Server busy"];
timeout => Progress[handle, serverTimeout, "Server timeout, will keep trying"];
troubleSending => Progress[handle, serverTrouble, "Trouble sending"];
nameNotFound => {Progress[handle, serverTrouble, "Server name not found"]; IO.Close[stream]; GO TO LeaveQuietly};
ENDCASE => {Progress[handle, serverTrouble, "Unknown name lookup error"]; IO.Close[stream]; GO TO LeaveQuietly};
Process.Pause[Process.SecondsToTicks[pauseTime]];
ENDLOOP;
handle.currentPage ← 0;
Progress[handle, transmitting, sendingMessage];
FOR i:
INT
IN [1..handle.totalNumberOfPages)
DO
ReadABlock[];
IF
NOT SuccessfullySendUnsafeBlock[pupHandle, unsafeBlock]
THEN {
Progress[handle, serverTrouble, "Server trouble"];
IO.Close[stream];
GOTO Quit;
};
handle.currentPage ← i;
Progress[handle, transmitting, sendingMessage];
ENDLOOP;
ReadABlock[];
IF
NOT aPDFile
THEN {
IF documentDirectory.Passwd # PressFormat.PressPasswd THEN ERROR;
fill in the document directory
StuffUserName[LOOPHOLE[@(documentDirectory.CreatStr)], userName];
copies ← MIN[MAX[copies, 0], LAST[NAT]];
documentDirectory.fCopy ← 1;
documentDirectory.lCopy ← copies;
pages ← documentDirectory.nParts-1;
};
IF
NOT SuccessfullySendUnsafeBlock[pupHandle, unsafeBlock]
THEN {
Progress[handle, serverTrouble, "Server trouble"];
IO.Close[stream];
GOTO Quit;
};
handle.currentPage ← handle.totalNumberOfPages;
Progress[handle, transmitting, sendingMessage];
CloseConnection[pupHandle];
{msg:
ROPE ← fileName.Concat[" sent to "].Concat[server];
IF copies # 1
THEN {
msg ← msg.Cat[" ", Convert.RopeFromInt[copies], " copies"];
};
IF pages >= 0
THEN {
msg ← msg.Cat[" ", Convert.RopeFromInt[pages], " page", IF pages#1 THEN "s" ELSE NIL];
};
Progress[handle, done, msg];
};
IO.Close[stream];
EXITS
Quit => {EFTPDefs.EFTPAbortSending[""]};
LeaveQuietly => NULL
};
};
BcplUserName: TYPE = PACKED ARRAY [0..32) OF CHAR;
StuffUserName:
UNSAFE
PROCEDURE [dest:
LONG
POINTER
TO BcplUserName, source:
ROPE] =
UNCHECKED {
length: [0..256) ← MIN[source.Length[], 31];
dest[0] ← LOOPHOLE[length, CHAR];
FOR i: NAT IN [0..length) DO dest[i+1] ← source.Fetch[i] ENDLOOP;
};
PupTrouble: TYPE = {none, busy, timeout, troubleSending, nameNotFound};
PupHandle: TYPE = REF PupRec;
PupRec:
TYPE =
RECORD [
trouble: PupTrouble ← none
];
CreatePupHandle:
UNSAFE PROCEDURE []
RETURNS [pupHandle: PupHandle] = UNCHECKED {
pupHandle ← NEW[PupRec];
PupDefs.PupPackageMake[];
};
OpenConnection:
UNSAFE PROCEDURE [pupHandle: PupHandle, server:
ROPE]
RETURNS [success: BOOLEAN ← TRUE] = UNCHECKED {OPEN EFTPDefs;
pupAddress: PupTypes.PupAddress;
pupHandle.trouble ← none;
pupAddress ← PupDefs.GetPupAddress[PupTypes.eftpReceiveSoc, server !
PupStream.PupNameTrouble => {success ← FALSE; pupHandle.trouble ← nameNotFound; CONTINUE}];
IF success
THEN
EFTPOpenForSending[pupAddress !
EFTPTimeOut => {success ← FALSE; pupHandle.trouble ← timeout; CONTINUE};
EFTPTroubleSending => {
success ← FALSE;
IF e = eftpReceiverBusyAbort THEN pupHandle.trouble ← busy
ELSE pupHandle.trouble ← troubleSending;
CONTINUE
};
];
};
SuccessfullySendUnsafeBlock:
UNSAFE PROCEDURE [
pupHandle: PupHandle,
unsafeBlock: IO.UnsafeBlock
] RETURNS [success: BOOLEAN ← TRUE] = UNCHECKED {OPEN EFTPDefs;
pupHandle.trouble ← none;
IF unsafeBlock.startIndex # 0 THEN ERROR;
EFTPSendBlock[unsafeBlock.base, unsafeBlock.count !
EFTPTimeOut => {success ← FALSE; pupHandle.trouble ← timeout; CONTINUE};
EFTPTroubleSending => {success ← FALSE; pupHandle.trouble ← troubleSending; CONTINUE};
];
};
CloseConnection:
UNSAFE PROCEDURE [pupHandle: PupHandle] =
UNCHECKED {
EFTPDefs.EFTPFinishSending[];
PupDefs.PupPackageDestroy[];
};
END.