-- File: IFSFileControl.mesa
-- Last edited by Levin:   1-Jul-81 16:43:27

DIRECTORY
  FileDefs USING [Operations, OperationsRecord],
  FrameDefs USING [IsBound],
  IFSFilePrivate USING [
    Abandon, Close, CopyString, Destroy, Extend, FinalizeFreeList, FSInstance, FSObject,
    GetLength, GetTimes, InitializeFreeList, Open, SetCreationTime, SetLength,
    StartRead, StartWrite, Truncate],
  Leaf USING [
    Answer, AnswerObject, LeafOp, leafSocket, paramsOp, ptLeaf, Request, RequestObject],
  LogDefs USING [DisplayNumber],
  PupDefs USING [
    GetPupAddress, PupAddress, PupNameTrouble, PupPackageDestroy, PupPackageMake],
  Sequin USING [
    Broken, Buffer, Create, Destroy, Get, GetEmptyBuffer, Handle, Put,
    ReleaseBuffer, SetZone],
  Storage USING [StringLength],
    StringDefs USING [WordsForString],
  VMDefs USING [Problem, UnableToLogin],
  VMStorage USING [longTerm, shortTerm];

IFSFileControl: MONITOR
  IMPORTS
    FrameDefs, IFSFilePrivate, LogDefs, PupDefs, Sequin, Storage, StringDefs,
    VMDefs, VMStorage
  EXPORTS FileDefs, IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;


  -- Global Variables --

  loginCount: CARDINAL;
  
  ifsOps: FileDefs.Operations;

  oldZone: MDSZone;
  

  -- Miscellaneous --

  someWordsForFilename: CARDINAL = 15;

  CantCommunicateByTelepathy: ERROR = CODE;
  FilesInUse: ERROR = CODE;
  InsufficientLogouts: ERROR = CODE;
  PupBuffersTooSmall: ERROR = CODE;
  ServerNameMissing: ERROR = CODE;
  TooManyLogouts: ERROR = CODE;


  -- Types Exported to FileDefs --

  -- Unfortunately, the compiler doesn't support this just yet
  -- FSInstance: PUBLIC TYPE = IFSFilePrivate.FSInstance;

  -- Procedures Exported to FileDefs --

  InitializeIFS: PUBLIC PROCEDURE RETURNS [ops: FileDefs.Operations] =
    BEGIN
    IF (loggingEnabled ← FrameDefs.IsBound[LogDefs.DisplayNumber]) THEN
      {ifsFiles ← 0; LogDefs.DisplayNumber["Open IFS Files"L, [short[@ifsFiles]]]};
    loginCount ← 0;
    InitializeFreeList[];
    -- the LOOPHOLEs below get around non-support for multiple implementors of an
    -- exported type.
    ifsOps ← VMStorage.longTerm.NEW[FileDefs.OperationsRecord ← [
      login: LOOPHOLE[Login],
      logout: LOOPHOLE[Logout],
      checkpoint: LOOPHOLE[Checkpoint],
      abort: LOOPHOLE[Abort],
      open: LOOPHOLE[Open],
      close: LOOPHOLE[Close],
      abandon: LOOPHOLE[Abandon],
      destroy: LOOPHOLE[Destroy],
      getLength: LOOPHOLE[GetLength],
      setLength: LOOPHOLE[SetLength],
      extend: LOOPHOLE[Extend],
      truncate: LOOPHOLE[Truncate],
      startRead: LOOPHOLE[StartRead],
      startWrite: LOOPHOLE[StartWrite],
      getTimes: LOOPHOLE[GetTimes],
      setCreation: LOOPHOLE[SetCreationTime]]];
    oldZone ← Sequin.SetZone[VMStorage.shortTerm];
    RETURN[ifsOps]
    END;

  FinalizeIFS: PUBLIC PROCEDURE =
    BEGIN
    IF loginCount ~= 0 THEN ERROR InsufficientLogouts;
    VMStorage.longTerm.FREE[@ifsOps];
    FinalizeFreeList[];
    [] ← Sequin.SetZone[oldZone];
    END;


  -- Variables Exported to IFSFilePrivate --

  loggingEnabled: PUBLIC BOOLEAN;

  ifsFiles: PUBLIC CARDINAL;


  -- Internal Procedures --

  Login: PROCEDURE [
    server, userName, password, secondaryName, secondaryPassword: STRING ← NIL]
    RETURNS [fs: FSInstance] =
    BEGIN
    serverAddr: PupDefs.PupAddress ← [net: , host: , socket: Leaf.leafSocket];

    TryForConnection: PROCEDURE =
      BEGIN
      LeafStringWords: PROCEDURE [s: STRING] RETURNS [CARDINAL] =
	{RETURN[StringDefs.WordsForString[Storage.StringLength[s]] - 1]};
      sequin: Sequin.Handle;
      buffer: Sequin.Buffer ← Sequin.GetEmptyBuffer[];
      adequate: CARDINAL =
	2*(MAX[SIZE[Leaf.RequestObject], SIZE[Leaf.AnswerObject]] + 1 +
          LeafStringWords[userName] + LeafStringWords[password] +
          LeafStringWords[secondaryName] + LeafStringWords[secondaryPassword] +
	  someWordsForFilename);
      problem: VMDefs.Problem;
      IF buffer.maxBytes < adequate THEN ERROR PupBuffersTooSmall;
      sequin ← Sequin.Create[dest: serverAddr, pupType: Leaf.ptLeaf];
      LOOPHOLE[buffer.data, Leaf.Request]↑ ←
	[Leaf.paramsOp, params[packetDataBytes: buffer.maxBytes,
			  fileLockTimeout: 2*60/5, connectionTimeout: 10*60/5]];
      buffer.nBytes ← Leaf.paramsOp.length;
      BEGIN
      ENABLE Sequin.Broken => {problem ← io; GO TO serverDead};
      answerOp: Leaf.LeafOp;
      Sequin.Put[sequin, buffer];
      buffer ← Sequin.Get[sequin];
      answerOp ← LOOPHOLE[buffer.data, Leaf.Answer].op;
      Sequin.ReleaseBuffer[buffer];
      IF answerOp.type ~= params OR answerOp.sense ~= reply THEN
	{problem ← other; GO TO serverDead};
      EXITS
        serverDead => {Sequin.Destroy[sequin]; ERROR VMDefs.UnableToLogin[problem]};
      END;
      Sequin.Destroy[sequin];
      END;

    NoteLogin: ENTRY PROCEDURE = INLINE {loginCount ← loginCount + 1};

    IF server= NIL OR server.length = 0 THEN ERROR ServerNameMissing;
    IF FrameDefs.IsBound[PupDefs.PupPackageMake] THEN PupDefs.PupPackageMake[]
    ELSE ERROR CantCommunicateByTelepathy;
    PupDefs.GetPupAddress[@serverAddr, server
      ! PupDefs.PupNameTrouble =>
	ERROR VMDefs.UnableToLogin[
	  SELECT code FROM noRoute, noResponse => io, ENDCASE => other]];
    TryForConnection[];
    fs ← VMStorage.shortTerm.NEW[FSObject ← [
	    primaryName: CopyString[userName], primaryPassword: CopyString[password],
	    secondaryName: CopyString[secondaryName],
	    secondaryPassword: CopyString[secondaryPassword],
	    serverAddr: serverAddr]];
    NoteLogin[];
    END;

  Logout: PROCEDURE [fs: FSInstance] =
    BEGIN

    NoteLogout: ENTRY PROCEDURE = INLINE {loginCount ← loginCount - 1};

    IF loginCount = 0 THEN ERROR TooManyLogouts;
    IF fs.fileList ~= NIL THEN ERROR FilesInUse;
    IF fs.primaryName ~= NIL THEN VMStorage.shortTerm.FREE[@fs.primaryName];
    IF fs.primaryPassword ~= NIL THEN VMStorage.shortTerm.FREE[@fs.primaryPassword];
    IF fs.secondaryName ~= NIL THEN VMStorage.shortTerm.FREE[@fs.secondaryName];
    IF fs.secondaryPassword ~= NIL THEN VMStorage.shortTerm.FREE[@fs.secondaryPassword];
    VMStorage.shortTerm.FREE[@fs];
    PupDefs.PupPackageDestroy[];
    NoteLogout[];
    END;

  Checkpoint: PROCEDURE [fs: FSInstance] = {NULL};

  Abort: PROCEDURE [fs: FSInstance] = {NULL};


  END.