DIRECTORY
Convert USING [RopeFromInt],
EFTP USING [Abort, CreateSender, Finish, Handle, PutBlock, Timeout, Trouble],
FS USING [Error, StreamOpen],
IO USING [Close, GetBlock, GetChar, GetLength, SetIndex, STREAM],
PressFormat USING [DDV, PressPasswd],
PressPrinter USING [Handle, PressPrinterRec, ProgressProc, State],
Process USING [Pause, SecondsToTicks],
Pup USING [Address],
PupName USING [Error, NameLookup],
PupWKS USING [eftp],
Rope USING [Cat, Concat, Fetch, Length, ROPE];
PressPrinterImpl:
CEDAR
PROGRAM
IMPORTS Convert, EFTP, FS, IO, Process, PupName, 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;
};
};
IsAPressFile:
PUBLIC
PROC [fileName:
ROPE]
RETURNS [
BOOLEAN] = {
buffer: REF TEXT ← NEW[TEXT[bytesPerPressRecord+1]];
ReadABlock:
PROC = {
nBytesRead: INT;
buffer[bytesPerPressRecord] ← '$;
nBytesRead ← IO.GetBlock[stream, buffer, 0, bytesPerPressRecord];
IF buffer[bytesPerPressRecord] # '$ THEN ERROR; -- it went too far!
IF nBytesRead # bytesPerPressRecord THEN ERROR; -- should always be reading full blocks
};
lengthInBytes: INT;
stream: STREAM;
stream ← FS.StreamOpen[fileName !
FS.Error => TRUSTED {GOTO Quit}
];
lengthInBytes ← IO.GetLength[stream];
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];
TRUSTED {
ddv: LONG POINTER TO PressFormat.DDV;
ddv ← LOOPHOLE[buffer, LONG POINTER]+TEXT[0].SIZE;
IF ddv.Passwd # PressFormat.PressPasswd THEN RETURN[FALSE];
IF ddv.nRecs # lengthInBytes/bytesPerPressRecord THEN RETURN[FALSE]; };
RETURN[TRUE];
EXITS Quit => {RETURN[FALSE]}
};
pauseTime: INT ← 4;
SendPressFile:
PUBLIC
PROCEDURE [
fileName: ROPE,
server: ROPE,
copies: INT ← 1,
progressProc: PressPrinter.ProgressProc ← NIL,
userName: ROPE ← NIL
] RETURNS [handle: Handle] = {
pages: INT ← -1;
sendingMessage: ROPE ← Rope.Cat["Sending ", fileName, " to ", server];
buffer: REF TEXT ← NEW[TEXT[bytesPerPressRecord+1]];
ReadABlock:
PROC = {
nBytesRead: INT;
buffer[bytesPerPressRecord] ← '$;
nBytesRead ← IO.GetBlock[stream, buffer, 0, bytesPerPressRecord];
IF buffer[bytesPerPressRecord] # '$ 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] ← 0C; nBytesRead ← nBytesRead + 1;
ENDLOOP;
IF aPDFile
AND handle.currentPage = 0
THEN
TRUSTED {
What a hack!
copiesFieldOffset: CARDINAL = 10; -- see the PD file description.
pdCopies: LONG POINTER TO CARDINAL;
pdCopies ← LOOPHOLE[buffer, LONG POINTER]+TEXT[0].SIZE;
pdCopies ← pdCopies + 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 ← NEW[PupRec];
documentDirectory: LONG POINTER TO PressFormat.DDV;
aPDFile: BOOLEAN ← FALSE;
maybePDFile: BOOLEAN ← TRUE;
maybePressFile: BOOLEAN ← TRUE;
TRUSTED { documentDirectory ← LOOPHOLE[buffer, LONG POINTER]+TEXT[0].SIZE; };
handle ← NEW[PressPrinterRec];
TRUSTED { handle.progressProc ← progressProc; }; -- Yetch
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[];
TRUSTED {
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]];
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 SuccessfullySendBlock[pupHandle, buffer]
THEN {
Progress[handle, serverTrouble, "Server trouble"];
IO.Close[stream];
GOTO Quit;
};
handle.currentPage ← i;
Progress[handle, transmitting, sendingMessage];
ENDLOOP;
ReadABlock[];
IF
NOT aPDFile
THEN
TRUSTED {
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 SuccessfullySendBlock[pupHandle, buffer]
THEN {
Progress[handle, serverTrouble, "Server trouble"];
IO.Close[stream];
GOTO Quit;
};
handle.currentPage ← handle.totalNumberOfPages;
Progress[handle, transmitting, sendingMessage];
EFTP.Finish[pupHandle.eftp];
{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 => IF pupHandle.eftp # NIL THEN EFTP.Abort[pupHandle.eftp, "Mumble"];
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 [
eftp: EFTP.Handle ← NIL,
trouble: PupTrouble ← none
];
OpenConnection:
PROC [pupHandle: PupHandle, server:
ROPE]
RETURNS [success: BOOLEAN ← TRUE] = {
pupAddress: Pup.Address;
pupHandle.trouble ← none;
pupAddress ← PupName.NameLookup[server, PupWKS.eftp !
PupName.Error => {success ← FALSE; pupHandle.trouble ← nameNotFound; CONTINUE}];
IF success
THEN
pupHandle.eftp ←
EFTP.CreateSender[pupAddress !
EFTP.Timeout => {success ← FALSE; pupHandle.trouble ← timeout; CONTINUE};
EFTP.Trouble => {
success ← FALSE;
IF code = receiverBusyAbort THEN pupHandle.trouble ← busy
ELSE pupHandle.trouble ← troubleSending;
CONTINUE
};
];
};
SuccessfullySendBlock:
PROC [pupHandle: PupHandle, buffer:
REF
TEXT]
RETURNS [success: BOOLEAN ← TRUE] = {
pupHandle.trouble ← none;
EFTP.PutBlock[pupHandle.eftp, buffer !
EFTP.Timeout => {success ← FALSE; pupHandle.trouble ← timeout; CONTINUE};
EFTP.Trouble => {success ← FALSE; pupHandle.trouble ← troubleSending; CONTINUE};
];
};
END.