-- PDUserImpl.mesa -- Copyright (C) 1984, Xerox Corporation. All rights reserved. -- Michael Plass, October 31, 1984 10:18:59 am PST -- Tim Diebert, 5-Sep-86 16:37:25 -- DIRECTORY PDQueue, PDUser, Process, Stream, Time, PDRemoteStream, NameInfoDefs, String; PDUserImpl: PROGRAM IMPORTS PDQueue, Process, Stream, Time, PDRemoteStream, NameInfoDefs, String EXPORTS PDUser = BEGIN helloMsg: LONG STRING ← "Peach PD Print Server"; CommandCode: TYPE = {login, cancel, cancelReprint, check, help, listQueue, messages, print, reprint, resetQueue, setLoginMessage, serviced, start, stop, wait, quit, ambiguous, illegal}; commandTable: ARRAY CommandCode[login..quit] OF LONG STRING ← [ login: "Login", cancel: "Cancel", cancelReprint: "CancelReprint", check: "Check", help: "Help", listQueue: "ListQueue", messages: "Messages", print: "Print", reprint: "Reprint", resetQueue: "ResetQueue", serviced: "Serviced", setLoginMessage: "SetLoginMessage", start: "Start", stop: "Stop", wait: "Wait", quit: "Quit" ]; Upper: PROC [ch: CHAR] RETURNS [CHAR] = INLINE { RETURN [IF ch IN ['a..'z] THEN ch - ('a - 'A) ELSE ch] }; defaultRegistry: LONG STRING ← ".pa"L; loginMessage: LONG STRING ← [160]; servicedTime: LONG STRING ← [160]; TalkWithUser: PUBLIC PROC [stream: Stream.Handle, local: BOOL ← FALSE] = { user: LONG STRING ← [80]; loggedIn: BOOLEAN ← FALSE; BEGIN ENABLE Stream.EndOfStream, Stream.TimeOut => GOTO Quit; command: LONG STRING ← [80]; password: LONG STRING ← [80]; account: LONG STRING ← [80]; quitting: BOOLEAN ← FALSE; accessAllowed: BOOLEAN ← TRUE; echo: BOOLEAN ← TRUE; flushed: BOOLEAN ← FALSE; commandCode: CommandCode; lastRequest: INT ← -1; PutChar: PROC [char: CHAR] = { Stream.PutChar[stream, char]; flushed ← FALSE; }; PutString: PROC [string: LONG STRING] = { Stream.PutString[stream, string]; flushed ← FALSE; }; PutLine: PROC = { PutChar['\n]; PutChar['\012]; }; PutOK: PROC = { PutLine[]; PutChar['o]; PutChar['k]; PutLine[]; }; PutXXX: PROC = { PutChar[' ]; PutChar['X]; PutChar['X]; PutChar['X]; PutLine[]; }; sep: CHAR ← ' ; DelHit: ERROR = CODE; SendNow: PROC = {Stream.SendNow[stream]; flushed ← TRUE}; GetChar: PROC RETURNS [CHAR] = { c: CHAR; ignore: INT ← 0; IF NOT flushed THEN SendNow[]; WHILE ignore >= 0 DO mark: NAT ← 0; timingMark: NAT = 5; timingMarkReply: NAT = 6; dataMark: NAT = 1; c ← Stream.GetChar[stream ! Stream.SSTChange => {mark ← sst; CONTINUE}; ]; SELECT mark FROM 0 => NULL; dataMark => {ignore ← 1}; timingMark => {ignore ← 1; Stream.SetSST[stream, timingMarkReply]}; ENDCASE => ignore ← 2; ignore ← ignore - 1; ENDLOOP; IF c = '\177 THEN {PutXXX[]; ERROR DelHit}; RETURN [c] }; GetStringToSpace: PROC [string: LONG STRING, stopper1: CHAR ← ' , stopper2: CHAR ← ' ] = { c: CHAR ← GetChar[]; dashCount: NAT ← 0; inComment: BOOLEAN ← FALSE; commentHit: BOOLEAN ← FALSE; string.length ← 0; UNTIL string.length = string.maxlength OR (NOT inComment AND (c=stopper1 OR c=stopper2)) OR c='\n DO IF c= 'H - 100B OR c= 'A - 100B THEN { IF commentHit THEN {PutXXX[]; ERROR DelHit}; IF string.length > 0 THEN { IF echo THEN PutChar[c]; string.length ← string.length - 1; }; } ELSE IF c= 'W - 100B THEN { IF commentHit THEN {PutXXX[]; ERROR DelHit}; WHILE string.length > 0 DO IF echo THEN PutChar['H - 100B]; string.length ← string.length - 1; ENDLOOP; } ELSE { IF echo THEN PutChar[c]; IF c = '- THEN { commentHit ← TRUE; dashCount ← dashCount + 1; IF dashCount = 2 THEN { inComment ← NOT inComment; dashCount ← 0; }; } ELSE { WHILE dashCount > 0 DO IF NOT inComment THEN { string[string.length] ← '-; string.length ← string.length + 1; }; dashCount ← dashCount - 1; ENDLOOP; IF NOT inComment THEN { string[string.length] ← c; string.length ← string.length + 1; }; }; }; c ← GetChar[]; ENDLOOP; IF string.length <= string.maxlength THEN sep ← c ELSE sep ← ' ; IF string.length = 0 AND sep # '\n THEN { PutChar[sep]; GetStringToSpace[string, stopper1, stopper2]; }; }; GetStringToCR: PROC [string: LONG STRING] = { GetStringToSpace[string, '\n, '\n]; }; GetNumber: PROC [default: INT ← 1] RETURNS [value: INT ← 0] = { string: LONG STRING ← [80]; GetStringToSpace[string]; FOR i: NAT IN [0..string.length) DO c: CHAR ← string[i]; IF c IN ['0..'9] THEN {value ← value * 10 + (c-'0)} ELSE {PutString[" Not a number!"]; PutLine[]; ERROR DelHit}; ENDLOOP; IF string.length = 0 THEN value ← default; }; PutNumber: PROC [value: INT, w: INTEGER ← 4] = { IF value < 0 THEN {PutChar['-]; value ← -value; w ← w-1}; IF value >= 10 THEN {PutNumber[value/10, w-1]; value ← value MOD 10; w ← 1}; WHILE w > 1 DO PutChar[' ]; w ← w - 1; ENDLOOP; PutChar['0+value]; }; Confirm: PROC RETURNS [yes: BOOLEAN] = { response: LONG STRING ← [10]; GetStringToSpace[response]; IF String.Compare[response, "Yes", FALSE] = 0 THEN { RETURN [TRUE] } ELSE IF response.length = 0 OR Upper[response[0]] # 'N THEN { PutLine[]; PutString["Sorry, you must say Yes in just the right way."]; PutLine[]; RETURN [FALSE] } ELSE PutXXX[] }; GetCommand: PROC = { nMatches: NAT ← 0; matchLength: NAT ← 0; commandCode ← illegal; PutString[">>"]; GetStringToSpace[command]; IF command.length = 0 THEN {PutLine[]; GetCommand[]} ELSE { FOR cmd: CommandCode IN [login..quit] DO candidate: LONG STRING ← commandTable[cmd]; IF command.length <= candidate.length THEN { matchLength ← 0; FOR i: NAT IN [0..command.length) DO IF Upper[command[i]] = Upper[candidate[i]] THEN matchLength ← i+1 ELSE EXIT; ENDLOOP; IF matchLength = command.length THEN { commandCode ← cmd; IF matchLength = candidate.length THEN {nMatches ← 1; EXIT}; nMatches ← nMatches + 1; IF NOT loggedIn THEN EXIT }; }; ENDLOOP; IF nMatches > 1 THEN commandCode ← ambiguous ELSE IF commandCode <= quit THEN { candidate: LONG STRING ← commandTable[commandCode]; FOR i: NAT IN [command.length..candidate.length) DO PutChar[candidate[i]]; ENDLOOP; }; }; }; PutRequestStatus: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = { PutString[request.requestTime]; PutChar[' ]; PutString[request.fileName]; IF request.copies # 1 THEN { PutString[" ("]; PutNumber[request.copies, 1]; PutString[" copies) "]; }; PutString[" ("]; PutString[request.requestor]; PutString[") "]; SELECT status FROM canceled => PutString["Cancelled"]; waiting => PutString["Waiting"]; printing => PutString["Printing"]; ENDCASE => NULL; PutLine[]; }; DoCommand: PROC = { GetCommand[]; IF NOT loggedIn AND commandCode # login AND commandCode # quit THEN { PutLine[]; PutString["Login first, please"]; PutLine[]; } ELSE { SELECT commandCode FROM login => { registryMissing: BOOLEAN ← TRUE; PutString[" --User-- "]; GetStringToSpace[user]; FOR i: NAT DECREASING IN [0..user.length) DO IF user[i] = '. THEN {registryMissing ← FALSE; EXIT}; ENDLOOP; IF registryMissing THEN { String.AppendString[user, defaultRegistry]; PutString[defaultRegistry]; }; PutString[" --Password-- "]; echo ← FALSE; GetStringToSpace[password ! UNWIND => echo ← TRUE]; echo ← TRUE; IF sep # '\n THEN { PutString[" --Account-- "]; GetStringToSpace[account]; }; PutString[" -- Authenticating ... "]; SendNow[]; SELECT NameInfoDefs.Authenticate[user, password] FROM individual => {PutString["OK"]; loggedIn ← TRUE}; allDown => {PutString["all GV servers down; I'll have to trust you."]; loggedIn ← TRUE}; badPwd => {PutString["bad password"]; loggedIn ← FALSE}; ENDCASE => {PutString["bad name"]; loggedIn ← FALSE}; PutLine[]; IF loginMessage.length > 0 THEN { PutString[loginMessage]; PutLine[]; }; }; serviced => { PutLine[]; IF local THEN BEGIN servicedTime.length ← 0; String.AppendString[servicedTime, "Printer serviced by: "]; String.AppendString[servicedTime, user]; String.AppendString[servicedTime, " at "]; Time.AppendCurrent[servicedTime]; PDQueue.LogMessage[servicedTime, , user]; END ELSE BEGIN PutString[servicedTime]; PutLine[]; END; }; print => { fileName: LONG STRING ← [80]; separator: LONG STRING ← [80]; time: LONG STRING ← [40]; requestNumber: INT ← 0; createDate: LONG STRING ← [40]; bytes: INT ← 0; copies: INT ← 1; IF NOT accessAllowed THEN { PutLine[]; PutString["Sorry, you are not allowed to queue requests at this time"]; }; PutString[" --File-- "]; GetStringToSpace[fileName]; IF sep # '\n THEN { PutString[" --Copies-- "]; copies ← MIN[GetNumber[default: 1], CARDINAL.LAST]; }; IF sep # '\n THEN { PutString[" --Title-- "]; GetStringToCR[separator]; }; bytes ← PDRemoteStream.Lookup[fileName, createDate, user, password ! PDRemoteStream.Error => { PutLine[]; PutString["Error: "]; PutString[expl]; PutLine[]; GOTO Bad; }; ]; Time.AppendCurrent[time]; requestNumber ← PDQueue.QueueRequest[[fileName, time, user, password, separator, copies]]; IF requestNumber < 0 THEN { PutLine[]; PutString["Print queue full, request denied."]; PutLine[]; GOTO Bad; }; PutLine[]; PutString["Print request "]; PutNumber[requestNumber, 0]; PutString[" queued for "]; PutString[fileName]; PutString[" of "]; PutString[createDate]; PutString[" ("]; PutNumber[bytes]; PutString[" bytes)"]; PutLine[]; BEGIN msg: LONG STRING ← [160]; String.AppendString[msg, "Version of "]; String.AppendString[msg, createDate]; String.AppendString[msg, "; "]; String.AppendLongNumber[msg, bytes]; String.AppendString[msg, " bytes."]; PDQueue.LogMessage[msg, requestNumber]; END; lastRequest ← requestNumber; EXITS Bad => NULL }; check => { requestNumber: INT; action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = { PutLine[]; PutNumber[requestNumber, 5]; PutChar[' ]; IF status = notFound THEN { PutString[" not found."]; PutLine[]; } ELSE PutRequestStatus[request, status]; }; PutString[" --Request Number<"]; IF lastRequest # -1 THEN PutNumber[lastRequest, 1]; PutString[">-- "]; lastRequest ← requestNumber ← GetNumber[default: lastRequest]; IF requestNumber = -1 THEN PutLine[] ELSE IF ABS[requestNumber] > NAT.LAST THEN action[[NIL,NIL,NIL,NIL,NIL,1], notFound] ELSE PDQueue.CheckRequest[requestNumber, action]; }; cancel => { requestNumber: INT; PutString[" --Request Number-- "]; requestNumber ← GetNumber[default: -1]; IF requestNumber = -1 THEN PutLine[] ELSE { owner: LONG STRING ← [80]; reqSatus: PDQueue.RequestStatus; action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = { reqSatus ← status; IF status # notFound THEN { String.AppendString[owner, request.requestor]; }; }; PDQueue.CheckRequest[requestNumber, action]; PutLine[]; IF PDQueue.CancelRequest[requestNumber].ok THEN { msg: LONG STRING ← [80]; IF reqSatus = printing THEN PDQueue.CancelReprint[]; String.AppendString[msg, "Cancelled by "]; String.AppendString[msg, user]; PDQueue.LogMessage[msg, requestNumber, IF owner.length=0 THEN NIL ELSE owner]; PutString["Print request cancelled."]; } ELSE {PutString["No such request in queue."]}; PutLine[]; }; }; cancelReprint => { PDQueue.CancelReprint[]; PutOK[]; }; listQueue => { action: PROC [requestNumber: CARDINAL, request: PDQueue.Request, status: PDQueue.RequestStatus] RETURNS [continue: BOOLEAN ← TRUE] = { PutNumber[requestNumber, 5]; PutChar[' ]; PutRequestStatus[request, status]; }; PutLine[]; IF PDQueue.GetSuspended[] THEN { PutString[" *** Printing is suspended ***"]; PutLine[]; }; PDQueue.EnumerateRequests[action]; }; messages => { action: PROC [message: LONG STRING] RETURNS [continue: BOOLEAN ← TRUE] = { IF Match[message] THEN { PutString[message]; PutLine[]; }; }; key: LONG STRING ← [80]; Match: PROC [message: LONG STRING] RETURNS [BOOLEAN] = { keyLength: CARDINAL ← key.length; IF message.length < keyLength THEN RETURN [FALSE]; FOR i: NAT IN [0..message.length-key.length) DO j: CARDINAL ← 0; WHILE j # keyLength AND Upper[message[i+j]] = key[j] DO j ← j + 1 ENDLOOP; IF j=key.length THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE]; }; IF sep # '\n THEN { PutString[" --matching string-- "]; GetStringToCR[key]; FOR i: NAT IN [0..key.length) DO key[i] ← Upper[key[i]] ENDLOOP; }; PutLine[]; PDQueue.EnumerateMessages[action]; }; start => { IF PDQueue.SetSuspended[FALSE].old = TRUE THEN { PDQueue.LogMessage["Printing started.",,user]; }; PutOK[]; }; stop => { IF PDQueue.SetSuspended[TRUE].old = FALSE THEN { PDQueue.LogMessage["Printing suspended.",,user]; }; PutOK[]; }; setLoginMessage => { old: LONG STRING ← [160]; String.Copy[old, loginMessage]; PutChar[' ]; GetStringToCR[loginMessage]; loginMessage.length ← MIN[loginMessage.length, loginMessage.maxlength-3-user.length]; String.AppendString[loginMessage, " ("]; String.AppendString[loginMessage, user]; String.AppendChar[loginMessage, ')]; PutLine[]; PutString["Old message was: "]; PutString[old]; PutLine[]; PDQueue.LogMessage[loginMessage]; }; reprint => { ncopies: INT ← 0; confirm: BOOLEAN ← TRUE; PutString[" --Number of copies-- "]; ncopies ← GetNumber[default: 0]; IF ncopies > 1 THEN { PutString[" --Confirm reprint of multiple copies-- "]; confirm ← Confirm[]; }; IF confirm THEN { msg: LONG STRING ← [50]; String.AppendString[msg, "Reprint request for "]; String.AppendLongDecimal[msg, ncopies]; IF ncopies = 1 THEN String.AppendString[msg, " copy"] ELSE String.AppendString[msg, " copies"]; PDQueue.LogMessage[msg,,user]; PDQueue.Reprint[ncopies]; PutOK[]; } ELSE PutXXX[]; }; resetQueue => { PutString[" --Do you really want to reset the entire queue?-- "]; IF Confirm[] THEN { PutOK[]; PDQueue.Reset[]; PDQueue.LogMessage["Queue reset",,user]; }; }; help, illegal => { PutLine[]; PutString["Valid commands are: "]; FOR cmd: CommandCode IN [login..quit] DO candidate: LONG STRING ← commandTable[cmd]; PutString[candidate]; IF cmd # quit THEN PutString[", "]; ENDLOOP; PutLine[]; }; quit => {loggedIn ← FALSE; PutLine[]; SendNow[]; quitting ← TRUE}; wait => { requestNumber: INT ← lastRequest; requestStatus: PDQueue.RequestStatus ← waiting; action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = { requestStatus ← status; }; toldUserThatItIsPrinting: BOOLEAN ← FALSE; DoPrintingMsg: PROC = { IF requestStatus = printing AND NOT toldUserThatItIsPrinting THEN { PutString["Printing..."]; toldUserThatItIsPrinting ← TRUE; }; }; IF sep # '\n THEN { PutString[" --Request Number<"]; IF lastRequest # -1 THEN PutNumber[lastRequest, 1]; PutString[">-- "]; lastRequest ← requestNumber ← GetNumber[default: lastRequest]; }; PutLine[]; IF requestNumber IN [0..NAT.LAST] THEN { PDQueue.CheckRequest[requestNumber, action]; IF requestStatus = waiting THEN { PutString["Waiting..."]; }; DoPrintingMsg[]; UNTIL requestStatus = notFound OR requestStatus = canceled DO SendNow[]; Process.Pause[Process.MsecToTicks[10000]]; PDQueue.CheckRequest[requestNumber, action]; DoPrintingMsg[]; PutChar['.]; ENDLOOP; PutString["Done."]; PutLine[]; }; }; ambiguous => { PutLine[]; PutString["Ambiguous command (type Help<CR> for help)"]; PutLine[]; }; ENDCASE => ERROR; }; }; stream.options.signalSSTChange ← TRUE; PutLine[]; PutString[helloMsg]; PutLine[]; UNTIL loggedIn OR quitting DO DoCommand[! DelHit => CONTINUE] ENDLOOP; IF loggedIn THEN PDQueue.LogMessage["Login", , user]; WHILE loggedIn DO DoCommand[! DelHit => CONTINUE] ENDLOOP; PDQueue.LogMessage["Logout", , user]; EXITS Quit => { IF loggedIn THEN PDQueue.LogMessage["Logged off due to timeout", , user]; }; END; }; Init: PROC [] = BEGIN servicedTime.length ← 0; String.AppendString[servicedTime, "Booted: "]; Time.AppendCurrent[servicedTime]; END; Init[]; END.