-- Copyright (C) Xerox Corporation 1984, 1985. All rights reserved.
-- MergeControlImpl.mesa
-- Last edited by Jacks  6-Nov-85 14:01:41

<<This module controls the merge operation.

Currently, the only merge operation supported is for the feps9700 (Formatting Print Service)..the FEPSMerge interface.  During the decomposition phase of FEPS, the text elements are separated from the graphics elements.  The text elements are stored in the form of IP master, and the graphics elements are converted into bands lists as is the case with the usual decomposition operation.  Then during the "merge" operation, the text elements and graphics elements are merged, page at a time, into another IP master.  Actually, bands list for a page is first BandBLTed and then compressed before being "merged" with text elements.  Therefore, the merge operation as it is currently implemented does the conversion of bands list into compressed pixel array as well as merging.>>

DIRECTORY
  ControlMessages USING [Key],
  DecomposerControl USING [FreeDecomposeObject],
  Environment USING [bytesPerPage],
  FEPSMerge USING [
    Cancel, Code, Create, ExceptionCode, ExceptionProc, FinishedCode, FreeMergeJob, Handle, Initialize, MergeDoc, PlatesMerged],
  Heap USING [systemZone],
  MergeControl USING [CancelProc, NotifyProc, TraceLevel, WaitChoice],
  NSFile USING[
    Close, Error, Handle, ID, nullAttributeList, nullHandle, nullID, nullTime, Session, Time],
  NSString USING [
    AppendString, FreeString, MakeString, nullString, String, StringFromMesaString],
  PrintingTypes USING [Option],
  PrintQueue USING [
    Empty, maxStringBytes, nilQueueObjectHandle, ObjectStatus, QueueObjectHandle, QueueStage, QueueWatcher, Requeue],
  Process USING [
    Detach, Priority, priorityBackground, SecondsToTicks, SetPriority, SetTimeout],
  PSAsyncMsg USING [ExpandArrayAndPutString, Proc, PutMesaString],
  PSKMessages USING [GetHandle],
  PSVolume USING [GetDefaultSession],
  QueueFile USING [CreateError, CreateTempFile, Delete, MakePermanent],
  String USING [
    AppendDecimal, AppendLongDecimal, Overflow, StringBoundsFault],
  System USING [
    gmtEpoch, GetGreenwichMeanTime, GreenwichMeanTime, Overflow, SecondsSinceEpoch],
  XMessage USING [Get, Handle],
  XString USING [ReaderBody];

MergeControlImpl: MONITOR
  IMPORTS 
    DecomposerControl, FEPSMerge, Heap, NSFile, NSString, PrintQueue, Process, PSAsyncMsg, PSKMessages, PSVolume, QueueFile, String, System, XMessage
  EXPORTS MergeControl
  SHARES XString =
  BEGIN
  
  Control: TYPE = RECORD [
    stop: BOOLEAN ← TRUE,
    start: BOOLEAN ← FALSE,
    jobQueueEntry: BOOLEAN ← FALSE,
    tpQueueEntry: BOOLEAN ← FALSE
    ];
    
  State: TYPE = RECORD [
    stopped: BOOLEAN ← TRUE,
    merging: BOOLEAN ← FALSE,
    mergeHandle: FEPSMerge.Handle ← NIL, --currently only one merge handle allowed
    tpJob: BOOLEAN ← FALSE,
    mergeFile: NSFile.Handle ← NSFile.nullHandle,
    mergeFileID: NSFile.ID ← NSFile.nullID,
    trace: MergeControl.TraceLevel ← none,
    qOH: PrintQueue.QueueObjectHandle ← PrintQueue.nilQueueObjectHandle,
    exceptions: CARDINAL ← 0, -- FEPSMerge.Exception calls
    stopStatus: PrintQueue.ObjectStatus ← canceledInMerger, -- specified in Stop call
    notifyProc: MergeControl.NotifyProc ← NIL,
    canceledWhileMerging: MergeControl.CancelProc ← NIL,
    putAsyncMsgFromKey: PSAsyncMsg.Proc ← NIL,
    startMergeTime: NSFile.Time ← NSFile.nullTime
    ];
  
  event: CONDITION;
  stopped: CONDITION;
  control: Control ← [];
  state: State ← [];
  
  -- Message domain handle
  controlMsgs: XMessage.Handle ← PSKMessages.GetHandle[control];
    
  session: NSFile.Session = PSVolume.GetDefaultSession[];
    
Init: PUBLIC ENTRY PROCEDURE [
  currentOption: PrintingTypes.Option,
  priority: Process.Priority ← Process.priorityBackground,
  notifyProc: MergeControl.NotifyProc,
  putAsyncMsgFromKey: PSAsyncMsg.Proc] =
  BEGIN
  IF currentOption # feps9700 THEN ERROR; --only engine currently supporting merge operation.
  FEPSMerge.Initialize[
    engineType: currentOption, session: session,
    finishedProc: Finished, exceptionProc: Exception];
  state ← [
    mergeHandle: FEPSMerge.Create[], notifyProc: notifyProc,
    putAsyncMsgFromKey: putAsyncMsgFromKey];
  PrintQueue.QueueWatcher[proc: JobQueueEntry, queueStage: decomposed];
  PrintQueue.QueueWatcher[proc: TPQueueEntry, queueStage: tpDecomposed];
  Process.SetTimeout[@event, Process.SecondsToTicks[seconds: 30]]; --check queue every 30 secs
  Process.Detach[FORK MergeControlLoop[priority]];
  END; --Init 

Start: PUBLIC ENTRY PROCEDURE =
  BEGIN
  control.start ← TRUE;
  control.stop ← FALSE;
  NOTIFY event;
  END; --Start

Stop: PUBLIC ENTRY PROCEDURE [status: PrintQueue.ObjectStatus,
canceledWhileMerging: MergeControl.CancelProc ← NIL] =
  BEGIN
  state.stopStatus ← status;
  state.canceledWhileMerging ← canceledWhileMerging;
  control.stop ← TRUE;
  control.start ← FALSE;
  NOTIFY event;
  END; --Stop

Stopped: PUBLIC ENTRY PROCEDURE [wither: MergeControl.WaitChoice ← dontWait]
  RETURNS [BOOLEAN] =
  BEGIN
  IF wither = wait THEN UNTIL state.stopped DO
    WAIT stopped;
    ENDLOOP;
  RETURN[state.stopped];
  END; --Stopped
  
Status: PUBLIC PROCEDURE RETURNS [merging: BOOLEAN] =
  BEGIN
  RETURN[state.merging];
  END; --Status
  
ModifyTraceLevel: PUBLIC ENTRY PROCEDURE [trace: MergeControl.TraceLevel] =
  BEGIN
  state.trace ← trace;
  END; --ModifyTraceLevel
  
JobQueueEntry: ENTRY PROCEDURE = -- called by PrintQueue
  BEGIN
  control.jobQueueEntry ← TRUE;
  NOTIFY event;
  IF state.trace = verbose THEN
    PSAsyncMsg.PutMesaString["$$Merger JobQEntry"L];
  END; --JobQueueEntry
  
TPQueueEntry: ENTRY PROCEDURE = --called by PrintQueue when TestPattern is queued
  BEGIN
  control.tpQueueEntry ← TRUE;
  NOTIFY event;
  IF state.trace = verbose THEN
    PSAsyncMsg.PutMesaString["$$Merger TPQEntry"L];
  END; --TPQueueEntry
  
MergeControlLoop: ENTRY PROCEDURE [priority: Process.Priority] =
  BEGIN
  Process.SetPriority[priority];
  DO --forever
    SELECT TRUE FROM
      control.tpQueueEntry AND NOT state.merging => BEGIN --over-rides state.stopped
        state.qOH ← PrintQueue.Requeue[fromQueue: tpDecomposed, toQueue: merging];
        IF state.qOH = PrintQueue.nilQueueObjectHandle THEN BEGIN
	  control.tpQueueEntry ← FALSE;
          LOOP;
	  END;
        state.tpJob ← TRUE;
        MergeDocument[];
        END;
      control.stop => BEGIN
        SELECT TRUE FROM --disables merging; merge in progress is aborted
          state.stopped  => NULL;
          state.merging => BEGIN
            FEPSMerge.Cancel[state.mergeHandle];
            Queue[status: state.stopStatus];
            END;
          ENDCASE;
        state.stopped ← TRUE;
        NOTIFY stopped;
        END;
      control.start => BEGIN
        state.stopped ← control.start ← FALSE; -- so we don't keep looking here
        LOOP; -- make another pass
        END;
      state.stopped => NULL;
      state.merging => NULL; --so we don't start multiple merge operations
      control.jobQueueEntry => BEGIN
        state.qOH ← PrintQueue.Requeue[fromQueue: decomposed, toQueue: merging];
        IF state.qOH = PrintQueue.nilQueueObjectHandle THEN BEGIN
          control.jobQueueEntry ← FALSE;
          LOOP;
	  END;
        MergeDocument[];
        END;
      ENDCASE;
    WAIT event;
    ENDLOOP;
  END; --MergeControlLoop

MergeDocument: INTERNAL PROCEDURE =
  BEGIN
  code: FEPSMerge.Code;
  retried: BOOLEAN ← FALSE;
  sMerging: LONG STRING = "$$Merging ""<1>"""L;
  nsMerging: NSString.String = Str[sMerging];
  mergeFileSuffix: NSString.String = Str[".IV"];
  mergeFileName: NSString.String ← NSString.MakeString[
    z: Heap.systemZone, bytes: PrintQueue.maxStringBytes + 10];
  BEGIN ENABLE 
    UNWIND => NSString.FreeString[z: Heap.systemZone, s: mergeFileName];
  IF state.trace # none THEN BEGIN
    stringArray: ARRAY [0..1) OF NSString.String;
    stringArray[0] ← state.qOH.fileName;
    PSAsyncMsg.ExpandArrayAndPutString[nsMerging, DESCRIPTOR[stringArray]];
    END;
  mergeFileName ← NSString.AppendString[to: mergeFileName, from: state.qOH.fileName];
  mergeFileName ← NSString.AppendString[to: mergeFileName, from: mergeFileSuffix];
  --Create merge file (merge file initially set to be
  --1 page long, merger redefines it to correct length for data):
  [state.mergeFileID, state.mergeFile] ← QueueFile.CreateTempFile[
    fName: mergeFileName, fBytes: Environment.bytesPerPage
      ! QueueFile.CreateError => IF retried THEN GOTO OutOfSpace
          ELSE
	    IF state.notifyProc # NIL THEN BEGIN
	      keepGoing: BOOLEAN ← state.notifyProc[
	        queueObject: state.qOH, code: noResources, continuable: TRUE];
	      IF keepGoing THEN {retried ← TRUE; RETRY}
	      ELSE GOTO OutOfSpace;
	    END ELSE GOTO OutOfSpace;
    ];
  state.exceptions ← 0;
  state.merging ← TRUE;
  state.qOH.currentStatus ← merging;
  state.startMergeTime  ← System.GetGreenwichMeanTime[];
  code ← FEPSMerge.MergeDoc[
    handle: state.mergeHandle, output: state.mergeFile,
    inputObjectH: state.qOH.printObjectHandle, 
    bannerOnly: state.qOH.bannerOnly, paperSize: state.qOH.paper];
  IF code # ok THEN ERROR;
  NSString.FreeString[z: Heap.systemZone, s: mergeFileName];
  EXITS OutOfSpace => BEGIN
    NSString.FreeString[z: Heap.systemZone, s: mergeFileName];
    Queue[status: mergeFailure];
    END;
  END; --enable clause and code
  END; --MergeDocument

Finished: ENTRY PROCEDURE [
  handle: FEPSMerge.Handle, code: FEPSMerge.FinishedCode] = --parameter to FEPSMerge.Initialize
  BEGIN
  IF code = bannerOnly AND NOT state.qOH.bannerOnly THEN BEGIN
    state.qOH.bannerOnly ← TRUE;
    state.qOH.priorStatus ← mergeFailure;
    --After the banner is forwarded this "priorStatus" will become the "currentStatus".
    END;
  Queue[status: IF code = noOutput THEN mergeFailure ELSE merged];
  END; --Finished
  
Exception: ENTRY FEPSMerge.ExceptionProc =
<<ExceptionProc: TYPE = PROCEDURE [
    handle: Handle, code: ExceptionCode, continuable: BOOLEAN]
    RETURNS [keepGoing: BOOLEAN]>>
  BEGIN
  --NOTE: Do we need a way to get error msgs and put them in queue object?
  --"Msg" parameter was removed in 10.0.
  IF state.trace # none THEN BEGIN
    sPage: LONG STRING ← [10];
    stringArray: ARRAY [0..5) OF NSString.String;
    atPage: CARDINAL ← FEPSMerge.PlatesMerged[state.mergeHandle];
    sPlates: LONG STRING ← [10];
    sMergeException: LONG STRING ← "$$Merge Exception: <1>File: ""<2>""; Plate: <3>; <4>Continuable"L;
    sNot: LONG STRING ← "NOT "L;
    nsMergeException: NSString.String ← Str[sMergeException];
    nsNot: NSString.String ← Str[sNot];
    stringArray[0] ← MergeCodeToString[code];
    stringArray[1] ← state.qOH.fileName;
    String.AppendDecimal[sPlates, atPage];
    stringArray[2] ← Str[sPlates];
    stringArray[3] ← IF NOT continuable THEN nsNot ELSE NSString.nullString;
    PSAsyncMsg.ExpandArrayAndPutString[nsMergeException, DESCRIPTOR[stringArray]];
    END;
  state.exceptions ← state.exceptions + 1;
  keepGoing ← IF code = noResources THEN 
      state.notifyProc[state.qOH, noResources, continuable]
    ELSE state.notifyProc[state.qOH, other, continuable];
  END; --Exception

Queue: INTERNAL PROCEDURE [status: PrintQueue.ObjectStatus] =
BEGIN
  startSinceEpoch: LONG CARDINAL;
  endSinceEpoch: LONG CARDINAL;
  IF state.qOH = PrintQueue.nilQueueObjectHandle THEN BEGIN
    --This could be true if Queue gets called twice (once by
    --MergeControlLoop and once from Finished) in case that doc
    --finishes decomposing while decomposition is being suspended.
    IF state.trace # none THEN
      PSAsyncMsg.PutMesaString["##Can't Queue nilQOH! "L];
    RETURN;
    END;
  startSinceEpoch ← System.SecondsSinceEpoch[state.startMergeTime];
  endSinceEpoch ← System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]; 
  IF endSinceEpoch >= startSinceEpoch THEN
    state.qOH.markTime ← endSinceEpoch - startSinceEpoch;
  IF state.trace # none THEN BEGIN
    sDoneMerging: LONG STRING ← "$$Done Merging  ""<1>""; status= <2>; Plates= <3>; seconds= <4>"L;
    sPlates: LONG STRING ← [10];
    sSeconds: LONG STRING ← [10];
    nsDoneMerging: NSString.String ← Str[sDoneMerging];
    stringArray: ARRAY [0..4) OF NSString.String;
    stringArray[0] ← state.qOH.fileName;
    stringArray[1] ← Str[SELECT status FROM
        restart => "restart"L,
        canceledInMerger => "canceledInMerger"L,
        mergeFailure => "mergeFailure"L,
	ENDCASE => "merged"L];
    String.AppendDecimal[sPlates, FEPSMerge.PlatesMerged[state.mergeHandle]];
    stringArray[2] ← Str[sPlates];
    String.AppendLongDecimal[sSeconds, state.qOH.markTime
      ! System.Overflow, String.Overflow, String.StringBoundsFault => CONTINUE];
    stringArray[3] ← Str[sSeconds];
    PSAsyncMsg.ExpandArrayAndPutString[nsDoneMerging, DESCRIPTOR[stringArray]]
    END;
  state.qOH.currentStatus ← status;
  FEPSMerge.FreeMergeJob[state.mergeHandle];
  
  SELECT TRUE FROM
    status = restart => BEGIN
      --Closing the temporary merge file will delete it...
      IF state.mergeFile # NSFile.nullHandle THEN
        NSFile.Close[state.mergeFile, session ! NSFile.Error => CONTINUE];
      --Free decompose handle (doc will be decomposed again)...
      IF state.qOH.printObjectHandle # NIL THEN BEGIN
        DecomposerControl.FreeDecomposeObject[state.qOH.uid];
	state.qOH.printObjectHandle ← NIL
	END;
      [] ← PrintQueue.Requeue[qOH: state.qOH, fromQueue: merging,
            toQueue: IF state.tpJob THEN aborted ELSE spooledNormal,
	    position: front];
      END;
    status = canceledInMerger => BEGIN
      --Closing the temporary merge file will delete it...
      IF state.mergeFile # NSFile.nullHandle THEN
        NSFile.Close[state.mergeFile, session ! NSFile.Error => CONTINUE];
      [] ← PrintQueue.Requeue[
        qOH: state.qOH, fromQueue: merging, toQueue: aborted]; 
      IF state.canceledWhileMerging # NIL THEN
        state.canceledWhileMerging[state.qOH.fileName, status];
      END;
    status # merged => BEGIN
      --Closing the temporary merge file will delete it...
      IF state.mergeFile # NSFile.nullHandle THEN
        NSFile.Close[state.mergeFile, session ! NSFile.Error => CONTINUE];
      [] ← PrintQueue.Requeue[qOH: state.qOH, fromQueue: merging, toQueue: aborted];
      END;
    ENDCASE => BEGIN
      IF state.qOH.printObjectHandle # NIL THEN BEGIN
        DecomposerControl.FreeDecomposeObject[state.qOH.uid];
	state.qOH.printObjectHandle ← NIL
	END;
      WITH q: state.qOH SELECT FROM
        feps9700 => q.ivFileID ← state.mergeFileID;
	ENDCASE => ERROR;
      --Delete the spooled file:
      IF NOT state.tpJob THEN
        state.qOH.fileID ← QueueFile.Delete[fileID: state.qOH.fileID];
      [] ← QueueFile.MakePermanent[state.mergeFile, NSFile.nullAttributeList];
      NSFile.Close[state.mergeFile, session];
      [] ← PrintQueue.Requeue[qOH: state.qOH, fromQueue: merging,
        toQueue: IF state.tpJob THEN tpMerged ELSE merged];
      END;
      
  state.mergeFile ← NSFile.nullHandle;
  state.mergeFileID ← NSFile.nullID;
  state.merging ← state.tpJob ← FALSE;
  state.qOH ← PrintQueue.nilQueueObjectHandle;
  control.jobQueueEntry ← NOT PrintQueue.Empty[decomposed];
  control.tpQueueEntry ← NOT PrintQueue.Empty[tpDecomposed];
  NOTIFY event; -- in case there's something to do
  END; --Queue
  
MergeCodeToString: INTERNAL PROCEDURE [code: FEPSMerge.ExceptionCode]
  RETURNS [NSString.String] =
  BEGIN
  RETURN [
    IF code = noResources THEN M[mDecompErrorNoResources]
    ELSE M[mDecompErrorUnknown]];
  END; --MergeCodeToString
    
  -- Proc for getting NSString from XMessage key
  M: PROCEDURE [key: ControlMessages.Key]
    RETURNS [string: NSString.String] = {
    r: XString.ReaderBody ← XMessage.Get[controlMsgs, ORD[key]];
    string ← [bytes: LOOPHOLE[r.bytes], length: r.limit, maxlength: r.limit]};
    
  Str: PROCEDURE [s: LONG STRING] RETURNS [ns: NSString.String] = INLINE {
    RETURN[NSString.StringFromMesaString[s]]};
  
END. --MergeControlImpl

LOG when/who/what
16-Oct-84 10:34:41 - Jacks - Created.
25-Oct-84 17:04:15 - Jacks - Added reference to bannerOnly in Finished.
16-Nov-84 12:32:26 - Jacks - Moved call to FreeMergeJob to beginning of Queue; updated to new FEPSMerge interface.
29-Nov-84 15:55:51 - Jacks - Added bannerOnly parm to MergeDoc call.
17-Dec-84 10:57:39 - Jacks - Removed ControlMessages from directory.
14-Jan-85 16:09:16 - Jacks - Make merged file permanent for TP jobs as well as normal jobs (in Queue).
17-Jun-85 16:14:37 - Jacks - Added copyright notice; updated to PS Euclid interfaces.
19-Jul-85 17:59:52 - Jacks - Updated to XMessage and QueueFile.
 6-Aug-85  9:08:56 - Jacks - Updated to 10.0 FEPSMerge interface and DecomposerControl.FreeDecomposeObject.
19-Sep-85 11:45:16 - Jacks - Rewrote Finished and some of Exception to handle bannerOnlys correctly; removed mergeFailure from state record.
 6-Nov-85 14:01:38 - Jacks - Get msg handle from PSKMessages.