<> <> <> DIRECTORY EFTPDefs, IO, FileIO, LongString, PressFormat, PressPrinter, Process, PupDefs, PupStream, PupTypes, Rope; PressPrinterImpl: CEDAR MONITOR IMPORTS EFTPDefs, LongString, IO, FileIO, 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 Process.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 Process.Aborted; }; }; Byte: TYPE = [0..256); Block: TYPE = PACKED ARRAY [0..bytesPerPressRecord] OF Byte; -- oversize for overrun check IsAPressFile: PUBLIC PROCEDURE [fileName: ROPE] RETURNS [BOOLEAN _ FALSE] = TRUSTED { buffer: REF Block _ NEW[Block]; unsafeBlock: IO.UnsafeBlock; 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; documentDirectory _ unsafeBlock.base _ LOOPHOLE[buffer]; unsafeBlock.startIndex _ 0; unsafeBlock.stopIndexPlusOne _ bytesPerPressRecord; stream _ FileIO.Open[fileName, read ! FileIO.OpenFailed => TRUSTED {GOTO Quit} ]; lengthInBytes _ stream.GetLength[]; IF lengthInBytes > 22 AND stream.GetChar = LOOPHOLE[0AAH, CHAR] AND stream.GetChar = LOOPHOLE[0AAH, CHAR] THEN RETURN[TRUE]; -- it looks like a PD file IF lengthInBytes = 0 OR (lengthInBytes MOD bytesPerPressRecord # 0) THEN RETURN; stream.SetIndex[lengthInBytes-bytesPerPressRecord]; ReadABlock[]; stream.Close; IF documentDirectory.Passwd # PressFormat.PressPasswd THEN RETURN; IF documentDirectory.nRecs # lengthInBytes/bytesPerPressRecord THEN RETURN; RETURN[TRUE]; EXITS Quit => {} }; SendPressFile: PUBLIC ENTRY PROCEDURE [ fileName: ROPE, server: ROPE, copies: INT _ 1, progressProc: PressPrinter.ProgressProc _ NIL, userName: ROPE _ NIL ] RETURNS [handle: Handle] = TRUSTED { ENABLE {ABORTED => GOTO Quit; UNWIND => EFTPDefs.EFTPAbortSending[""]}; sendingMessage: ROPE _ Rope.Cat["Sending ", fileName, " to ", server]; buffer: REF Block _ NEW[Block]; unsafeBlock: IO.UnsafeBlock; 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; 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; documentDirectory _ unsafeBlock.base _ LOOPHOLE[buffer]; unsafeBlock.startIndex _ 0; unsafeBlock.stopIndexPlusOne _ bytesPerPressRecord; handle _ NEW[PressPrinterRec]; handle.progressProc _ progressProc; stream _ FileIO.Open[fileName, read ! FileIO.OpenFailed => TRUSTED {Oops[unableToOpenFile, ": unable to open file"]; GOTO Quit} ]; lengthInBytes _ stream.GetLength[]; IF lengthInBytes <= 22 THEN maybePDFile _ FALSE; IF lengthInBytes < bytesPerPressRecord THEN maybePressFile _ FALSE; maybePDFile _ maybePDFile AND stream.GetChar = LOOPHOLE[0AAH, CHAR] AND stream.GetChar = LOOPHOLE[0AAH, CHAR]; 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"]; RETURN }; IF maybePressFile OR maybePDFile THEN aPDFile _ maybePDFile ELSE { Oops[invalidPressFile, ": not a valid Press or PD file"]; 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"]; stream.Close; GO TO LeaveQuietly} ENDCASE; Process.Yield[]; 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"]; stream.Close; 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; }; IF NOT SuccessfullySendUnsafeBlock[pupHandle, unsafeBlock] THEN { Progress[handle, serverTrouble, "Server trouble"]; stream.Close; GOTO Quit; }; handle.currentPage _ handle.totalNumberOfPages; Progress[handle, transmitting, sendingMessage]; CloseConnection[pupHandle]; Progress[handle, done, fileName.Concat[" sent to "].Concat[server]]; stream.Close; 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: PupDefs.PupAddress _ [, , PupTypes.eftpReceiveSoc]; shortName: STRING = [40]; pupHandle.trouble _ none; LongString.AppendString[shortName, LOOPHOLE[server.ToRefText[]]]; PupDefs.GetPupAddress[@pupAddress, shortName ! 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.stopIndexPlusOne ! 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. <> <> <> <> <> <> <> <<>>