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