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