-- File: MFileOnNSFileImpl.mesa  Edited by
-- Breisacher.es	31-Jan-86 15:04:17
-- Wagner.pa		23-May-86 11:57:30
-- Saxe.pa		19-May-86 17:53:41

-- Copyright (C) 1985, 1986 by Xerox Corporation. All rights reserved.

DIRECTORY
  Containee,
  Environment USING [Block, bytesPerPage],
  File,
  Heap,
  MFile,
  MFileOnNSFile,
  MFileOnNSFileOps,
  MFileOps,
  NSAssignedTypes,
  NSFile,
  NSSegment,
  NSSegmentInternal,
  NSString,
  SpecialDesktop,
  StarDesktop,
  String,
  SpecialMFile,
  System USING [gmtEpoch],
  Time USING [Packed],
  Volume USING [ID, InsufficientSpace, nullID],
  XString;

MFileOnNSFileImpl: MONITOR
  IMPORTS 
    Containee, File, Heap, MFileOps, NSFile, NSSegment, NSSegmentInternal, 
    NSString, SpecialDesktop, StarDesktop, String, Volume, XString
  EXPORTS MFile, MFileOnNSFile, MFileOnNSFileOps, MFileOps, SpecialMFile = 
  BEGIN OPEN MFile;

  Handle: TYPE = LONG POINTER TO Object;
  Object: PUBLIC TYPE = RECORD [
    ref: NSFile.Reference,
    nsfh: NSFile.Handle,
    length: ByteCount,
    lengthChanged: BOOLEAN,
    access: Access];
    
  zone: UNCOUNTED ZONE ← Heap.Create [initial: 1];
  
  NotImplemented: ERROR = CODE;
  
  << MFileOnNSFileOps EXPORTS >>
  
  NSRefFromMFHandle: PUBLIC PROCEDURE [mfh: Handle] 
    RETURNS [NSFile.Reference] = {RETURN [mfh.ref]};
  
  NSHandleFromMFHandle: PUBLIC PROCEDURE [mfh: Handle] 
    RETURNS [NSFile.Handle] = {RETURN [mfh.nsfh]}; 
  
  FirstPageAfterLeader: PUBLIC PROCEDURE [mfh: Handle] RETURNS [File.PageNumber] = {
    RETURN[NSSegmentInternal.GetID[NSHandleFromMFHandle[mfh]].firstPage] };
    
  << MFile EXPORTS >>
  
  Error: PUBLIC ERROR [file: MFile.Handle, code: ErrorCode] = CODE;

  NameForError: PUBLIC SIGNAL RETURNS [errorName: LONG STRING] = CODE;

  AppendErrorMessage: PUBLIC PROCEDURE [msg: LONG STRING, code: ErrorCode, file: Handle] = {};
 
  -- getting and releasing files
  
  Acquire: PUBLIC PROCEDURE [
    name: LONG STRING, access: Access, release: ReleaseData,  
    mightWrite: BOOLEAN ← FALSE, initialLength: InitialLength ← dontCare, 
    type: Type ← unknown]
    RETURNS [mfh: Handle ← NIL] = {
    SELECT access FROM 
      anchor, readOnly, delete, rename => {
        mfh ← GetFile[name, access];
	IF mfh = NIL THEN ERROR Error[NIL, noSuchFile] };
      writeOnly, readWrite, log => {
        mfh ← GetFile[name, access];
	IF mfh = NIL THEN mfh ← CreateFile[name, initialLength, access, type] };
      ENDCASE;
    };

  AcquireTemp: PUBLIC PROCEDURE [
    type: Type, initialLength: InitialLength ← dontCare,
    volume: Volume.ID ← Volume.nullID] RETURNS [mfh: Handle ← NIL] = {
    RETURN [CreateFile ["temp$"L, initialLength, readWrite, unknown, TRUE]];
    };

  Log: PUBLIC PROCEDURE [
    name: LONG STRING, release: ReleaseData, 
    initialLength: InitialLength ← dontCare] 
    RETURNS [Handle] = {ERROR NotImplemented};

  ReadOnly: PUBLIC PROCEDURE [name: LONG STRING, release: ReleaseData, 
    mightWrite: BOOLEAN ← FALSE] 
    RETURNS [mfh: Handle ← NIL] = {
    mfh ← GetFile[name, readOnly];
    IF mfh = NIL THEN ERROR Error[NIL, noSuchFile];
    };

  ReadWrite: PUBLIC PROCEDURE [
    name: LONG STRING, release: ReleaseData, type: Type, 
    initialLength: InitialLength ← dontCare] 
    RETURNS [mfh: Handle ← NIL] = {
    mfh ← GetFile[name, readWrite];
    IF mfh = NIL THEN mfh ← CreateFile [name, initialLength, readWrite, type];
    };

  WriteOnly: PUBLIC PROCEDURE [
    name: LONG STRING, release: ReleaseData, type: Type, 
    initialLength: InitialLength ← dontCare] 
    RETURNS [mfh: Handle ← NIL] = {
    mfh ← GetFile[name, writeOnly];
    IF mfh = NIL THEN mfh ← CreateFile [name, initialLength, writeOnly, type];
    };

  DeleteWhenReleased: PUBLIC PROCEDURE [file: Handle] = {ERROR NotImplemented};

  Delete: PUBLIC PROCEDURE [file: Handle] = {
    IF file.lengthChanged THEN ReallySetLength[file];
    NSFile.Delete [file.nsfh !NSFile.Error => Error[file, conflictingAccess]];
    IF StarDesktop.SelectReference[file.ref] THEN 
      SpecialDesktop.RemoveReferenceFromDesktop[file.ref];
    zone.FREE [@file];
    };

  Release: PUBLIC PROCEDURE [file: Handle] = {
    IF file.lengthChanged THEN ReallySetLength[file];
    NSFile.Close [file.nsfh];
    zone.FREE [@file];
    };
  
  CopyFileHandle: PUBLIC PROCEDURE [
    file: Handle, release: ReleaseData, access: Access ← null]
    RETURNS [mfh: Handle] = {
      fh: NSFile.Handle ← NSFile.OpenByReference[NSRefFromMFHandle[file]];
      mfh ← zone.NEW [Object ← [ref: NSRefFromMFHandle[file], nsfh: fh, 
        access: IF access = null THEN file.access ELSE access,
	lengthChanged: FALSE, length: file.length]];};

  SameFile: PUBLIC PROCEDURE [file1, file2: Handle] RETURNS [BOOLEAN] = {ERROR NotImplemented};

  GetAccess: PUBLIC PROCEDURE [file: Handle] RETURNS [access: Access] = {
    RETURN[file.access]};

  GetReleaseData: PUBLIC PROCEDURE [file: Handle] RETURNS [release: ReleaseData] = {ERROR NotImplemented};

  SetAccess: PUBLIC PROCEDURE [file: Handle, access: Access] = {
    file.access ← access};

  SetReleaseData: PUBLIC PROCEDURE [file: Handle, release: ReleaseData] = {};

  Copy: PUBLIC PROCEDURE [file: Handle, newName: LONG STRING] = {ERROR NotImplemented};

  CreateDirectory: PUBLIC PROCEDURE [dir: LONG STRING] = {ERROR NotImplemented};

  EnumerateDirectory: PUBLIC PROCEDURE [name: LONG STRING, proc: EnumerateProc, which: EnumerationType] = {ERROR NotImplemented};

  FreeSearchPath: PUBLIC PROCEDURE [SearchPath] = {ERROR NotImplemented};

  GetNextHandleForReading: PUBLIC PROCEDURE [
      filter, name: LONG STRING, release: ReleaseData, lastState: EnumerateState, stopNow: BOOLEAN ← FALSE] 
      RETURNS [file: Handle, state: EnumerateState] = {ERROR NotImplemented};
  -- make sure that you call in with stopNow = TRUE to release resources if quitting enumeration early

  GetSearchPath: PUBLIC PROCEDURE RETURNS [SearchPath] = {ERROR NotImplemented};
  
  GetFullName: PUBLIC PROCEDURE [file: Handle, name: LONG STRING] = {
    attrRec: NSFile.AttributesRecord;
    NSFile.GetAttributes [file.nsfh, [[name: TRUE]], @attrRec];
    NSString.AppendToMesaString [name, attrRec.name ! 
      String.StringBoundsFault => RESUME[NIL] ];
    NSFile.ClearAttributes [@attrRec];
  };

  Rename: PUBLIC PROCEDURE [file: Handle, newName: LONG STRING] = {
    attrib: ARRAY [0..1) OF NSFile.Attribute ← [ [name[nsname]] ];
    rb: XString.ReaderBody;
    data: Containee.Data;
    nsname: NSString.String ← NSString.StringFromMesaString[newName];
    attrib ← [ [name[nsname]] ];
    NSFile.ChangeAttributes[
      file: file.nsfh,
      attributes: DESCRIPTOR[attrib]
      ! NSFile.Error => {Error[file, insufficientAccess]}];
    data.reference ← file.ref;
    rb ← XString.FromSTRING[newName, TRUE];
    Containee.SetCachedName[@data, @rb];};

  SetSearchPath: PUBLIC PROCEDURE [SearchPath] RETURNS [succeeded: BOOLEAN ← TRUE] = {ERROR NotImplemented};

  SwapNames: PUBLIC PROCEDURE [f1, f2: Handle] = {
    attrRec1, attrRec2: NSFile.AttributesRecord;
    attrs1, attrs2: ARRAY [0..1) OF NSFile.Attribute;
    NSFile.GetAttributes [f1.nsfh, [[name: TRUE]], @attrRec1];
    NSFile.GetAttributes [f2.nsfh, [[name: TRUE]], @attrRec2];
    attrs1[0] ← [name[attrRec1.name]];
    attrs2[0] ← [name[attrRec2.name]];
    NSFile.ChangeAttributes [f1.nsfh, DESCRIPTOR[attrs2]];
    NSFile.ChangeAttributes [f2.nsfh, DESCRIPTOR[attrs1]];
    NSFile.ClearAttributes [@attrRec1];
    NSFile.ClearAttributes [@attrRec2];
    };
  
  ValidFilename: PUBLIC PROCEDURE [name: LONG STRING] RETURNS [ok: BOOLEAN] = {ERROR NotImplemented};

  CompleteFilename: PUBLIC PROCEDURE [name, addedPart: LONG STRING]
    RETURNS [exactMatch: BOOLEAN, matches: CARDINAL] = {ERROR NotImplemented};

  ComputeFileType: PUBLIC PROCEDURE [file: Handle] RETURNS [type: Type] = {ERROR NotImplemented};
    
  AddNotifyProc: PUBLIC PROCEDURE [
    proc: NotifyProc, filter: Filter, clientInstanceData: LONG POINTER] = {};

  RemoveNotifyProc: PUBLIC PROCEDURE [
    proc: NotifyProc, filter: Filter, clientInstanceData: LONG POINTER] = {};

  -- file properties
  
  PropertyError: PUBLIC ERROR [code: PropertyErrorCode] = CODE;
  
  GetVolume: PUBLIC PROCEDURE [file: Handle] RETURNS [Volume.ID] = {ERROR NotImplemented};

  GetDirectoryName: PUBLIC PROCEDURE [file: Handle, name: LONG STRING] = {ERROR NotImplemented};

  GetProperties: PUBLIC PROCEDURE [file: Handle, name: LONG STRING ← NIL]
    RETURNS [
      create, write, read: Time.Packed,
      length: ByteCount, type: Type, 
      deleteProtected, writeProtected, readProtected: BOOLEAN] = {
    attrs: NSFile.AttributesRecord;
    NSFile.GetAttributes [NSHandleFromMFHandle [file], [[name: TRUE, createdOn: TRUE, modifiedOn: TRUE, readOn: TRUE]], @attrs];
    IF name # NIL THEN NSString.AppendToMesaString [name, attrs.name];
    NSFile.ClearAttributes [@attrs];
    RETURN [
      create: attrs.createdOn, 
      write: attrs.modifiedOn, 
      read: attrs.readOn,
      length: file.length,
      type: unknown,
      deleteProtected: FALSE, 
      writeProtected: FALSE, 
      readProtected: FALSE];
    };

  GetCreateDate: PUBLIC PROCEDURE [file: Handle] RETURNS [create: Time.Packed] = {RETURN[GetTimes[file].create]};

  GetTimes: PUBLIC PROCEDURE [file: Handle] RETURNS [create, write, read: Time.Packed] = {
    attrs: NSFile.AttributesRecord;
    NSFile.GetAttributes [NSHandleFromMFHandle [file], [[createdOn: TRUE, modifiedOn: TRUE, readOn: TRUE]], @attrs];
    RETURN [
      create: attrs.createdOn, 
      write: attrs.modifiedOn, 
      read: attrs.readOn];
    };

  GetType: PUBLIC PROCEDURE [file: Handle] RETURNS [type: Type] = {ERROR NotImplemented};

  GetLength: PUBLIC PROCEDURE [file: Handle] RETURNS [ByteCount] = {
    RETURN[file.length];
    };

  GetProtection: PUBLIC PROCEDURE [file: Handle] 
    RETURNS [deleteProtected, writeProtected, readProtected: BOOLEAN] = {ERROR NotImplemented};

  SetProperties: PUBLIC PROCEDURE [
      file: Handle, create, write, read: Time.Packed ← System.gmtEpoch,
      length: ByteCount, type: Type, 
      deleteProtected, writeProtected, readProtected: BOOLEAN ← FALSE] = {ERROR NotImplemented};

  SetTimes: PUBLIC PROCEDURE [file: Handle, create, read, write: Time.Packed ← System.gmtEpoch] = {};

  SetType: PUBLIC PROCEDURE [file: Handle, type: Type] = {ERROR NotImplemented};

  SetLength: PUBLIC PROCEDURE [file: Handle, length: ByteCount] = {
    SetMinimumFileDataPages[file, MFileOps.PageForBytes[length]];
    file.lengthChanged ← TRUE;
    file.length ← length};

  SetDeleteProtect: PUBLIC PROCEDURE [file: Handle, deleteProtected: BOOLEAN] = {ERROR NotImplemented};

  SetWriteProtect: PUBLIC PROCEDURE [file: Handle, writeProtected: BOOLEAN] = {ERROR NotImplemented};

  SetReadProtect: PUBLIC PROCEDURE [file: Handle, readProtected: BOOLEAN] = {};

  SetProtection: PUBLIC PROCEDURE [
    file: Handle, deleteProtected, writeProtected, readProtected: BOOLEAN ← FALSE] = {ERROR NotImplemented};
  
  AddProperty: PUBLIC PROCEDURE [file: Handle, property: Property, maxLength: CARDINAL] = {ERROR NotImplemented};

  CopyProperties: PUBLIC PROCEDURE [from, to: Handle] = {ERROR NotImplemented};

  GetProperty: PUBLIC PROCEDURE [file: Handle, property: Property, block: Environment.Block]
    RETURNS [length: CARDINAL] = {ERROR NotImplemented};

  RemoveProperty: PUBLIC PROCEDURE [file: Handle, property: Property] = {ERROR NotImplemented};

  RemoveProperties: PUBLIC PROCEDURE [file: Handle] = {ERROR NotImplemented};

  SetProperty: PUBLIC PROCEDURE [file: Handle, property: Property, block: Environment.Block] = {ERROR NotImplemented};

  InitializeFileSystem: PUBLIC PROCEDURE = {};
  
  << SpecialMFile >>
  
  AcquireID: PUBLIC PROCEDURE[
    id: File.File, access: MFile.Access, release: MFile.ReleaseData]
    RETURNS [MFile.Handle] = {ERROR NotImplemented};

  EscapeMatch: PUBLIC PROCEDURE [string, pattern: LONG STRING, pos: CARDINAL]
    RETURNS [correspondingingPos: CARDINAL] = {ERROR NotImplemented};

  GetCapaWithAccess: PUBLIC PROCEDURE [file: Handle] RETURNS [File.File] = {RETURN [NSSegmentInternal.GetID [file.nsfh].pilotFile]};

  LeaderPages: PUBLIC PROCEDURE RETURNS [CARDINAL] = {
    RETURN[MFileOps.leaderPages]};

  RegisterWithSupervisor: PUBLIC PROCEDURE = {ERROR NotImplemented};

  << MFileOps >>

  SetMinimumFileDataPages: PUBLIC PROCEDURE [
    file: Handle, pages: File.PageCount] = {
    f: File.File = GetCapaWithAccess[file];
    IF File.GetSize[f] < pages + MFileOps.leaderPages THEN
      SetFileDataSize[f, pages !
	Volume.InsufficientSpace => Error[file, noRoomOnVolume]] };
  
  SetFileDataSize: PUBLIC PROCEDURE [id: File.File, dataPages: File.PageCount] = {
    pages: File.PageCount ← MFileOps.IncrementCeiling[dataPages];
    File.SetSize[file: id, size: pages]};

  << MFile on NSFile impls >>
  
  defaultInitialLength: InitialLength = 512; -- from MPM
  
  CreateFile: PROCEDURE [name: LONG STRING, initialLength: InitialLength, access: Access,
    type: Type ← unknown, temp: BOOLEAN ← FALSE] 
    RETURNS [mfh: Handle] = {
    << Creates an NSFile on the desktop >>
    nssName: NSString.String ← NSString.StringFromMesaString [name];
    attrs: ARRAY [0..4) OF NSFile.Attribute ← [
      [name[nssName]],
      [sizeInBytes[IF initialLength = dontCare THEN defaultInitialLength ELSE initialLength]],
      [isTemporary[temp]],
      [type[SELECT type FROM
        text => NSAssignedTypes.tText,
	ENDCASE => NSAssignedTypes.tUnspecified]]];
    fh: NSFile.Handle;
    ref: NSFile.Reference;
    
    FirstDirectory: MFileOnNSFile.EachElementProc = {
      dfh: NSFile.Handle ← NSFile.OpenByReference [element];
      fh ← NSFile.Create[
	directory: dfh,
	attributes: DESCRIPTOR[attrs]
	! NSFile.Error => CONTINUE];
      NSFile.Close [dfh];
      IF fh # NSFile.nullHandle AND element = StarDesktop.GetCurrentDesktopFile[] THEN {
        ref ← NSFile.GetReference [fh];
        StarDesktop.AddReferenceToDesktop [ref] };
      RETURN[done: TRUE];
      };

    EnumeratePath [FirstDirectory];
    ref ← NSFile.GetReference [fh];
    mfh ← zone.NEW [Object ← [ref: ref, nsfh: fh, access: access, 
      lengthChanged: FALSE, 
      length: IF initialLength = dontCare THEN defaultInitialLength 
        ELSE initialLength]];
    };
  
  GetFile: PROCEDURE [name: LONG STRING, access: Access] RETURNS [mfh: Handle ← NIL] = {
    nssName: NSString.String ← NSString.StringFromMesaString [name];
    attrs: NSFile.AttributesRecord;
    
    EachDirectory: MFileOnNSFile.EachElementProc = {
      fh: NSFile.Handle ← NSFile.nullHandle;
      dfh: NSFile.Handle ← NSFile.OpenByReference [element];

      fh ← NSFile.OpenByName[directory: dfh, path: nssName 
        ! NSFile.Error => {CONTINUE} ];
      IF fh # NSFile.nullHandle THEN {
        mfh ← zone.NEW [Object ← [
	  ref: TRASH,
	  nsfh: fh,
	  access: access,
	  lengthChanged: FALSE,
	  length: NSSegment.GetSizeInBytes[fh]]];
        NSFile.GetAttributes[fh, [[fileID: TRUE, service: TRUE]], @attrs];
	mfh.ref ← [attrs.fileID, attrs.service]  };
      NSFile.Close [dfh];
      RETURN [done: mfh#NIL];
      };
    
    EnumeratePath [EachDirectory];
    };
  
  ReallySetLength: PROCEDURE [file: Handle] = {
    NSSegment.SetSizeInBytes[file.nsfh, file.length] };
    
    
  << Search path stuff - EXPORTed to MFileOnNSFile >>
  
  Path: TYPE = LONG POINTER TO PathSeq;
  
  PathSeq: TYPE = RECORD [SEQUENCE l: CARDINAL OF NSFile.Reference];
  
  BadPath: PUBLIC ERROR = CODE;
  
  searchPath: Path ← NIL;
  
  SetPath: PUBLIC ENTRY PROCEDURE [
    path: LONG DESCRIPTOR FOR ARRAY OF NSFile.Reference] = {
    ENABLE UNWIND => NULL;
    CheckPath[path];
    zone.FREE [@searchPath];
    searchPath ← zone.NEW [PathSeq [path.LENGTH]];
    FOR i: CARDINAL IN [0..path.LENGTH) DO
      searchPath[i] ← path[i];
      ENDLOOP;
    };
  
  EnumeratePath: PUBLIC ENTRY PROCEDURE [
    proc: MFileOnNSFile.EachElementProc] = {
    ENABLE UNWIND => NULL;
    IF searchPath = NIL THEN RETURN;
    FOR i: CARDINAL IN [0..searchPath.l) DO
      IF proc[searchPath[i]] THEN EXIT;
      ENDLOOP;
    };
    
  CheckPath: PROCEDURE[
    path: LONG DESCRIPTOR FOR ARRAY OF NSFile.Reference] = {
    -- Checks that all files are directories.
    attributes: NSFile.AttributesRecord;
    FOR i:CARDINAL IN [0..path.LENGTH) DO
      file: NSFile.Handle;
      file ← NSFile.OpenByReference[path[i] !NSFile.Error => ERROR BadPath];
      NSFile.GetAttributes[file, [[isDirectory: TRUE]], @attributes];
      IF ~attributes.isDirectory THEN ERROR BadPath;
      ENDLOOP;
  };
  
  END.

LOG
NFS	19-May-86 17:56:47	Added check to setting search path that all files are directories.