PDUserImpl.mesa
Copyright (C) 1984, 1986, Xerox Corporation. All rights reserved.
Michael Plass, October 31, 1984 10:18:59 am PST
Tim Diebert: February 17, 1986 1:53:38 pm PST
Dave Rumph, August 8, 1986 12:55:06 pm PDT
DIRECTORY
BasicTime,
Commander,
CommandTool,
Convert,
FS,
GVNames,
IO,
PDQueue,
PDRemoteStream,
PDUser,
Process,
Pup,
PupName,
PupStream,
PupWKS,
Rope,
RuntimeError USING [UNCAUGHT],
UserCredentials USING [Get],
ViewerIO USING [CreateViewerStreams],
WatchStats USING [WatchStatsRecord, GetWatchStats];
PDUserImpl: PROGRAM
IMPORTS BasicTime, Commander, CommandTool, Convert, FS, GVNames, IO, PDQueue, PDRemoteStream, Process, PupName, PupStream, Rope, RuntimeError, UserCredentials, ViewerIO, WatchStats
EXPORTS PDUser
= BEGIN
ROPE: TYPE ~ Rope.ROPE;
helloMsg: ROPE ← "Cedar Peach PD Print Server";
CommandCode: TYPE = {login, cancel, cancelReprint, check, help, listQueue, messages, print, reprint, resetQueue, setLoginMessage, start, stop, wait, watch, quit, ambiguous, illegal};
commandTable: ARRAY CommandCode[login..quit] OF ROPE ← [
login: "Login",
cancel: "Cancel",
cancelReprint: "CancelReprint",
check: "Check",
help: "Help",
listQueue: "ListQueue",
messages: "Messages",
print: "Print",
reprint: "Reprint",
resetQueue: "ResetQueue",
setLoginMessage: "SetLoginMessage",
start: "Start",
stop: "Stop",
wait: "Wait",
watch: "Watch",
quit: "Quit"
];
Upper: PROC [ch: CHAR] RETURNS [CHAR] = INLINE {
RETURN [IF ch IN ['a..'z] THEN ch - ('a - 'A) ELSE ch]
};
defaultRegistry: ROPE ← ".pa";
loginMessage: ROPENIL;
TalkWithUser: PUBLIC PROC [stream: IO.STREAM] = {
user: ROPENIL;
loggedIn: BOOLEANFALSE;
TRUSTED BEGIN
ENABLE {PupStream.StreamClosing => GOTO Closing;
PupStream.Timeout => GOTO TimeOut};
string: REF TEXTNEW [TEXT [180]];
password: ROPE;
account: ROPE;
quitting: BOOLEANFALSE;
accessAllowed: BOOLEANTRUE;
echo: BOOLEANTRUE;
flushed: BOOLEANFALSE;
commandCode: CommandCode;
lastRequest: INT ← -1;
PutChar: PROC [char: CHAR] = { IO.PutChar[stream, char]; flushed ← FALSE; };
PutString: PROC [string: ROPE] = { IO.PutRope[stream, string]; flushed ← FALSE; };
PutOK: PROC = {PutString["\n\lok\n\l"]};
PutXXX: PROC = { PutString[" XXX\n\l"]};
sep: CHAR ← ' ;
DelHit: ERROR = CODE;
SendNow: PROC = {IO.Flush[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;
charsAvail: INT ← 0;
bytes: PACKED ARRAY [0..4] OF CHAR;
charsAvail ← IO.UnsafeGetBlock[stream, [LOOPHOLE[LONG[@bytes]], 0, 1]];
IF charsAvail # 0 THEN c ← bytes[0]
ELSE {
gotMark: BOOLFALSE;
IF stream.GetInfo.class = $Pup THEN {
gotMark ← TRUE;
mark ← PupStream.ConsumeMark[stream ! RuntimeError.UNCAUGHT => {gotMark ← FALSE; CONTINUE}];
};
};
SELECT mark FROM
0 => NULL;
dataMark => {ignore ← 1};
timingMark => {ignore ← 1};
ENDCASE => ignore ← 2;
ignore ← ignore - 1;
ENDLOOP;
IF c = '\177 THEN {PutXXX[]; ERROR DelHit};
RETURN [c]
};
GetStringToSpace: PROC [stopper1: CHAR ← ' , stopper2: CHAR ← '\t] RETURNS [r: ROPE] = {
c: CHAR ← GetChar[];
dashCount: NAT ← 0;
inComment: BOOLEANFALSE;
commentHit: BOOLEANFALSE;
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];
IO.Flush[stream];
RETURN[GetStringToSpace[stopper1, stopper2]];
};
IO.Flush[stream];
RETURN [Rope.FromRefText[string]];
};
GetStringToCR: PROC [] RETURNS [ROPE] = {
RETURN[GetStringToSpace['\n, '\n]];
};
GetNumber: PROC [default: INT ← 1] RETURNS [value: INT ← 0] = {
r: ROPE ← GetStringToSpace[];
IF Rope.Length[r] = 0 THEN value ← default ELSE value ← Convert.IntFromRope[r ! Convert.Error => {PutString["Not a vailid number.\n\l"]; value ← default; CONTINUE}];
};
Confirm: PROC RETURNS [yes: BOOLEANFALSE] = {
response: ROPE ← GetStringToSpace[];
IF Rope.Equal[response, "Yes", FALSE] THEN {
RETURN [TRUE]
}
ELSE IF Rope.Length[response] = 0 OR Upper[Rope.Fetch[response, 0]] # 'N THEN {
PutString["Sorry, you must say Yes in just the right way.\n\l"];
RETURN [FALSE]
}
ELSE PutXXX[]
};
GetCommand: PROC = {
nMatches: NAT ← 0;
matchLength: NAT ← 0;
command: ROPE;
commandCode ← illegal;
PutString[">>"];
command ← GetStringToSpace[];
IF Rope.Length[command] = 0 THEN {PutChar['\n]; PutChar['\l]; IO.Flush[stream]; GetCommand[]}
ELSE {
FOR cmd: CommandCode IN [login..quit] DO
candidate: ROPE ← commandTable[cmd];
IF Rope.Length[command] <= Rope.Length[candidate] THEN {
matchLength ← 0;
FOR i: INT IN [0 .. Rope.Length[command]) DO
IF Upper[Rope.Fetch[command, i]] = Upper[Rope.Fetch[candidate, i]]
THEN matchLength ← i+1 ELSE EXIT;
ENDLOOP;
IF matchLength = Rope.Length[command] THEN {
commandCode ← cmd;
IF matchLength = Rope.Length[candidate] 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: ROPE ← commandTable[commandCode];
FOR i: INT IN [Rope.Length[command] .. Rope.Length[candidate]) DO
PutChar[Rope.Fetch[candidate, i]];
ENDLOOP;
IO.Flush[stream];
};
};
};
PutRequestStatus: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = {
PutString[IO.PutFR["%g %g", IO.rope[request.requestTime], IO.rope[request.fileName]]];
IF request.copies # 1 THEN PutString[IO.PutFR1[" (%g copies) ", IO.int[request.copies]]];
PutString[IO.PutFR[" (%g) %g\n\l", IO.rope[request.requestor], IO.rope[(SELECT status FROM
canceled => "Cancelled", waiting => "Waiting", printing => "Printing", ENDCASE => NIL)]]];
};
DoCommand: PROC = {
GetCommand[];
IF NOT loggedIn AND commandCode # login AND commandCode # quit THEN {
PutString["Please log in.\n\l"]
}
ELSE {
SELECT commandCode FROM
login => {
registryMissing: BOOLEANTRUE;
PutString[" --User-- "];
user ← GetStringToSpace[];
registryMissing ← Rope.Find[user, "."] < 0;
IF registryMissing THEN {
user ← Rope.Concat[user, defaultRegistry];
PutString[defaultRegistry];
};
PutString[" --Password-- "];
echo ← FALSE;
password ← GetStringToSpace[ ! UNWIND => echo ← TRUE];
echo ← TRUE;
IF sep # '\n THEN {
PutString[" --Account-- "];
account ← GetStringToSpace[];
};
PutString[" -- Authenticating ... "];
SendNow[];
SELECT GVNames.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};
PutChar['\n];
PutChar['\l];
IO.Flush[stream];
IF loginMessage.Length > 0 THEN {
IO.PutF1[stream, "%g\n\l", IO.rope[loginMessage]];
};
};
print => {
fileName: ROPE;
separator: ROPE;
time: ROPE;
requestNumber: INT ← 0;
createDate: ROPENIL;
bytes: INT ← 0;
copies: INT ← 1;
IF NOT accessAllowed THEN {
PutString["\n\lSorry, you are not allowed to queue requests at this time."];
};
PutString[" --File-- "];
fileName ← GetStringToSpace[];
IF sep # '\n THEN {
PutString[" --Copies-- "];
copies ← MIN[GetNumber[default: 1], CARDINAL.LAST];
};
IF sep # '\n THEN {
PutString[" --Title-- "];
separator ← GetStringToCR[];
};
[bytes, createDate] ← PDRemoteStream.Lookup[fileName, BasicTime.nullGMT, user, password !
PDRemoteStream.Error => {IO.PutF1[stream, "Error: %g\n\l", IO.rope[expl]]; GOTO Bad}];
time ← TimeToRope[];
requestNumber ← PDQueue.QueueRequest[[fileName, time, user, password, separator, copies]];
IF requestNumber < 0 THEN {PutString["\n\lPrint queue full, request denied.\n\l"]; GOTO Bad};
PutString[IO.PutFR["\n\lPrint request %g queued for %g of %g (%g bytes)\n\l", IO.int[requestNumber], IO.rope[fileName], IO.rope[createDate], IO.int[bytes]]];
BEGIN
msg: ROPEIO.PutFR["Version of %g; %g bytes.", IO.rope[createDate], IO.int[bytes]];
PDQueue.LogMessage[msg, requestNumber];
END;
lastRequest ← requestNumber;
EXITS Bad => NULL
};
check => {
requestNumber: INT;
action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = {
PutString[IO.PutFR1["%g ", IO.int[requestNumber]]];
IF status = notFound THEN {
PutString[" not found.\n\l"];
}
ELSE PutRequestStatus[request, status];
};
PutString[" --Request Number<"];
IF lastRequest # -1 THEN IO.Put[stream, IO.int[lastRequest]];
PutString[">-- "];
lastRequest ← requestNumber ← GetNumber[default: lastRequest];
IF requestNumber = -1 THEN {PutChar['\n]; PutChar['\l]}
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 {PutChar['\n]; PutChar['\l]}
ELSE {
owner: ROPENIL;
reqSatus: PDQueue.RequestStatus ← notFound;
action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = {
reqSatus ← status;
IF status # notFound THEN owner ← request.requestor;
};
PDQueue.CheckRequest[requestNumber, action];
PutChar['\n];
IF PDQueue.CancelRequest[requestNumber].ok THEN {
msg: ROPE;
IF reqSatus = printing THEN PDQueue.CancelReprint[];
msg ← IO.PutFR["Cancelled by %g", IO.rope[user]];
PDQueue.LogMessage[msg, requestNumber, IF Rope.IsEmpty[owner] THEN NIL ELSE owner];
PutString["Print request cancelled."];
}
ELSE {PutString["No such request in queue."]};
PutChar['\n]; PutChar['\l];
};
};
cancelReprint => {
PDQueue.CancelReprint[];
PutOK[];
};
listQueue => {
action: PROC [requestNumber: CARDINAL, request: PDQueue.Request, status: PDQueue.RequestStatus] RETURNS [continue: BOOLEANTRUE] = {
IO.Put[stream, IO.int[requestNumber]];
PutChar[' ];
PutRequestStatus[request, status];
};
PutChar['\n];
IF PDQueue.GetSuspended[] THEN {
PutString[" *** Printing is suspended ***"];
PutChar['\n]; PutChar['\l];
};
PDQueue.EnumerateRequests[action];
};
messages => {
action: PROC [message: ROPE] RETURNS [continue: BOOLEANTRUE] = {
IF Match[message] THEN {
PutString[message];
PutChar['\n]; PutChar['\l];
};
};
key: ROPENIL;
Match: PROC [message: ROPE] RETURNS [BOOLEAN] = {
IF Rope.IsEmpty[key] THEN RETURN [TRUE];
RETURN [Rope.Find[message, key]>=0];
};
IF sep # '\n THEN {
PutString[" --matching string-- "];
key ← GetStringToCR[];
};
PutChar['\n]; PutChar['\l];
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: ROPE ← loginMessage;
PutChar['\n];
loginMessage ← GetStringToCR[];
loginMessage ← IO.PutFR["%g (%g)\n\l", IO.rope[loginMessage], IO.rope[user]];
PutString["Old message was: "];
PutString[old];
PutChar['\n]; PutChar['\l];
PDQueue.LogMessage[loginMessage];
};
reprint => {
ncopies: INT ← 0;
confirm: BOOLEANTRUE;
PutString[" --Number of copies-- "];
ncopies ← GetNumber[default: 0];
IF ncopies > 1 THEN {
PutString[" --Confirm reprint of multiple copies-- "];
confirm ← Confirm[];
};
IF confirm THEN {
msg: ROPEIO.PutFR["Reprint request for %g", IO.int[ncopies]];
IF ncopies = 1 THEN msg ← Rope.Concat[msg, " copy"]
ELSE msg ← Rope.Concat[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 => {
PutString["\n\lValid commands are: "];
FOR cmd: CommandCode IN [login..quit] DO
candidate: ROPE ← commandTable[cmd];
PutString[candidate];
IF cmd # quit THEN PutString[", "];
ENDLOOP;
PutChar['\n]; PutChar['\l];
};
quit => {loggedIn ← FALSE; PutChar['\n]; PutChar['\l]; SendNow[]; quitting ← TRUE};
watch => {
stats: WatchStats.WatchStatsRecord ← WatchStats.GetWatchStats[];
IO.PutF[stream, " Free disk:\t%g\n\l", IO.int[stats.diskFree]];
IO.PutF[stream, " Free mds:\t%g\n\l", IO.int[stats.mdsFree]];
IO.PutF[stream, " Free gfi:\t%g\n\l", IO.int[stats.gfiFree]];
IO.PutF[stream, " Free VM:\t%g\n\l", IO.int[stats.vmFree]];
IO.PutF[stream, " VM run:\t%g\n\l", IO.int[stats.vmRun]];
IO.PutF[stream, " Load:\t%g\n\l", IO.real[stats.cpuLoad]];
flushed ← FALSE;
};
wait => {
requestNumber: INT ← lastRequest;
requestStatus: PDQueue.RequestStatus ← waiting;
action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] = {
requestStatus ← status;
};
toldUserThatItIsPrinting: BOOLEANFALSE;
DoPrintingMsg: PROC = {
IF requestStatus = printing AND NOT toldUserThatItIsPrinting THEN {
PutString["Printing..."];
toldUserThatItIsPrinting ← TRUE;
};
};
IF sep # '\n THEN {
PutString[" --Request Number<"];
IF lastRequest # -1 THEN IO.Put[stream, IO.int[lastRequest]];
PutString[">-- "];
lastRequest ← requestNumber ← GetNumber[default: lastRequest];
};
PutChar['\n]; PutChar['\l];
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.\n\l"];
};
};
ambiguous => {
PutString["\nAmbiguous command (type Help<CR> for help)\n\l"];
};
ENDCASE => ERROR;
};
};
IO.PutF1[stream, "\n\l%g\n\l", IO.rope[helloMsg]];
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 Closing => {
IF loggedIn THEN PDQueue.LogMessage["Logged off due to remote close", , user];
};
TimeOut => {
IF loggedIn THEN PDQueue.LogMessage["Logged off due to TimeOut", , user];
};
END;
};
monthName: ARRAY BasicTime.MonthOfYear OF Rope.ROPE ~ ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???"];
TimeToRope: PROC [time: BasicTime.GMT ← BasicTime.nullGMT] RETURNS [r: ROPE] = BEGIN
unpacked: BasicTime.Unpacked ← BasicTime.Unpack[(IF time = BasicTime.nullGMT THEN BasicTime.Now[] ELSE time)];
stream: IO.STREAMIO.ROS[];
zoneChar: CHAR ← ' ;
IO.PutF[stream, "%2g-%g-%02g ", IO.int[unpacked.day], IO.rope[monthName[unpacked.month]], IO.int[unpacked.year MOD 100]];
IO.PutF[stream, "%2g:%02g:%02g", IO.int[unpacked.hour], IO.int[unpacked.minute], IO.int[unpacked.second]];
r ← IO.RopeFromROS[stream];
END;
abortCurrent: REF BOOLNEW[BOOLFALSE];
logStream: IO.STREAMNIL;
MakeLogViewer: PROC = BEGIN
IF logStream # NIL THEN RETURN;
logStream ← ViewerIO.CreateViewerStreams["Peach log"].out;
PDQueue.RegisterTTY[logStream];
END;
PrintFile: PUBLIC PROC [fileName: Rope.ROPE, stream: IO.STREAM, waitUntilCompleted: BOOLFALSE] = BEGIN
copies: INT ← 1;
requestNumber, bytes: INT ← 0;
user, password: ROPE;
[user, password] ← UserCredentials.Get[];
IF fileName # NIL THEN
{fileName ← FS.ExpandName[fileName].fullFName;
[bytes: bytes] ← PDRemoteStream.Lookup[fileName, BasicTime.nullGMT, NIL, NIL !
PDRemoteStream.Error => {PDQueue.LogMessage[expl]; GOTO Out}]};
requestNumber ← PDQueue.QueueRequest[[fileName, TimeToRope[], user, password, NIL, copies]];
IF waitUntilCompleted THEN FOR secs: CARDINAL ← 5, secs+secs/5 DO
done: BOOLFALSE;
Action: PROC [request: PDQueue.Request, status: PDQueue.RequestStatus] ~ {
done ← ~ status IN [waiting..printing];
};
PDQueue.CheckRequest[requestNumber: requestNumber, action: Action];
Process.Pause[Process.SecondsToTicks[secs]]; --wait 5, 6, 7, 8, 9, 10, 12 ... seconds
IF done THEN EXIT;
ENDLOOP;
EXITS Out => RETURN;
END;
RePrintIt: Commander.CommandProc = TRUSTED BEGIN
MakeLogViewer[];
PrintFile[NIL, cmd.out !
FS.Error => {msg ← error.explanation; GOTO Complain};
];
EXITS
Complain => {RETURN [$Failure, msg]};
END;
PrintIt: Commander.CommandProc = TRUSTED BEGIN
tokens: LIST OF ROPE ~ CommandTool.ParseToList[cmd].list;
usageMessage: ROPE ~ "Usage: PDPrint { filename | -wait | -noWait }\n";
wait: BOOLFALSE;
MakeLogViewer[];
IF tokens=NIL THEN RETURN [msg: usageMessage];
FOR each: LIST OF ROPE ← tokens, each.rest UNTIL each=NIL DO
SELECT TRUE FROM
Rope.IsEmpty[each.first] => {};
Rope.Fetch[each.first]='- => SELECT TRUE FROM
each.first.Equal["-wait", FALSE] => wait ← TRUE;
each.first.Equal["-noWait", FALSE] => wait ← FALSE;
ENDCASE => RETURN [msg: usageMessage];
ENDCASE => {
ENABLE FS.Error => {msg ← error.explanation; GOTO Complain};
PrintFile[each.first, cmd.out, wait];
cmd.out.PutF[format: "%g %g.\n", v1: [rope[each.first]], v2: [rope[IF wait THEN "printed" ELSE "queued"]]];
};
ENDLOOP;
EXITS
Complain => {RETURN [$Failure, msg]};
END;
NewConnection: SAFE PROCEDURE [stream: IO.STREAM, clientData: REF ANY, remote: Pup.Address] = TRUSTED {
PupStream.ListenerProc
otherGuy: Rope.ROPE ← PupName.AddressToRope[remote];
TalkWithUser[stream ! PupStream.StreamClosing => CONTINUE];
IO.Close[stream];
};
PeachIt: Commander.CommandProc = TRUSTED BEGIN
MakeLogViewer[];
IF pupListener = NIL THEN {IO.PutRope[cmd.out, "Started Peach\n"]; pupListener ← PupStream.CreateListener[
local: PupWKS.telnet,
worker: NewConnection,
getTimeout: 300000, -- 5 minutes
putTimeout: 300000 -- 5 minutes
];
PDQueue.LogMessage["Started Peach"]}
ELSE {IO.PutRope[cmd.out, "Stopped Peach\n"]; PupStream.DestroyListener[pupListener]; PDQueue.LogMessage["Stop Peach"]; pupListener ← NIL};
END;
pupListener: PupStream.Listener ← NIL;
Commander.Register["PDReprint", RePrintIt, "Reprints last image."];
Commander.Register["PDPrint", PrintIt, "Prints PD files"];
Commander.Register["Peach", PeachIt, "Enables Peach server"];
END....