-- Transport Mechanism Filestore - heap object reader --

-- [Juniper]<Grapevine>MS>Reader.mesa

-- Mark Johnson 	10-Nov-81 16:59:41 --
-- Randy Gobbel		19-May-81 11:52:23 --
-- Andrew Birrell	24-Feb-81 16:25:59 --

DIRECTORY
  BodyDefs USING [ItemHeader, maxRNameLength, RName, RNameSize],
  HeapDefs USING [
    Buffer, HeapEndRead, HeapReadRName, HeapStartRead, ObjectOffset, objectStart],
  HeapFileDefs USING [NextPage],
  HeapXDefs USING [PageHeader, ObjectHeader, ReaderData],
  Inline USING [COPY, LowHalf],
  ObjectDirDefs USING [FreeObject, ObjectNumber, UseObject],
  ObjectDirXDefs USING [ObjectBase],
  ProtocolDefs USING [endSST, Failed, SendCount],
  PupStream USING [StreamClosing],
  Stream USING [Handle, PutBlock, SetSST],
  VMDefs USING [AllocatePage, Page, PageIndex, pageSize, ReadPage, Release];

Reader: MONITOR LOCKS from USING from: Handle
  IMPORTS
    BodyDefs, HeapDefs, HeapFileDefs, Inline, ObjectDirDefs, ObjectDirXDefs,
    ProtocolDefs, PupStream, Stream, VMDefs
  EXPORTS HeapDefs, HeapXDefs =
  BEGIN

  ReaderData: PUBLIC TYPE = HeapXDefs.ReaderData;
  Handle: TYPE = POINTER TO ReaderData;



  SubObjectMissing: ERROR = CODE;
  ReadingTooFar: ERROR = CODE;
  ObjectNotThere: SIGNAL = CODE;

  HeapReadData: PUBLIC ENTRY PROCEDURE [from: Handle, to: HeapDefs.Buffer]
    RETURNS [end: BOOLEAN, used: CARDINAL] =
    BEGIN  -- there is always a current page --
    WHILE from.stopped DO WAIT from.canStart ENDLOOP;
    IF from.end THEN ERROR ReadingTooFar[];
    end ← FALSE;
    used ← 0;
    DO
      BEGIN
      word: VMDefs.PageIndex ← from.where.word;
      givenOffset: HeapDefs.ObjectOffset;  --offset specified by disk data--
      objectHead: POINTER TO HeapXDefs.ObjectHeader;
      endOfSubObject: BOOLEAN ← FALSE;  --whether complete sub-object read--
      ignored: BOOLEAN ← FALSE;  --whether sub-object was ignored--

      -- check for offset given by page header --
      IF word = FIRST[VMDefs.PageIndex] THEN
        BEGIN
        pageHead: POINTER TO HeapXDefs.PageHeader =
          LOOPHOLE[from.page, POINTER] + word;
        word ← word + SIZE[HeapXDefs.PageHeader];
        givenOffset ← pageHead.offset;
        END
      ELSE givenOffset ← HeapDefs.objectStart;

      -- read object or sub-object header --
      objectHead ← LOOPHOLE[from.page, POINTER] + word;
      word ← word + SIZE[HeapXDefs.ObjectHeader];
      IF objectHead.number # from.object THEN  -- skip past gaps or intervening objects --
        BEGIN
        IF from.offset = HeapDefs.objectStart THEN SIGNAL ObjectNotThere[];
        ignored ← endOfSubObject ← TRUE;
        END

      ELSE
        BEGIN
        IF from.offset < givenOffset THEN ERROR SubObjectMissing[];
        BEGIN  -- read object data --
        relocation: CARDINAL = Inline.LowHalf[
          MIN[from.offset - givenOffset, objectHead.size]];
        available: CARDINAL = objectHead.size - relocation;
        amount: CARDINAL = MIN[to.length - used, available];
        Inline.COPY[from.page + word + relocation, amount, to.where + used];
        used ← used + amount;
        from.offset ← from.offset + amount;

        -- update disk position only if complete sub-object read --
        endOfSubObject ← amount >= available;
        END;
        END;

      -- deal with end of page or of object --
      -- Remember that an object never ends on a page boundary --
      IF endOfSubObject THEN
        BEGIN
        from.where.word ← word + objectHead.size;
        IF from.where.word + SIZE[HeapXDefs.ObjectHeader] > LAST[VMDefs.PageIndex]
          THEN
          BEGIN  -- move to sub-object on next page --
          VMDefs.Release[from.page];
          from.where ← HeapFileDefs.NextPage[from.where];
          from.page ← VMDefs.ReadPage[
            from.where.page, IF from.object.type = body THEN 1 ELSE 0
            --lookAhead-- ];
          END
        ELSE  -- ignorable sub-object or end of object --
          IF NOT ignored THEN
            BEGIN  --positioned after object--
            from.end ← end ← TRUE;
            EXIT
            END;
        END
      ELSE  -- check for buffer full --
        IF used = to.length THEN EXIT ELSE ERROR;

      END;
      ENDLOOP;
    END;

  BadString: ERROR [why: {tooLong, endOfObject}] = CODE;

  HeapReadString: PUBLIC PROCEDURE [from: Handle, s: STRING]
    RETURNS [ended: BOOLEAN] =
    BEGIN
    Read: PROCEDURE [base: POINTER, amount: CARDINAL] =
      BEGIN
      used: CARDINAL;
      [ended, used] ← HeapReadData[from, [base, amount]];
      IF used # amount THEN ERROR BadString[endOfObject];
      END;
    temp: STRING = [0];
    Read[temp, SIZE[StringBody [0]]];
    IF temp.length > s.maxlength THEN ERROR BadString[tooLong];
    s.length ← temp.length;
    IF s.length > 0 THEN
      BEGIN
      IF ended THEN ERROR BadString[endOfObject];
      Read[@(s.text), SIZE[StringBody [s.length]] - SIZE[StringBody [0]]];
      END;
    END;

  ReadItemHeader: PUBLIC PROCEDURE [from: Handle]
    RETURNS [header: BodyDefs.ItemHeader] =
    BEGIN [] ← HeapReadData[from, [@header, SIZE[BodyDefs.ItemHeader]]]; END;

  ReadRList: PUBLIC PROCEDURE [
    from: Handle, work: PROCEDURE [BodyDefs.RName] RETURNS [done: BOOLEAN]] =
    BEGIN
    length: CARDINAL ← LAST[CARDINAL];
    [, ] ← HeapReadData[from, [@length, SIZE[CARDINAL]]];
    WHILE length > 0 DO
      name: BodyDefs.RName = [BodyDefs.maxRNameLength];
      [] ← HeapDefs.HeapReadRName[from, name];
      length ← length - BodyDefs.RNameSize[name];
      IF work[name] THEN EXIT;
      ENDLOOP;
    END;

  SendComponent: PUBLIC PROCEDURE [from: Handle, str: Stream.Handle] =
    BEGIN
    length: CARDINAL ← LAST[CARDINAL];
    bLength: CARDINAL = 64;
    buffer: ARRAY [0..bLength) OF WORD;
    bufferAddr: LONG POINTER ← @buffer;
    [, ] ← HeapReadData[from, [@length, SIZE[CARDINAL]]];
    ProtocolDefs.SendCount[str, length];
    WHILE length > 0 DO
      used: CARDINAL = HeapReadData[from, [@buffer, MIN[length, bLength]]].used;
      length ← length - used;
      Stream.PutBlock[
        str, [LOOPHOLE[bufferAddr], 0, used * 2], FALSE !
        PupStream.StreamClosing => ERROR ProtocolDefs.Failed[communicationError]];
      ENDLOOP;
    END;

  SendObj: PUBLIC PROCEDURE [from: Handle, str: Stream.Handle] =
    BEGIN
    ended: BOOLEAN ← FALSE;
    used: CARDINAL;
    buffer: VMDefs.Page = VMDefs.AllocatePage[];
    bufferAddr: LONG POINTER ← buffer;
    BEGIN
    ENABLE
      UNWIND => BEGIN VMDefs.Release[buffer]; HeapDefs.HeapEndRead[from]; END;
    DO
      BEGIN
      [ended, used] ← HeapReadData[from, [buffer, VMDefs.pageSize]];
      Stream.PutBlock[
        str, [LOOPHOLE[bufferAddr], 0, used * 2], FALSE !
        PupStream.StreamClosing => ERROR ProtocolDefs.Failed[communicationError]];
      IF ended THEN EXIT;
      END
      ENDLOOP;
    END;
    VMDefs.Release[buffer];
    HeapDefs.HeapEndRead[from];
    Stream.SetSST[
      str, ProtocolDefs.endSST !
      PupStream.StreamClosing => ERROR ProtocolDefs.Failed[communicationError]];
    END;

  ReaderInfo: ENTRY PROCEDURE [from: Handle]
    RETURNS [ObjectDirDefs.ObjectNumber, HeapDefs.ObjectOffset] = INLINE
    BEGIN
    ObjectDirDefs.UseObject[from.object];
    RETURN[from.object, from.offset];
    END;

  CopyReader: PUBLIC PROCEDURE [from: Handle] RETURNS [new: Handle] =
    BEGIN
    -- Must not enter Reader monitor, since it calls ReaderAlloc --
    object: ObjectDirDefs.ObjectNumber;
    offset: HeapDefs.ObjectOffset;
    [object, offset] ← ReaderInfo[from];  -- snap-shot of reader --
    new ← HeapDefs.HeapStartRead[object];
    ObjectDirDefs.FreeObject[object];  -- to compensate for ReaderInfo --
    SetReaderOffset[new, offset];
    END;

  GetReaderOffset: PUBLIC ENTRY PROCEDURE [from: Handle]
    RETURNS [HeapDefs.ObjectOffset] = BEGIN RETURN[from.offset]; END;

  SetReaderOffset: PUBLIC ENTRY PROCEDURE [
    from: Handle, offset: HeapDefs.ObjectOffset] =
    BEGIN
    WHILE from.stopped DO WAIT from.canStart ENDLOOP;
    IF offset >= from.offset THEN from.offset ← offset  -- let reader skip by itself --
    ELSE {from.offset ← offset; Reset[from]};
    END;

  GetReaderObject: PUBLIC ENTRY PROCEDURE [from: Handle]
    RETURNS [ObjectDirDefs.ObjectNumber] = {RETURN[from.object]};

  Reset: INTERNAL PROCEDURE [from: Handle] =
    BEGIN
    -- re-calculate disk address for current position --
    VMDefs.Release[from.page];
    from.where ← ObjectDirXDefs.ObjectBase[from.object];
    from.end ← FALSE;
    BEGIN
    -- skip to approximate place efficiently --
    excess: HeapDefs.ObjectOffset ← from.offset;
    WHILE excess > LAST[VMDefs.PageIndex] DO
      from.where ← HeapFileDefs.NextPage[from.where];
      excess ← excess - LAST[VMDefs.PageIndex];
      ENDLOOP;
    END;
    from.page ← VMDefs.ReadPage[from.where.page, 0 --lookAhead-- ];
    -- reader will skip to correct place all by itself --
    END;

  StopReader: PUBLIC ENTRY PROCEDURE [from: Handle] =
    BEGIN from.stopped ← TRUE; END;

  RestartReader: PUBLIC ENTRY PROCEDURE [from: Handle] =
    BEGIN
    Reset[from];  --to re-calculate disk address--
    from.stopped ← FALSE;
    BROADCAST from.canStart;
    END;


  END.