-- File: AltoFileList.mesa
-- Last edited by Levin:  30-Apr-81 16:23:53

DIRECTORY
  AltoFile USING [FP],
  AltoFilePrivate USING [
    closedSeal, FileHandle, FileObject, openSeal, underwaySeal],
  DiskIODefs USING [DiskError, FID],
  VMStorage USING [shortTerm];

AltoFileList: MONITOR LOCKS lock↑
  IMPORTS DiskIODefs, VMStorage
  EXPORTS AltoFile, AltoFilePrivate =

  BEGIN OPEN AltoFilePrivate;

  -- Global Variables --

  fileList: FileHandle;

  lock: POINTER TO MONITORLOCK;
  LOCK: MONITORLOCK;
  
  ChangeInOpenState: CONDITION;

  -- Miscellaneous Declarations --

  DuplicateSerialNumbers: ERROR = CODE;
  FileInUse: ERROR = CODE;
  FileListSmashed: ERROR = CODE;
  FileNotInList: ERROR = CODE;
  IllegalDestroy: ERROR = CODE;
  InvalidFile: ERROR = CODE;


  -- Procedures and Signals Exported to AltoFilePrivate --

  InitializeFileList: PUBLIC PROCEDURE =
    {lock ← @LOCK; fileList ← NIL};

  FinalizeFileList: PUBLIC PROCEDURE =
    {IF fileList ~= NIL THEN ERROR FileInUse};

  InsertFile: PUBLIC PROCEDURE [
    fp: AltoFile.FP, openProc: PROCEDURE [FileHandle, BOOLEAN] RETURNS [BOOLEAN]]
    RETURNS [FileHandle] =
    BEGIN
    OpenState: TYPE = {newlyOpened, wasOpen};
    file: FileHandle;

    CheckOpenState: ENTRY PROCEDURE RETURNS [OpenState] = INLINE
      -- checks to see if the file identified by 'fp' has already been entered in the
      -- file list.  If not, it enters it and indicates that the open operation is
      -- underway.  If so, it waits for any previous open attempt to be resolved, then
      -- reports the outcome.
      BEGIN
      fID: DiskIODefs.FID = [version: 1, serial: fp.serial];
      file ← fileList;
      UNTIL file = NIL DO
	IF file.fileID = fID THEN
	  BEGIN
	  IF file.leadervDA ~= fp.leaderDA THEN ERROR DuplicateSerialNumbers;
	  SELECT file.seal FROM
	    openSeal => {file.openCount ← file.openCount + 1; RETURN[wasOpen]};
	    underwaySeal => {WAIT ChangeInOpenState; file ← fileList};
	    ENDCASE => GO TO BogusList
	  END
	ELSE -- ensure pointer validity
	  SELECT file.seal FROM
	    openSeal, underwaySeal => file ← file.link;
	    ENDCASE => GO TO BogusList;
	REPEAT BogusList => ERROR FileListSmashed;
	ENDLOOP;
      file ← VMStorage.shortTerm.NEW[FileObject ← FileObject[
	fileID: fID, leadervDA: fp.leaderDA, lengthKnown: FALSE, lastPage:, bytes:,
	lengthChanged:, openCount: 1, link: fileList, seal: underwaySeal, nRuns:,
	runTable:]];
      fileList ← file;
      RETURN[newlyOpened]
      END;

    AnnounceOutcome: ENTRY PROCEDURE [success: BOOLEAN] = INLINE
      BEGIN
      IF success THEN file.seal ← openSeal ELSE RemoveFile[file];
      BROADCAST ChangeInOpenState;
      IF ~success THEN RETURN WITH ERROR InvalidFP[fp];
      END;

    SELECT CheckOpenState[] FROM
      newlyOpened => AnnounceOutcome[openProc[file, TRUE]];
      wasOpen => [] ← openProc[file, FALSE];
      ENDCASE;
    RETURN[file]
    END;

  InvalidFP: PUBLIC ERROR [fp: AltoFile.FP] = CODE;

  ReleaseFile: PUBLIC PROCEDURE [file: FileHandle, closeProc: PROCEDURE [FileHandle]] =
    BEGIN
    IF LastReference[file] THEN
      BEGIN
      closeProc[file ! DiskIODefs.DiskError => CONTINUE];
      FlushFile[file];
      END;
    END;

  PurgeFile: PUBLIC PROCEDURE [
    file: FileHandle, destroyProc: PROCEDURE [FileHandle]] =
    BEGIN
    IF LastReference[file] THEN
      BEGIN
      destroyProc[file ! DiskIODefs.DiskError => CONTINUE];
      FlushFile[file];
      END
    ELSE ERROR IllegalDestroy;
    END;


  -- Internal Procedures --

  LastReference: ENTRY PROCEDURE [file: FileHandle] RETURNS [last: BOOLEAN] =
    BEGIN
    IF file.seal ~= openSeal OR file.openCount = 0 THEN
      RETURN WITH ERROR InvalidFile;
    IF (last ← file.openCount = 1) THEN file.seal ← underwaySeal
    ELSE file.openCount ← file.openCount - 1;
    RETURN[last]
    END;

  FlushFile: PROCEDURE [file: FileHandle] =
    BEGIN
    DoRemoveFile: ENTRY PROCEDURE = INLINE
      {RemoveFile[file]; BROADCAST ChangeInOpenState};

    DoRemoveFile[];
    VMStorage.shortTerm.FREE[@file];
    END;

  RemoveFile: INTERNAL PROCEDURE [file: FileHandle] =
    -- removes the file from the list.
    BEGIN
    IF file = fileList THEN fileList ← file.link
    ELSE
      BEGIN
      IF fileList = NIL THEN GO TO Trouble;
      FOR prev: FileHandle ← fileList, prev.link UNTIL prev.link = NIL DO
	IF prev.link = file THEN BEGIN prev.link ← file.link; EXIT END;
	REPEAT FINISHED => GO TO Trouble;
	ENDLOOP;
      EXITS Trouble => ERROR FileNotInList;
      END;
    file.seal ← closedSeal; -- try to catch dangling references
    END;

  END.