-- Transport Mechanism Filestore - heap object reader --

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

-- 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;
   [,] ← 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,[@buffer, 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[];
   BEGIN
      ENABLE UNWIND =>
         BEGIN VMDefs.Release[buffer]; HeapDefs.HeapEndRead[from]; END;
      DO BEGIN
         [ended,used] ← HeapReadData[from,[buffer,VMDefs.pageSize]];
         Stream.PutBlock[str,[buffer, 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.