-- StreamsC.Mesa  Edited by Sandman on July 1, 1980  8:38 AM
-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  AltoDefs USING [BytesPerPage],
  AltoFileDefs USING [eofDA, fillinDA, vDA],
  DiskDefs USING [
    CB, CBZ, CBptr, CBZptr, DDC, DoDiskCommand, GetCB, InitializeCBstorage,
    RetryableDiskError, VirtualDA],
  InlineDefs USING [COPY],
  NucleusOps USING [],
  SegmentDefs USING [FileHandle, GetFileLength, UpdateFileLength],
  StreamDefs USING [
    CleanupDiskStream, DiskHandle, GetIndex, SetIndex, StreamError, StreamHandle,
    StreamIndex],
  StreamScan USING [BDHandle, lBD, lSSD, restart, Handle, Descriptor],
  Storage USING [Node, Free];

StreamsC: PROGRAM
  IMPORTS DiskDefs, InlineDefs, SegmentDefs, Storage, StreamDefs
  EXPORTS NucleusOps, StreamScan
  SHARES DiskDefs, StreamDefs, StreamScan =
  BEGIN OPEN StreamDefs, StreamScan;

  CBptr: TYPE = DiskDefs.CBptr;
  CBZptr: TYPE = DiskDefs.CBZptr;

  pSSD: TYPE = POINTER TO StreamScan.Descriptor;

  Init: PUBLIC PROCEDURE [
    stream: StreamHandle, bufTable: POINTER TO ARRAY OF POINTER, nBufs: CARDINAL]
    RETURNS [Handle] =
    -- Creates a Scan Stream Descriptor (SSD) in preparation for scanning
    -- the file corresponding to the stream ks.  bufTable is a table of pointers
    -- to page-size buffers, and nBufs is the number of buffers (there must
    -- be at least one).
    -- The SSD is allocated from the zone from which ks was allocated.
    -- Subsequent calls to GetBuffer return pointers to buffers
    -- containing successive pages of the file, starting with the page at which
    -- the stream was positioned initially.
    -- No other operations on the stream should be performed while the scan
    -- is in progress (i.e., before Finish is called).
    -- Note: the da, pageNumber, and numChars cells in the SSD are public
    -- and refer to the page most recently returned by GetBuffer.
    BEGIN
    ssd: pSSD;
    WITH ks: stream SELECT FROM
      Disk =>
	BEGIN
	bd: BDHandle;
	i: CARDINAL;
	index: StreamIndex ← GetIndex[@ks];
	lenCBZ: CARDINAL =
	  SIZE[DiskDefs.CBZ] +
	    (SIZE[DiskDefs.CB] + SIZE[DiskDefs.CBptr])*(nBufs + 2);
	index.byte ← 0;
	SetIndex[@ks, index];
	CleanupDiskStream[@ks];
	ssd ← Storage.Node[lSSD + lenCBZ + (nBufs + 1)*lBD];
	ssd.ks ← @ks;
	SegmentDefs.GetFileLength[ks.file, @ssd.hintLastFA];
	ssd.cbz ← LOOPHOLE[ssd, POINTER] + lSSD;
	ssd.nCB ← nBufs + 2;
	ssd.nextPage ← restart;
	ssd.bd ← bd ← LOOPHOLE[ssd.cbz, POINTER] + lenCBZ;
	FOR i IN [0..nBufs) DO
	  bd↑ ← [nextBD: bd + lBD, buffer: bufTable[i], full: FALSE];
	  bd ← bd.nextBD;
	  ENDLOOP;
	bd↑ ← [nextBD: ssd.bd, buffer: ks.buffer.word, full: TRUE];
	END;
      ENDCASE => ERROR StreamError[stream, StreamType];
    RETURN[ssd]
    END;

  GetBuffer: PUBLIC PROCEDURE [ssd: Handle] RETURNS [POINTER] =
    -- Returns a pointer to a buffer containing the next page of the file
    -- being scanned, or zero if end-of-file has been reached.
    -- This pointer remains valid only until the next call on GetBuffer.
    BEGIN
    pssd: pSSD = LOOPHOLE[ssd];
    cbz: CBZptr = pssd.cbz;
    ks: DiskHandle = pssd.ks;
    nextDA: AltoFileDefs.vDA ← AltoFileDefs.fillinDA;
    pssd.bd.full ← FALSE;
    IF pssd.nextPage = restart THEN
      BEGIN
      DiskDefs.InitializeCBstorage[cbz, pssd.nCB, pssd.ks.page + 1, clear];
      cbz.cleanup ← ScanCleanupCb;
      cbz.info ← pssd;
      pssd.nextPage ← cbz.currentPage;
      nextDA ← ks.das[next];
      pssd.thisCB ← DiskDefs.GetCB[cbz, clear];
      pssd.nextCB ← DiskDefs.GetCB[cbz, clear];
      END; -- The current BD is empty and is ready to have a new command initiated
    -- for it.  The next BD is either empty (because no command for it has
    -- ever been issued) or is in the process of being read into and has not
    -- yet been cleaned up.  In the latter case, the call to GetCB at the
    -- bottom of the loop will cause it to be cleaned up and marked full.
    -- This depends on the number of CBs being equal to the number of BDs plus one
    -- (the +1 is because we always keep one CB in our pocket).
    UNTIL pssd.bd.full DO
      IF ks.das[next] = AltoFileDefs.eofDA THEN
	BEGIN
	IF nextDA # AltoFileDefs.eofDA THEN
	  BEGIN
	  pssd.hintLastFA ← [da: ks.das[current], page: ks.page, byte: ks.char];
	  SegmentDefs.UpdateFileLength[ks.file, @pssd.hintLastFA];
	  RETURN[NIL]
	  END;
	-- The following can happen only if the stream was already positioned
	-- at end-of-file when the SSD was initialized.  Simply advance to
	-- the BD with the stream buffer, without queueing new commands.
	pssd.bd ← pssd.bd.nextBD;
	LOOP
	END; -- Stop queueing commands if we have reached the alleged end-of-file
      IF pssd.nextPage # pssd.hintLastFA.page + 1 THEN
	BEGIN -- Queue new command using empty BD and chaining to nextCB
	ddc: DiskDefs.DDC ←
	  [cb: pssd.thisCB, ca: pssd.bd.buffer, da: nextDA, page: pssd.nextPage,
	    fp: @ks.file.fp, restore: FALSE, action: ReadD];
	pssd.thisCB.labelAddress ← LOOPHOLE[@pssd.nextCB.header.diskAddress];
	DiskDefs.DoDiskCommand[@ddc];
	nextDA ← AltoFileDefs.fillinDA;
	pssd.nextPage ← pssd.nextPage + 1;
	END;
      -- Advance BD, and get another CB to which the next command will be chained
      pssd.bd ← pssd.bd.nextBD;
      pssd.thisCB ← pssd.nextCB;
      pssd.nextCB ← DiskDefs.GetCB[
	cbz, clear !
	DiskDefs.RetryableDiskError =>
	  BEGIN
	  pssd.nextPage ← cbz.currentPage;
	  nextDA ← ks.das[next];
	  pssd.thisCB ← DiskDefs.GetCB[cbz, clear];
	  pssd.nextCB ← DiskDefs.GetCB[cbz, clear];
	  LOOP
	  END];
      ENDLOOP;
    -- If this is the page claimed by the length hint to be the last page
    -- of the file, and it isn't, then invalidate the hint and force the
    -- next call on GetBuffer to restart the transfer.
    IF ks.page = pssd.hintLastFA.page AND ks.das[next] # AltoFileDefs.eofDA THEN
      BEGIN pssd.hintLastFA.page ← 0; pssd.nextPage ← restart END;
    pssd.da ← ks.das[current];
    pssd.pageNumber ← ks.page;
    pssd.numChars ← ks.char;
    RETURN[pssd.bd.buffer]
    END;

  ScanCleanupCb: PROCEDURE [cb: CBptr] =
    BEGIN
    ssd: Handle = cb.zone.info;
    ks: DiskHandle = ssd.ks;
    ssd.bd.full ← TRUE;
    ks.das[last] ← ks.das[current];
    ks.das[current] ← ks.das[next];
    ks.das[next] ← DiskDefs.VirtualDA[cb.labelAddress.next];
    ks.page ← ks.page + 1;
    ks.char ← cb.zone.currentBytes;
    END;

  Noop: PROCEDURE [CBptr] = BEGIN END;

  Finish: PUBLIC PROCEDURE [ssd: Handle] =
    BEGIN OPEN DiskDefs;
    -- Wait 'til all queued transfers have completed, but don't clean them up
    -- and don't bother to retry any that suffered errors.
    ks: DiskHandle = ssd.ks;
    cbz: CBZptr = ssd.cbz;
    cbz.cleanup ← Noop;
    UNTIL cbz.cbQueue[cbz.qHead] = NIL DO
      [] ← GetCB[cbz, dontClear ! RetryableDiskError => CONTINUE]; ENDLOOP;
    -- Put the contents of the current page into the stream buffer,
    -- if it's not already there.
    IF ks.buffer.word # ssd.bd.buffer THEN
      InlineDefs.COPY[
	to: ks.buffer.word, from: ssd.bd.buffer, nwords: AltoDefs.BytesPerPage/2];
    Storage.Free[LOOPHOLE[ssd]];
    END;


  END..