-- FileCache.Mesa
-- Edited by:
--            Sandman on January 17, 1979  9:52 AM
--            Barbara on November 2, 1978  9:24 AM
--            Bruce on July 7, 1980  11:56 AM
--            Evans on Jun 26, 1980 1:54 PM

DIRECTORY
  AltoFileDefs USING [eofDA, FP, NullFP],
  DebugOps USING [],
  DirectoryDefs USING [DirectoryLookup, EnumerateDirectory],
  Inline USING [BITAND, BITOR, COPY],
  SegmentDefs USING [AccessOptions, Append, DefaultAccess, DefaultVersion, FileAccessError, FileHandle, FileNameError, FileObject, FindFile, InsertFile, InvalidFP, NewFileOnly, Object, OldFileOnly, OpenFile, Read, ReleaseFile, SetFileAccess, VersionOptions],
  Storage USING [CopyString, FreeString, Node],
  String USING [AppendString, EquivalentSubStrings, SubStringDescriptor],
  SwapperOps USING [AllocateObject];

FileCache: PROGRAM 
  IMPORTS DirectoryDefs, Inline, SegmentDefs, Storage, String, SwapperOps
  EXPORTS DebugOps
  SHARES SegmentDefs =
  BEGIN

  NullFP: AltoFileDefs.FP = AltoFileDefs.NullFP;
  FileHandle: TYPE = SegmentDefs.FileHandle;
  NullFileObject: SegmentDefs.FileObject = [
    busy: FALSE, body: file[open: FALSE, length: FALSE, lengthvalid: FALSE,
    read: FALSE, write: FALSE, append: FALSE, lengthchanged: FALSE, lock: 0,
    swapcount: 0, segcount: 0, fp: NullFP]];

  FCRecord: TYPE = RECORD [
    name: STRING,
    fp: AltoFileDefs.FP];

  FCSize: CARDINAL = 20;
  FCLimit: CARDINAL = FCSize-1;
  FCArray: DESCRIPTOR FOR ARRAY [0..FCSize) OF FCRecord;

  InitFileCache: PROCEDURE =
    BEGIN
    i: CARDINAL;
    FCArray ← DESCRIPTOR[Storage.Node[FCSize*SIZE[FCRecord]],FCSize];
    FOR i IN [0..FCSize) DO
      FCArray[i] ← FCRecord[NIL,NullFP];
      ENDLOOP;
    cacheInvalid ← FALSE;
    RETURN
    END;

  PromoteFCRecord: PROCEDURE [i: CARDINAL] =
    BEGIN
    ithFCR: FCRecord;
    IF i = FCLimit THEN RETURN;
    ithFCR ← FCArray[i];
    Inline.COPY[to: @FCArray[i], from: @FCArray[i+1], nwords: SIZE[FCRecord]*(FCLimit-i)];
    FCArray[FCLimit] ← ithFCR;
    RETURN
    END;

  CopyFileName: PROCEDURE [name: STRING] RETURNS [copy: STRING] =
    BEGIN
    IF name = NIL THEN RETURN[NIL];
    copy ← Storage.CopyString[name];
    RETURN
    END;

  AddFCRecord: PROCEDURE [name: STRING, fh: FileHandle] =
    BEGIN
    IF FCArray[0].name # NIL THEN
      Storage.FreeString[FCArray[0].name];
    Inline.COPY[to: @FCArray[0], from: @FCArray[1], nwords: SIZE[FCRecord]*(FCSize-1)];
    FCArray[FCLimit].name ← CopyFileName[name];
    IF fh # NIL THEN FCArray[FCLimit].fp ← fh.fp
    ELSE FCArray[FCLimit].fp ← NullFP;
    cacheInvalid ← TRUE;
    RETURN
    END;

  CacheNewFile: PUBLIC PROCEDURE [
    name: STRING, access: SegmentDefs.AccessOptions,
    version: SegmentDefs.VersionOptions ← SegmentDefs.DefaultVersion]
    RETURNS [file: FileHandle] =
    BEGIN OPEN AltoFileDefs;
    i: CARDINAL;
    create: BOOLEAN;
    [access,version] ← ValidateOptions[access,version];
    create ← Inline.BITAND[version,SegmentDefs.OldFileOnly]=0;
    file ← NIL;
    FOR i DECREASING IN [0..FCSize) DO
      IF FCArray[i].name = NIL THEN
	BEGIN AddFCRecord[name,NIL]; EXIT END;
      IF EquivalentFileNames[name, FCArray[i].name] THEN
	BEGIN PromoteFCRecord[i]; EXIT END;
      REPEAT FINISHED => AddFCRecord[name,NIL];
      ENDLOOP;
    IF FCArray[FCLimit].fp.leaderDA # eofDA AND ~cacheInvalid THEN
      BEGIN OPEN SegmentDefs;
      file ← InsertFile[@FCArray[FCLimit].fp, access];
      OpenFile[file ! InvalidFP => GOTO BadCache];
      RETURN;
      EXITS BadCache =>
	BEGIN
	IF file.segcount = 0 THEN ReleaseFile[file];
	cacheInvalid ← TRUE;
	END;
      END;
    ValidateCache[];
    SELECT TRUE FROM
      FCArray[FCLimit].fp.leaderDA # eofDA =>
	file ← SegmentDefs.InsertFile[@FCArray[FCLimit].fp, access];
      create => file ← RealNewFile[name,access];
      ENDCASE => ERROR SegmentDefs.FileNameError[name];
    RETURN
    END;

  FileName: PUBLIC PROCEDURE [name: STRING, file: FileHandle] =
    BEGIN
    localname: STRING ← [40];
    i: CARDINAL;
      BEGIN
      IF cacheInvalid THEN GO TO notincache
      ELSE FOR i DECREASING IN [0..FCSize) DO
	IF FCArray[i].name = NIL THEN GO TO notincache;
	IF FCArray[i].fp = file.fp THEN
	  BEGIN
	  String.AppendString[name,FCArray[i].name];
	  PromoteFCRecord[i];
	  RETURN
	  END;
	REPEAT FINISHED => GO TO notincache;
	ENDLOOP;
      EXITS notincache => AddFCRecord[NIL,file];
      END;
    ValidateCache[];
    IF FCArray[FCLimit].name = NIL THEN
      BEGIN
      FOR i DECREASING IN [1..FCSize) DO
	FCArray[i] ← FCArray[i-1];
	ENDLOOP;
      FCArray[0] ← [NIL,NullFP];
      SIGNAL SegmentDefs.InvalidFP[@file.fp]
      END
    ELSE String.AppendString[name,FCArray[FCLimit].name];
    RETURN
    END;

  EquivalentFileNames: PROCEDURE [n1, n2: STRING] RETURNS [BOOLEAN] =
    BEGIN
    s1,s2: String.SubStringDescriptor;
    s1 ← [base: n1, offset: 0,
      length: n1.length - (IF n1[n1.length-1] = '. THEN 1 ELSE 0)];
    s2 ← [base: n2, offset: 0,
      length: n2.length - (IF n2[n2.length-1] = '. THEN 1 ELSE 0)];
    RETURN[String.EquivalentSubStrings[@s1,@s2]]
    END;

  cacheInvalid: BOOLEAN;

  InvalidateFileCache: PUBLIC PROCEDURE = BEGIN cacheInvalid ← TRUE END;

  ValidateCache: PROCEDURE =
    BEGIN
    i: CARDINAL;
    num, found: CARDINAL ← 0;
    caseBit: WORD = 40B;
    CheckEntry: PROCEDURE [fp: POINTER TO AltoFileDefs.FP, dirname: STRING]
      RETURNS[BOOLEAN] =
      BEGIN
      fcr: POINTER TO FCRecord ← @FCArray[FCLimit];
      THROUGH [0..FCSize) DO
	IF fcr.name = NIL THEN
	  BEGIN
	  IF fcr.fp.leaderDA = AltoFileDefs.eofDA THEN EXIT;
	  IF fcr.fp = fp↑ THEN
	    {fcr.name ← CopyFileName[dirname]; found ← found+1};
	  END
	ELSE
	  IF Inline.BITOR[fcr.name[0],caseBit] = Inline.BITOR[dirname[0],caseBit]  AND EquivalentFileNames[fcr.name, dirname] 
	  THEN
	    {fcr.fp ← fp↑; found ← found+1};
	fcr ← LOOPHOLE[fcr - SIZE[FCRecord]];
	ENDLOOP;
      RETURN[found = num];
      END;
    IF ~cacheInvalid THEN RETURN;
    FOR i IN [0..FCSize) DO
      IF FCArray[i].name # NIL THEN {num ← num+1; FCArray[i].fp ← NullFP; LOOP};
      IF FCArray[i].fp.leaderDA # AltoFileDefs.eofDA THEN num ← num+1;
      ENDLOOP;
    DirectoryDefs.EnumerateDirectory[CheckEntry];
    cacheInvalid ← FALSE;
    END;

  RealNewFile: PROCEDURE [name: STRING, access: SegmentDefs.AccessOptions]
    RETURNS [file: FileHandle] =
    BEGIN
    fp: AltoFileDefs.FP;
    [] ← DirectoryDefs.DirectoryLookup[@fp,name,TRUE];
    IF (file ← SegmentDefs.FindFile[@fp]) = NIL THEN
      BEGIN
      file ← LOOPHOLE[
	SwapperOps.AllocateObject[SIZE[file SegmentDefs.Object]]];
      file↑ ← NullFileObject;
      file.fp ← fp;
      END;
    SegmentDefs.SetFileAccess[file,access];
    RETURN
    END;

  ValidateOptions: PROCEDURE [
      access: SegmentDefs.AccessOptions, version: SegmentDefs.VersionOptions]
    RETURNS [SegmentDefs.AccessOptions, SegmentDefs.VersionOptions] =
    BEGIN OPEN SegmentDefs, Inline;
    IF access = SegmentDefs.DefaultAccess THEN access ← SegmentDefs.Read;
    -- IF version = DefaultVersion THEN version ← 0;
    IF BITAND[version,NewFileOnly+OldFileOnly] = NewFileOnly+OldFileOnly
    OR (BITAND[version,NewFileOnly]#0 AND BITAND[access,Append]=0)
      THEN ERROR FileAccessError[NIL];
    IF BITAND[access,Append]=0 THEN
      version ← BITOR[version,OldFileOnly];
    RETURN[access,version]
    END;

  InitFileCache[];

  END...