<> <> <> <> 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 { <> 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; <> 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. <<>> <> <> <> <> <> <> <> <> <<>>