-- File: IFSFileImplA.mesa
-- Last edited by:
-- Levin - 13-Oct-81  9:09:48

DIRECTORY
  Heap USING [systemMDSZone, systemZone],
  IFSFile USING [Problem],
  IFSFilePrivate USING [
    connectionTimeout, CopyString, fileLockTimeout, FinalizeFreeList, FSInstance,
    FSObject, InitializeFreeList],
  Leaf USING [
    Answer, AnswerObject, LeafOp, leafSocket, paramsOp, ptLeaf, Request, RequestObject],
  PupDefs USING [
    GetPupAddress, PupAddress, PupNameTrouble, PupPackageDestroy, PupPackageMake],
  Sequin USING [
    Broken, Buffer, Create, Destroy, Get, GetEmptyBuffer, Handle, Put,
    ReleaseBuffer];

IFSFileImplA: MONITOR
  IMPORTS Heap, IFSFilePrivate, PupDefs, Sequin
  EXPORTS IFSFile, IFSFilePrivate =

  BEGIN OPEN IFSFilePrivate;


  -- Global Variables --

  loginCount: CARDINAL;
  

  -- Miscellaneous --

  someWordsForFilename: CARDINAL = 15;

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


  -- Procedures and Types Exported to IFSFile --

  FSObject: PUBLIC TYPE = IFSFilePrivate.FSObject;

  Initialize: PUBLIC PROCEDURE =
    BEGIN
    zone ← Heap.systemZone;
    mdsZone ← Heap.systemMDSZone;
    loginCount ← 0;
    InitializeFreeList[];
    END;

  Finalize: PUBLIC PROCEDURE =
    BEGIN
    IF loginCount ~= 0 THEN ERROR InsufficientLogouts;
    FinalizeFreeList[];
    END;

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

    TryForConnection: PROCEDURE RETURNS [sequin: Sequin.Handle] =
      BEGIN
      LeafStringWords: PROCEDURE [s: LONG STRING] RETURNS [CARDINAL] =
	{RETURN[((IF s = NIL THEN 0 ELSE s.length)+1)/2+1]};
      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: IFSFile.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: fileLockTimeout/5,
	 	connectionTimeout: connectionTimeout/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 UnableToLogin[problem]};
      END;
      END;

    GetPupAddress: PROCEDURE =
      BEGIN
      serverName: STRING ← mdsZone.NEW[StringBody[server.length]];
      FOR i: CARDINAL IN [0..server.length) DO
        serverName[i] ← server[i];
	ENDLOOP;
      serverName.length ← server.length;
      PupDefs.GetPupAddress[@serverAddr, serverName
        ! PupDefs.PupNameTrouble =>
	  BEGIN
	  mdsZone.FREE[@serverName];
	  ERROR UnableToLogin[SELECT code FROM noRoute, noResponse => io, ENDCASE => other];
	  END];
      mdsZone.FREE[@serverName];
      END;

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

    IF server = NIL OR server.length = 0 THEN ERROR ServerNameMissing;
    PupDefs.PupPackageMake[];
    GetPupAddress[];
    sequin ← TryForConnection[];
    fs ← zone.NEW[FSObject ← [
	    primaryName: CopyString[userName], primaryPassword: CopyString[password],
	    secondaryName: CopyString[secondaryName],
	    secondaryPassword: CopyString[secondaryPassword],
	    serverAddr: serverAddr,
	    cachedSequin: sequin, haveSequin: TRUE]];
    NoteLogin[];
    END;

  UnableToLogin: PUBLIC ERROR [reason: IFSFile.Problem] = CODE;

  Logout: PUBLIC 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 zone.FREE[@fs.primaryName];
    IF fs.primaryPassword ~= NIL THEN zone.FREE[@fs.primaryPassword];
    IF fs.secondaryName ~= NIL THEN zone.FREE[@fs.secondaryName];
    IF fs.secondaryPassword ~= NIL THEN zone.FREE[@fs.secondaryPassword];
    IF fs.haveSequin THEN Sequin.Destroy[fs.cachedSequin];
    zone.FREE[@fs];
    PupDefs.PupPackageDestroy[];
    NoteLogout[];
    END;

  -- Procedures and Variables Exported to IFSFilePrivate --

  zone: PUBLIC UNCOUNTED ZONE;

  mdsZone: PUBLIC MDSZone;

  GetSequinForFS: PUBLIC ENTRY PROCEDURE [fs: FSInstance]
    RETURNS [sequin: Sequin.Handle, fromCache: BOOLEAN] =
    BEGIN
    IF (fromCache ← fs.haveSequin) THEN
      {fs.haveSequin ← FALSE; sequin ← fs.cachedSequin}
    ELSE sequin ← Sequin.Create[dest: fs.serverAddr, pupType: Leaf.ptLeaf];
    END;

  FreeSequinForFS: PUBLIC ENTRY PROCEDURE [
    fs: FSInstance, sequin: Sequin.Handle, toCache: BOOLEAN ← TRUE] =
    BEGIN
    -- we cache the newest one to minimize the chance of subsequent timeout
    IF ~toCache THEN {Sequin.Destroy[sequin]; RETURN};
    IF fs.haveSequin THEN Sequin.Destroy[fs.cachedSequin];
    fs.cachedSequin ← sequin; fs.haveSequin ← TRUE;
    END;

  END.