-- VultureOpsImpl.mesa
-- Scavanger for Mail Archive files.
-- Maurice Herlihy August 17, 1982

DIRECTORY
 BodyDefs,
IO,
 Rope,
 Space,
 VMDefs,
 VultureDefs,
 VultureOps;

VultureOpsImpl: PROGRAM
IMPORTS IO, Space
EXPORTS VultureOps
 = {
-- Public types.
 Item: PUBLIC TYPE = LONG POINTER TO BodyDefs.ItemHeader;

-- Opaque types
 DirStream: PUBLIC TYPE = REF DirStreamRep;
 DirStreamRep: PUBLIC TYPE = RECORD[
  dirPage: LONG POINTER TO UNSPECIFIED, -- first directory page
  pages: INT, -- number of pages
  next: INT-- index of next directory
  ];

 ItemStream: PUBLIC TYPE = REF ItemStreamRep;
 ItemStreamRep: PUBLIC TYPE = RECORD[
  start: LONG POINTER TO UNSPECIFIED, -- start of region
  size: INT, -- size of region
  next: INT-- offset of next item
  ];

ROPE: TYPE = Rope.ROPE;
 postMarkSize: INT = 6; -- Suggested by empirical observation.
  
 EndOfStream: PUBLIC ERROR = CODE;
 BadFormat: PUBLIC ERROR = CODE;
OpenDirStream: PUBLIC PROC [space: Space.Handle] RETURNS [dirStream: DirStream]
-- Signals EndOfStream --
 ={
 first: INT ← FindFirstMessage[space -- resignal EndOfStream --];
 dirStream ← NEW[
  DirStreamRep ← [
   dirPage: Space.LongPointer[space],
   pages: first / VMDefs.pageSize,
   next: 0
   ]
  ];
 };

NextDirectory: PUBLIC PROC [dirStream: DirStream] RETURNS [directory: VultureDefs.Directory]
-- Signals EndOfStream --
 = {
 pageIndex: INT ← dirStream.next / VultureDefs.entriesPerPage;
 entryIndex: INT ← dirStream.next MOD VultureDefs.entriesPerPage;
 dirPage: VultureDefs.DirPage ← LOOPHOLE[dirStream.dirPage + pageIndex*VMDefs.pageSize];
IF pageIndex >= dirStream.pages THEN ERROR EndOfStream;
 dirStream.next ← dirStream.next+1;
 directory ← @dirPage.entries[entryIndex];
 };
CloseDirStream: PUBLIC PROC [dirStream: DirStream] = {
 directory: VultureDefs.Directory;
 maxIndex: INT;
 index: INT;

IF dirStream = NIL THEN RETURN; -- File is very sick.
 maxIndex ← dirStream.pages * VultureDefs.entriesPerPage - 1;
FOR index IN [dirStream.next .. maxIndex] DO
  directory ← NextDirectory[dirStream];
  directory.bodyStart ← 0;
  directory.bodyLength ← 0;
ENDLOOP;
};
FindFirstMessage: PROC [space: Space.Handle] RETURNS [first: INT]
-- Signals EndOfStream --
 ={
 itemStream: ItemStream;
-- read offset of first message from directory.
 dirPage: VultureDefs.DirPage ← LOOPHOLE[Space.LongPointer[space]];
 first ← dirPage.entries[0].bodyStart;
-- if this points to a page boundary, believe it.
IF first > 0 AND first MOD VMDefs.pageSize = 0 THEN RETURN;
-- otherwise try a linear scan from second page.
 itemStream ← NEW[ItemStreamRep ← [
   start: LOOPHOLE[Space.LongPointer[space]],
   size: LONG[Space.GetAttributes[space].size]*LONG[VMDefs.pageSize],
   next: 0
   ]
  ];
 first ← FindPostmark[itemStream -- resignal EndOfStream --];
 };
OpenItemStream: PUBLIC PROC [space: Space.Handle] RETURNS [stream: ItemStream]
-- signals EndOfStream --
 ={
 stream ← NEW[
  ItemStreamRep ← [
   start: LOOPHOLE[Space.LongPointer[space]],
   size: LONG[Space.GetAttributes[space].size]*LONG[VMDefs.pageSize],
   next: FindFirstMessage[space -- Resignal EndOfStream --]
   ]
  ];
 };
NextItem: PUBLIC PROC [stream: ItemStream] RETURNS [item: Item] ={
IF stream.next + SIZE[BodyDefs.ItemHeader] >= stream.size
  THEN ERROR EndOfStream;
 item ← LOOPHOLE[stream.start+stream.next];
IF ~IsItem[item] THEN ERROR BadFormat;
 stream.next ← (item.length+1)/2 + SIZE[BodyDefs.ItemHeader] + stream.next;
-- check size for overflow
IF stream.next > stream.size THEN {
  stream.next ← stream.size;
  ERROR BadFormat;
  }
 };
Current: PUBLIC PROC [stream: ItemStream] RETURNS [INT] ={
RETURN [stream.next];
 };
Reset: PUBLIC PROC [stream: ItemStream, address: INT] = {
IF address > stream.size THEN ERROR EndOfStream;
 stream.next ← address;
 };
FindPostmark: PUBLIC PROC [stream: ItemStream] RETURNS [INT] = {
 stream.next ← stream.next+1;
WHILE stream.next < stream.size - SIZE[BodyDefs.ItemHeader] DO
  item: Item ← LOOPHOLE[stream.start+stream.next];
  IF item.type = BodyDefs.ItemType[PostMark]
   AND item.length = postMarkSize THEN RETURN [stream.next];
  stream.next ← stream.next + 1;
  ENDLOOP;
ERROR EndOfStream;
 };
NoMoreItems: PUBLIC PROC [stream: ItemStream] RETURNS [BOOL] ={
RETURN[stream.next >= stream.size]
 }; 
ParseItem: PUBLIC PROC [item: Item] RETURNS [ROPE] = {
 format: ROPE;
SELECT item.type FROM
  BodyDefs.ItemType[PostMark] => format ← "PostMark(%g)";
  BodyDefs.ItemType[Sender] => format ← "Sender(%g)";
  BodyDefs.ItemType[ReturnTo] => format ← "Return To(%g)";
  BodyDefs.ItemType[Recipients] => format ← "Recipients(%g)";
  BodyDefs.ItemType[Text] => format ← "Text(%g)";
  BodyDefs.ItemType[Capability] => format ← "Capability(%g)";
  BodyDefs.ItemType[Audio] => format ← "Audio(%g)";
  BodyDefs.ItemType[updateItem] => format ← "updateItem(%g)";
  BodyDefs.ItemType[reMail] => format ← "reMail(%g)";
  BodyDefs.ItemType[LastItem] => format ← "Last Item(%g)";
  ENDCASE => format ← "Unknown Item!(%g)";
RETURN [IO.PutFR[format, IO.int[item.length]]];
  };
IsItem: PROC [item: Item] RETURNS [BOOL] = {
SELECT item.type FROM
  BodyDefs.ItemType[PostMark] => RETURN[TRUE];
  BodyDefs.ItemType[Sender] => RETURN[TRUE];
  BodyDefs.ItemType[ReturnTo] => RETURN[TRUE];
  BodyDefs.ItemType[Recipients] => RETURN[TRUE];
  BodyDefs.ItemType[Text] => RETURN[TRUE];
  BodyDefs.ItemType[Capability] => RETURN[TRUE];
  BodyDefs.ItemType[Audio] => RETURN[TRUE];
  BodyDefs.ItemType[updateItem] => RETURN[TRUE];
  BodyDefs.ItemType[reMail] => RETURN[TRUE];
  BodyDefs.ItemType[LastItem] => RETURN[TRUE];
  ENDCASE => RETURN[FALSE];
  };
 }.