//Spruce.Bcpl - last modified:March 4, 1980 9:41 AM

// Spruce.Bcpl -- Spruce Spooler -- Main Procedure
// errors 100
get "Spruce.d"
get "sprucefiles.d"
get "PressFile.d"
// outgoing procedures
external // defined here
[
Spruce
Sprouller
SpruceUserFinishProc
]
// incoming procedures
external // external
[
// SpruceSpool
PrepareToSpool; SomeoneIsKnocking; SpoolAFile
// SpruceUser
Post
// SpruceQueue
CheckPointQueue; CleanupQueue; MarkQueueEntry; SortQueue
// SpruceCheck
CheckPoint; RestoreFromCheckPoint; ActOnEntry
// SpruceInit
SpruceInit
// SpruceUtils
FindErrorMessage; FSGetX; FSPut; SetRMR; Reclaim
// Spruce Errors
SpruceCondition
// SpruceMl
InitFrameRuntime
// Timer
Dismiss; SetTimer; TimerHasExpired
// Contexts
Block; CallContextList; Unqueue
// Interrupts
DisableInterrupts
// OS
InLd; MoveBlock; ReadCalendar; StartIO
// statics
ctxQ; CheckFp; stopsPrinting; SpoolVec
Debug; DebugSystem; DPzero; Capabilities
inMsg; lvSwatContextProc; lvUserFinishProc; numMustPrint; numFilesSpooled; outMsg
PressServer; printDoc; printing; savedSCP; spooling; timeRestart
sprintFPRD; SproullerQ; spruceFPRD; SpruceSavedUFP; SpruceZone
firstDCB//defined here
]

static firstDCB

manifest MinTarryTime = 300 // at least 3 sec. after any invocation
manifest PostSpoolerTarryTime = 300 // wait at least 3 sec. before returning
manifest PostSpoolerKnockTime = 700 // wait 7 seconds before printing, if someone else knocked
manifest PostPrinterTarryTime = 1000 // 10 seconds after printer return, if someone else knocked or error
// Discussions:
// ------------------------------------------------------
//
numMustPrint is an optimization. You can ignore references to it on first reading. It is a count of
// the number of files that must be printed before more spooling can occur -- used to flush all or part of
// the spool queue, to make room, or assure forward progress.

//
Spooling requests generally have priority over formatting and printing. While one file is being
// accepted, others are rejected, but the spooling detects "knocking": If there’s any indication at all that
// another file might be on the way, Spruce tarries a while waiting for it. Otherwise, after a minimum
// delay to allow reading the file transmission message posted on the screen, if there’s anything to
// print, it is printed.
// ------------------------------------------------------
let Spruce(pDoc, userParams, runCfa; numargs na) be
// ------------------------------------------------------

//
Called from OS. Controls switching to Initialization, then recalled after Junta, and dispatches to
// operating contexts.
[
DPzero= table [ 0;0 ]
CheckFp = table [ 0; 0; 0; 0; 0; 0 ] // FP for checkpoint file, for use after Junta
MoveBlock(CheckFp, pDoc, 6) // lFP is currently 5

// First, call initialization routine. It will call Spruce(pDoc) when finished.
if na ne 1 then SpruceInit(userParams, runCfa)

// Among the contexts is one that runs Sprouller, below. This here’s the Spooler!
CallContextList(ctxQ!0) repeat
]
// ------------------------------------------------------
and Sprouller() be
// ------------------------------------------------------

//
Main Spruce control. Repeatedly monitor the network, accept files, print them by deferring to the
// Sprint program, and analyze the reports that Sprint provides on its return. In addition, control Spruce’s
// tendencies to dally when there are files to be printed. It does this for two reasons: to give people time
// to read the system status and error reports on the screen, and when it is suspected that someone is
// about to send a file (a request was heard when Spruce or Sprint was doing something else).
//
When files have been spooled, but printing is disabled, store the queue and other vital
// information in the checkpoint after the tarry interval -- then set the tarry interval to 5 minutes, to
// avoid doing this often. This setting is useful only for recovery via "Spruce Restart". The queue is not
// sorted into print priority order.
//
After the appropriate tarry time, if not printing or nothing to print, and if the spooling queue is
// dirty, save it (in arrival order) in checkpoint file (all via CheckPointQueue()).
[
let tarry = false; SetTimer(lv tarry, MinTarryTime)
// This loop restarts at the top (via SpruceInit ) after every call to the printer.
[
Block()

// inMsg provides results of the print operation, if any. It exists, but is null, the first time.
if inMsg&
AnalyzeResults() then SetTimer(lv tarry, PostPrinterTarryTime)

// Satisfy any new spooling requests, if legal.
numMustPrint=0
if SomeoneIsKnocking() then SetTimer(lv tarry,
SpoolAFile()? PostSpoolerKnockTime, PostSpoolerTarryTime) // may set numMustPrint

// Tarry for the appropriate interval, then switch to the printing program if anything’s to print.
if numMustPrint&printing then until TimerHasExpired(lv tarry) do Block() // MUST print next
if TimerHasExpired(lv tarry) then test printing&numFilesSpooled then
PrintFiles() or [ CheckPointQueue(); SetTimer(lv tarry, 1) ] // keep timer fresh
] repeat
]

// ------------------------------------------------------
and SpruceUserFinishProc() be
// ------------------------------------------------------
//
Called as Spruce finishes -- shut everything down, including RAM tasks.
[
@lvSwatContextProc = savedSCP
@#420 = 0
SetRMR(#177776)// setup for silent boot
StartIO(#100000)// Silent boot
for i = 0 to 30000 do [ ] // wait for display to quiesce
@lvUserFinishProc=SpruceSavedUFP
]
// ------------------------------------------------------
and PrintFiles() be
// ------------------------------------------------------

//
There are files to print. Fill in the outMsg supplied to Sprint with last-minute global printing
// information, sort the SproullerQ (spooling queue) into print-priority order, and save the queue in the
// LEVRunDocs region (see SpruceQueue, SpruceCheck). Shut things down, and defer to the previously-
// saved Sprint image. Sprint will resume at the return from the initial OutLd in SpruceInit() -- all state
// will be rebuilt in SpruceInit(), Sprouller() and AnalyzeResults() from information in the checkpoint
// file and in the returned inMsg.
//
If the lead document after the sort was "inProgress" -- had been partially processed when Sprint
// last suspended in favor of Spruce -- indicate permission to resume the processing. Otherwise, the in-
// progress document has been aborted or rescheduled, and processing should begin at the beginning.
// Sprint’s suspend-resume code has complementary testing and state reporting (see also
// AnalyseResults(), below).
[
unless spooling do numMustPrint = numFilesSpooled // no switchout if spooling off
// SortQueue(SORTPriority)
//restore appropriate entry from QueueFile
//
let pDoc = @SproullerQ
let pDoc = printDoc
let inProg = pDoc>>DocG.inProgress
ActOnEntry(SpoolVec!(SpoolVec!0), true)// restore last entry
pDoc>>DocG.inProgress = true // even now
ActOnEntry(SpoolVec!(SpoolVec!0), false)// write back
Reclaim()
//save appropriate entry in from QueueFile
CheckPoint(LEVEternalStatics, LEVEternalStatics)
CheckPoint(LEVSharedRun, LEVReport)
// printDoc = pDoc // saved over checkpoint call, which uses printDoc as a temporary ~~ consider fix
outMsg = FSGetX(20, SpruceZone, 0) // (lInLdMessage)
MoveBlock(lv outMsg>>TOPrinterMsg.fprd, spruceFPRD, 5) // (lFP)
outMsg>>TOPrinterMsg.Capabilities = Capabilities
if inProg then
[
outMsg>>TOPrinterMsg.inProgress = true
outMsg>>TOPrinterMsg.fileCode = printDoc>>DocG.PressFile>>SPruceFile.fileCode
// ~~ duplicate LEVSharedRun info here, for full swap situation
outMsg>>TOPrinterMsg.numSpooled = numFilesSpooled
outMsg>>TOPrinterMsg.numMustPrint = numMustPrint
outMsg>>TOPrinterMsg.DebugSystem = DebugSystem
]
@#420 = 0; SetRMR(#177776); StartIO(#100000)
// display off, silent boot -- stop RAM tasks
InitFrameRuntime(false)
DisableInterrupts()
InLd(sprintFPRD, outMsg) // go print
] // N.B. Have not saved self, so control returns within SpruceInit, thence to top of main loop
// ------------------------------------------------------
and AnalyzeResults() = valof
// ------------------------------------------------------

//
There is an inMsg, so (because this routine deletes it), the system has just been started, or
// Sprint has just resumed Spruce (see PrintFiles(), above, and SpruceInit()). The entire story is revealed
// in inMsg, which must be analyzed with reference to the retrieved SproullerQ -- still in priority order.
//
inMsg reveals the number of completely processed files and commands, and the file index (see
// SpoolAFile() and SpruceQueue) of the last one. It also contains a code (CC..., see Spruce.d), describing
// the reason for resumption -- one indicates system (re)startup (then very little of this applies).
// CCFinished indicates the completion of all processing. In all other cases, processing of some entry was
// underway; its file index is also in the message. The values are checked against previous stored queue
// entries, and discrepancy messages are posted. The status of the queued documents is also updated.
//
Finally, analyze suspension reason and display it. Often, a printing error or printer problem (e.g.,
// jam) occurred: the completion code is a Spruce error code, obtained from Spruce.Errors, substituting
// parameters extracted from the original Sprint error call and supplied in inMsg. If the code doesn’t let
// processing continue (only spooling and keyboard-initiated suspensions and those due to printer jams
// and such may continue), abort processing of the in-progress document by changing its status.
//
To finish up, sort the queue in arrival order, start up the spooler’s listener, and delete inMsg.
//
Returns true only if a longer dalliance is desirable due to error reporting or spooling requests.
[
let completionCode, inProg = inMsg>>TOSpoolerMsg.completionCode, false
// unless completionCode eq CCInit do
//[
RestoreFromCheckPoint(LEVSharedRun, LEVReport, 0 , true)
RestoreFromCheckPoint(LEVEternalStatics, LEVEternalStatics, 0 , true)
//
]
switchon completionCode into
[
case CCInit: endcase // nothing to do -- cold start
case CCRestart: timeRestart = FSGetX(2); ReadCalendar(timeRestart)
spooling, printing = false, false; docase -1 // go resort queue
default: inProg = true // suspension before completion
case CCFinished:
[
//let pDoc, lastCode = @SproullerQ, 0
let pDoc, lastCode = 0, 0
// mark printed files printed, check for consistency
for i = 1 to inMsg>>TOSpoolerMsg.numPrinted do
[
ActOnEntry(SpoolVec!i, true)
pDoc = printDoc
//
unless pDoc¬ pDoc>>DocG.printed do SpruceCondition(150, ECSpoolTerminate)
let stat = STATPrinted + (pDoc>>DocG.protected? STATNeedsPassword, 0)
MarkQueueEntry(pDoc, stat, true) // mark, but don’t repair queue yet
lastCode = pDoc>>DocG.PressFile; lastCode = lastCode eq -1? 0, lastCode>>SPruceFile.fileCode
Post(pDoc, 0, "Printing Complete")
ActOnEntry(SpoolVec!i, false)
let lastDoc = pDoc
//pDoc = @pDoc
]
unless lastCode eq inMsg>>TOSpoolerMsg.lastPrintedCode do SpruceCondition(151, ECSpoolTerminate)
stopsPrinting = inMsg>>TOSpoolerMsg.stopsPrinting
// Handle completion report
if inProg then
[
unless pDoc&
inMsg>>TOSpoolerMsg.inProgressCode eq pDoc>>DocG.PressFile>>SPruceFile. fileCode do
SpruceCondition(152, ECSpoolTerminate)
test completionCode eq CCKnock then [
Post(pDoc,0, "Suspended printing to honor spooling request")
MarkQueueEntry(pDoc, STATInProg, true ) ]
or test completionCode > maxCCOK then
[
PressServer = true // inhibit automatic quit
let errorClass = inMsg>>TOSpoolerMsg.errorClass
let errorSpec = lv inMsg>>TOSpoolerMsg.reportFailed
@errorSpec = completionCode // ~~ should redo all reports
// ~~ could use new SpruceCondition stuff here, but would have to demote fatal print errors some
let str = vec lenErrStr
FindErrorMessage(errorSpec, str, lenErrStr)
Post(pDoc, errorClass, str)
// ~~ shd mark inProgress only a few times, then give up -- shd record abort condition better
let stat = STATPrinted + (pDoc>>DocG.protected? STATNeedsPassword, 0)
MarkQueueEntry(pDoc, (errorClass eq ECEngineTerminate? STATInProg, stat) , true)
]
or Post(pDoc, 0, "Suspended printing for unknown reason")
]
]
case -1: // Restart merges here
// SortQueue(SORTFifo) // Restore chronological order
// CleanupQueue() // Mark new avails, etc.; Only legal when queue in fifo order
] // switchon
// Do whether returning from printer, starting, or restarting
unless PressServer%numFilesSpooled do [ Dismiss(200); finish ] // Stand Alone, all done, good bye
PrepareToSpool() // open up, first time or after printing
inMsg = FSPut(inMsg) // delete message
Post(0,ECStateRequest,"")
resultis completionCode > CCFinished
]
// ------- History . . .

// DCS, July 15, 1977 4:40 PM, called Sprouller, derived from Spruce.Bcpl
// July 16, 1977 3:33 PM, yield Storage Management,
//
Error, Overlay, Miscellaneous utilities to Spruceutils
// July 18, 1977 9:31 PM, first cut at main spooler control loop
// July 21, 1977 5:09 PM, first pass at shared-statics, LEVRun checkpoint version --
//
almost complete.
// July 22, 1977 4:25 PM, add OutLd, InLd stuff
// July 25, 1977 9:52 AM, restore state after return from printer
// July 25, 1977 3:23 PM, add a q sorter of sorts, better analysis
// July 29, 1977 3:12 PM, handle stand-alone stuff better
// August 3, 1977 1:19 PM, repair FIFO sort
// August 8, 1977 9:38 PM, add error management code converging on correct
// August 22, 1977 1:57 PM, freeFile set up before calling SortSpooledFiles
// August 28, 1977 7:50 AM, Spruce->Sprint, Sprouller->Spruce
// September 7, 1977 12:56 PM, add queue control (numMustPrint, etc.)
// October 25, 1977 10:18 AM, numMustPrint=numFilesSpooled if not spooling
// November 3, 1977 7:37 AM, improve spooler-printer comm. in full swap, v4.(2,7)
// November 3, 1977 1:36 PM, user spooler for stand-alone stuff. v4.(2,7)
// December 20, 1977 12:11 PM, SwatContextProc in finish for Trident
// May 15, 1978 9:49 PM, improve tarry timing
// August 31, 1978 7:28 AM, major reorganization, some queue mgmt. moved to SpruceQueue
// September 1, 1978 9:34 AM, use SpruceCondition statt SpruceError -- will post
// September 4, 1978 5:18 PM, repair and simplify tarry timing
// September 5, 1978 8:31 AM, use MarkQueueEntry to modify queue entry status
// September 5, 1978 9:30 AM, silent boot set up at time used
// September 12, 1978 3:44 PM, add CheckCfa
// September 12, 1978 11:36 PM, adapt further to new installation stuff
// September 18, 1978 9:28 AM, CheckCfa -> CheckFp
// September 20, 1978 3:35 PM, format, document
// September 22, 1978 10:09 PM, handle warm restart
// August 1, 1979 11:11 AM, pass output bin status back and forth thru inMsg/outMsg
// August 1, 1979 12:06 PM, reset needsPassword bit when printed
// August 9, 1979 11:54 AM, pass Capabilities thru TOPrinterMsg; PrintFiles says in progress only if it is
// August 29, 1979 12:29 PM, use LEVReport
// March 4, 1980 9:41 AM, use static firstDCB to prevent loss of display stream
//
October 25, 1982 2:02 PM add LEVEternalStatics checkpointing calls