// IfsPressSend.bcpl -- Background process that sends Press files
// Copyright Xerox Corporation 1979, 1980, 1981, 1982
// Last modified April 9, 1982  12:45 PM by Taft

get wordItem, ST, par1 from "Streams.d"
get "IfsPress.decl"
get "PupEFTP.decl"
get "IfsRs.decl"
get "IfsName.decl"
get "IfsFiles.decl"
get "IfsDirs.decl"

// outgoing procedures
InitPress; EnablePress; PressSendCtx; EnumeratePressQueue;

// incoming procedures
GenPrintInstance; GetPrinterCapabilities; PressEvent
LookupIFSFile; LookupFD; NextFD; LockDirFD; UnlockDirFD; DestroyFD
IFSOpenFile; OpenIFSStream; CloseIFSStream; DeleteFileFromFD
TransferLeaderPage; GetBufferForFD; CloseIFSFile
DestroyJob; DeclarePupSoc; CreateEvent; JobOK
Gets; Resets; Endofs; Closes; ReadBlock; WriteBlock; CleanupDiskStream
FilePos; SetFilePos; FileLength; KsBufferAddress; LnPageSize; PositionPtr
OpenEFTPSoc; CloseEFTPSoc; SendEFTPBlock; SendEFTPEnd; GetEFTPAbort
ExtractSubstring; DoubleDifference; DoubleIncrement; DoubleUsc; DoubleSubtract
MultEq; SetTimer; TimerHasExpired; Dismiss
SysAllocate; SysAllocateZero; SysFree; Zero; MoveBlock; ReadCalendar; Usc

// outgoing statics

// incoming statics
CtxRunning; system

static [ @pps ]

// The Press printing queue is represented as files whose names are
// of the form <System>Press>hostname, where hostname is the name of
// the printing server.  Each file contains one or more Press Queue Entries
// (PQE) describing a Press file to be printed.  Queueing up a new Press file
// consists simply of appending a new PQE to the end of the queue file
// for the destination printing host.

// The background Press file sender context reads through the queue files.
// For each PQE marked pending, it attempts to send the Press file named
// in the PQE to the designated printer.  Upon success or permanent
// failure, it marks the PQE as having been completed.  If the printing
// host is not responding, the PQE is left for a later retry.
// When all PQEs in a queue file have been processed, the file is deleted.

// At present only one version of a queue file for a particular server
// is permitted to exist, and no information is left behind after a
// printing request has been completed.  Possible improvements on this
// scheme are obvious.
let InitPress() be
pps = SysAllocateZero(lenPPS)
CreateEvent(PressEvent, 6000)

and EnablePress(value) be pps>>PPS.enable = value

and PressSendCtx(ctx) be
// This is the context woken up to do the work.  It self-destructs when done.
[ // repeat
CtxRunning>>RSCtx.userInfo = system
pps>>PPS.somethingToDo = false
pps>>PPS.timer = retryInterval/pressCheckInterval
let serverDown = nil

EnumeratePressQueue(PressWantFile, PressSendFile, lv serverDown)

unless pps>>PPS.somethingToDo do pps>>PPS.timer = 32767
if pps>>PPS.timer ne 0 then
   [ pps>>PPS.ctx = 0; DestroyJob() ]
] repeat

and PressWantFile(qfd, lvServerDown) = valof
@lvServerDown = false
pps>>PPS.somethingToDo = true
unless JobOK(jobTypePress) resultis false

// Process this queue file only if
// (1) it was written more recently than last read, meaning new items
//     have been appended to it, or
// (2) it was last read more than retryInterval seconds ago.
let buf = GetBufferForFD(qfd)
TransferLeaderPage(qfd, buf)
let now = vec 2; ReadCalendar(now)
let wantFile = DoubleUsc(lv buf>>ILD.written, lv buf>>ILD.read) ge 0 %
 DoubleDifference(now, lv buf>>ILD.read) ge retryInterval
resultis wantFile
and PressSendFile(pqe, serverName, lvServerDown) = valof
// Attempts to send the press file described by pqe to the specified server.
// Returns true if the request is completed or otherwise disposed of
//  and false if unable to contact the printing server.
// Updates the pqe.status in the true case.
let now = vec 1; ReadCalendar(now)
DoubleSubtract(now, table [ 0; discardTimeout ])
if DoubleUsc(lv pqe>>PQE.time, now) ls 0 then
   [ pqe>>PQE.status = ecPQETimedOut; resultis true ]
if @lvServerDown % pqe>>PQE.status ne ecPQEPending resultis false

let ec = nil
let stream = IFSOpenFile(pqe+pqe>>PQE.fileName, lv ec)
if stream eq 0 then  // file seems to have gone away
   [ pqe>>PQE.status = ec; resultis true ]

// Compute file length in 256-word records
let fileLength = vec 1; FileLength(stream, fileLength)
let nRecords = fileLength!0 lshift 7 + fileLength!1 rshift 9

// Convert the server name to a port
let port = vec lenPort
ec = EnumeratePupAddresses(serverName, 0, port)
if ec ne 0 then
   switchon ec into
      case ecCantGetThere:
      case ecNoServerResponded:
         @lvServerDown = true; resultis false  // transient failure, retry
         pqe>>PQE.status = ec; resultis true  // permanent failure
if port>>Port.socket↑1 eq 0 & port>>Port.socket↑2 eq 0 then
   port>>Port.socket↑2 = socketEFTPReceive

// Check printer capabilities if Spruce 11 format required.
if pqe>>PQE.spruce11 then
   // GetPrinterCapabilities will adjust the Spruce 11 requests in
   // the pqe according to the capabilities of the server.  If no 
   // capability reply is received, then the print request will be
   // issued without any Spruce 11 features.  If the server returns
   // a printer busy or not available status, then @lvServerDown will
   // be set so that the request can be retried later.
   switchon GetPrinterCapabilities(port, pqe) into
      case psGotCapabilities:
      case psNotSpooling:
      case psSpoolingAndBusy:
         @lvServerDown = true;
         Closes(stream); resultis false;
         pqe>>PQE.spruce11 = false;

// Open an EFTP socket and begin the transfer
let soc = vec lenEFTPSoc
OpenEFTPSoc(soc, 0, port)
// SendPressFile (cont'd)

// Main loop of transfer
let recNum, ptr = 0, nil
let timer = nil; SetTimer(lv timer, 100*deadTimeout)

   [ // repeat
   if recNum eq 0 then
      Resets(stream); ptr = 0
      if pqe>>PQE.spruce11 then
         let buf = SysAllocate(256);
         let nBytes = GenPrintInstance(buf, pqe);
         ec = SendEFTPBlock(soc, buf, nBytes, 100*deadTimeout);
   let rec = KsBufferAddress(stream) + ptr rshift 1
   if recNum eq nRecords-1 then
      [  // have document directory in the buffer now
      MoveBlock(lv rec>>DDV.FileStr, pqe+pqe>>PQE.fileName,
       size DDV.FileStr/16)
      MoveBlock(lv rec>>DDV.CreatStr, pqe+pqe>>PQE.requestorName,
       size DDV.CreatStr/16)
      rec>>DDV.fCopy = 1; rec>>DDV.lCopy = pqe>>PQE.copies
      rec>>DDV.fPage = -1; rec>>DDV.lPage = -1

   // Send record, or End if all records now sent
   unless ec ls 0 do  // This check is for the errors in the Spruce11 case.
    ec = recNum ls nRecords? SendEFTPBlock(soc, rec, 512, 100*deadTimeout),
     (SendEFTPEnd(soc, 100*deadTimeout)? 0, EFTPTimeout)
   if ec ls 0 then
      switchon ec into
         case EFTPAbortReceived:
            switchon GetEFTPAbort(soc)>>PBI.pup.words↑1 into
               default:  // abandon this attempt, retry later
               case FileRejectAbort:
                  // Change this to discard the printing request when (if?)
                  // Spruce is modified to generate this error only in
                  // appropriate circumstances.
               case ReceiverBusyAbort:  // restart after 1-second wait
               case OutOfSynchAbort:  // restart immediately
                  // fall thru
         case EFTPAbortSent:  // restart immediately
            soc>>EFTPSoc.SeqNum = 0
            recNum = 0; loop
         //case EFTPTimeout:  // abandon this attempt, retry later
         default: break
   if recNum eq nRecords then  // file sent successfully
      [ pqe>>PQE.status = ecPQECompleted; break ]

   // advance to next record
   SetTimer(lv timer, 100*deadTimeout)  // making progress, reset timer
   recNum = recNum+1; ptr = ptr+512
   PositionPtr(stream, ptr, false)  // will stop at eof
   if ptr eq 2 lshift LnPageSize(stream) then
      [ ptr = 0; CleanupDiskStream(stream) ] // advance to next page
   ] repeatuntil TimerHasExpired(lv timer)


if ec ne 0 then @lvServerDown = true
resultis ec eq 0
and EnumeratePressQueue(WantFile, HandlePQE, arg) be
// Enumerates the Press printing queue.
// For each queue file (i.e., each printing server with pending requests),
// calls WantFile(fd, arg) with the directory locked.
// If WantFile returns true, the file is processed; if false, skipped over.
// Then, for each PQE in the file, calls HandlePQE(pqe, serverName, arg).
// If HandlePQE returns true, the PQE is rewritten into the file.
// Finally, if the file contains no PQEs with status = pending, deletes
// the file.
// Note: caller must have privileges to open Press queue files for writing.
let qfd = LookupIFSFile("<System>Press>**", lcVAll+lcMultiple)
if qfd eq 0 return

   [ // repeat -- for each file
   let wantThis = LookupFD(qfd, lockRead) eq 0? WantFile(qfd, arg), false
   unless wantThis do loop

   // Remember the FP of this file -- so we can detect whether or not
   // the file has been deleted and recreated during subsequent opens.
   let qFP = vec lFP
   MoveBlock(qFP, lv qfd>>FD.dr>>DR.fp, lFP)

   // Call HandlePQE(pqe, serverName) for each entry in the file.
   // n.b. do not keep file open while we are doing this, since
   // HandlePQE might take arbitrarily long.
   let serverName = ExtractSubstring(lv qfd>>FD.dr>>DR.pathName,
    qfd>>FD.lenSubDirString+1, qfd>>FD.lenBodyString-1)
   let pqe = vec maxLenPQE
   let pos = vec 1; Zero(pos, 2)
   let rewriteLast = false
   let foundPending = false
   let busyCount = 0

      [ // repeat -- for each PQE
      let ec = nil
      let stream = OpenIFSStream(qfd, lv ec, modeReadWrite, wordItem)
      if stream eq 0 then
         if ec ne ecFileBusy % busyCount gr 10 then break
         busyCount = busyCount+1
      unless MultEq(lv qfd>>FD.dr>>DR.fp, qFP, lFP) do
         [ CloseIFSStream(stream); break ]  // not the same file as before
// EnumeratePressQueue (cont'd)

      // Here have file open.  Process next entry.
      SetFilePos(stream, pos)
      if rewriteLast then WriteBlock(stream, pqe, pqe!0)
      rewriteLast = false
      FilePos(stream, pos)

      if Endofs(stream) then
         // If no pending entries, delete file.  To prevent races,
         // pass open file to DeleteFileFromFD.
         CloseIFSStream(stream, not foundPending)
         unless foundPending do
            if DeleteFileFromFD(qfd, false, true) ne 0 then CloseIFSFile(qfd)

      let pqeOK = valof
         pqe!0 = Gets(stream)
         if Usc(pqe!0, maxLenPQE) gr 0 resultis false
         resultis ReadBlock(stream, pqe+1, pqe!0-1) eq pqe!0-1
      unless pqeOK break

      rewriteLast = HandlePQE(pqe, serverName, arg)
      unless rewriteLast do DoubleIncrement(pos, pqe!0 lshift 1)
      foundPending = foundPending % (pqe>>PQE.status eq ecPQEPending)
      ] repeat

   ] repeatwhile NextFD(qfd)