-- File: DBFileAlpineImpl.mesa
-- Last edited by:
--   MBrown on June 7, 1983 4:40 pm
--   Kolling on May 12, 1983 3:35 pm
  DIRECTORY
    AlpineEnvironment
       USING[bytesPerPage, Conversation, OpenFileID, Outcome, PageCount,
          PageNumber, TransID, UniversalFile, wordsPerPage],
    AlpFile
       USING[GetSize, Handle, Open, ReadPages, SetSize, WritePages, WriteProperties],
    AlpineInterimDirectory
       USING[Error, Open],
    AlpInstance
       USING[AccessFailed, Create, Failed, Handle, Unknown],
    AlpTransaction
       USING[Create, Handle, Finish, OperationFailed],
    DBEnvironment
       USING[Aborted, Failure, Error],
    DBCommon
       USING[VersionOptions],
    DBFileAlpine,
    DBStats
       USING[Starting, Stopping],
    FileIO
       USING[CreateOptions],
    RPC
       USING[CallFailed],
    Rope
       USING[ROPE, Text];
DBFileAlpineImpl: PROGRAM
  IMPORTS AlpF: AlpFile, AlpineInterimDirectory, AlpI: AlpInstance, AlpT: AlpTransaction,
     DBEnvironment, DBStats, RPC
  EXPORTS DBFileAlpine
    
  = BEGIN OPEN AE: AlpineEnvironment;
  
  ROPE: TYPE = Rope.ROPE;
  VersionOptions: TYPE = DBCommon.VersionOptions;
  bytesPerPage: INT = AE.bytesPerPage;
  Conversation: TYPE = AE.Conversation;
  OpenFileID: TYPE = AE.OpenFileID;
  PageCount: TYPE = AE.PageCount;
  PageNumber: TYPE = AE.PageNumber;
  TransID: TYPE = AE.TransID;
  
  AlpineTrans: TYPE = REF ANY;
    -- must narrow to AlpTransaction.Handle
  AlpineOpenFileHandle: TYPE = REF ANY;
    -- must narrow to AlpFile.Handle
  CreateTransaction: PUBLIC PROC [server: ROPE] RETURNS [t: AlpineTrans] = {
    needRetry: BOOL ← FALSE;
    haveRetried: BOOL ← FALSE;
    transHandle: AlpT.Handle;
    DBStats.Starting[AlpineFileCreateTransaction];
    DO
      instance: AlpI.Handle ← AlpI.Create[fileStore: server ! AlpI.Failed =>
        ERROR DBEnvironment.Failure[IF why = authenticateFailed
          THEN $authentication ELSE $communication, server]
        ];
      transHandle ← AlpT.Create[instance !
        AlpT.OperationFailed => IF why = busy THEN ERROR DBEnvironment.Failure[
          $serverBusy, server];
        RPC.CallFailed =>
          IF why = unbound THEN needRetry ← TRUE
            -- a moderately likely failure, due to the instance cache
          ELSE IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
            $communication, server]
        ];
      IF NOT needRetry THEN EXIT;
      IF haveRetried THEN ERROR DBEnvironment.Failure[
        $communication, transHandle.inst.fileStore];
      needRetry ← FALSE;  haveRetried ← TRUE;
      ENDLOOP;
    DBStats.Stopping[AlpineFileCreateTransaction];
    RETURN[transHandle]
    };
  FinishTransaction: PUBLIC PROC [t: AlpineTrans, abort: BOOL, continue: BOOL] = {
    outcome: AE.Outcome;
    transHandle: AlpT.Handle = NARROW[t];
    DBStats.Starting[AlpineFileFinishTransaction];
    outcome ← transHandle.Finish[
      requestedOutcome: IF abort THEN abort ELSE commit,
      continue: continue ! RPC.CallFailed => IF why IN [timeout .. busy] THEN
        ERROR DBEnvironment.Failure[$communication, transHandle.inst.fileStore]
      ];
    IF NOT abort AND outcome = abort THEN ERROR DBEnvironment.Aborted[t];
    DBStats.Stopping[AlpineFileFinishTransaction];
    };
  CreateOptionsFromVersionOptions:
    ARRAY VersionOptions OF FileIO.CreateOptions =
      [NewFileOnly: newOnly, OldFileOnly: oldOnly, None: none];
  OpenFile: PUBLIC PROC [t: AlpineTrans, file: Rope.Text,
    version: VersionOptions, discardFileContents: BOOL, nPagesInitial: INT, noLog: BOOL]
    RETURNS [f: AlpineOpenFileHandle, createdFile: BOOL] = {
    ENABLE
      AlpI.Unknown => SELECT what FROM
        transID, openFileID => ERROR DBEnvironment.Aborted[t];
        ENDCASE => REJECT;
    transHandle: AlpT.Handle = NARROW[t];
    fileHandle: AlpF.Handle;
    refUniversalFile: REF AE.UniversalFile ← NIL;
    needRetry: BOOL ← FALSE;
    haveRetried: BOOL ← FALSE;
    DBStats.Starting[AlpineFileOpen];
    DO
    [, refUniversalFile, createdFile] ← AlpineInterimDirectory.Open[
      file, CreateOptionsFromVersionOptions[version], nPagesInitial*bytesPerPage !
      AlpineInterimDirectory.Error =>
        SELECT why FROM
          authenticateFailed => ERROR DBEnvironment.Failure[
            $authentication, transHandle.inst.fileStore];
          damaged, ownerRecordFull => REJECT;
          fileAlreadyExists => ERROR DBEnvironment.Error[AlreadyExists];
          fileNotFound, ownerNotFound => ERROR DBEnvironment.Error[FileNotFound];
          illegalFileName => ERROR DBEnvironment.Error[IllegalFileName];
          insufficientPermission => ERROR DBEnvironment.Failure[
            $accessDenied, transHandle.inst.fileStore];
          lockFailed, transAborted => needRetry ← TRUE;
          quota => ERROR DBEnvironment.Failure[
            $quotaExceeded, transHandle.inst.fileStore];
          remoteCallFailed, regServersUnavailable, serverNotFound =>
            ERROR DBEnvironment.Failure[$communication, transHandle.inst.fileStore];
          serverBusy => ERROR DBEnvironment.Failure[
            $serverBusy, transHandle.inst.fileStore];
          ENDCASE => REJECT]; -- DirectoryInconsistent {ownerRootFileNotFound}
      IF NOT needRetry THEN EXIT;
      IF haveRetried THEN ERROR DBEnvironment.Failure[
        $lockConflict, transHandle.inst.fileStore];
      needRetry ← FALSE;  haveRetried ← TRUE;
      ENDLOOP;
   { ENABLE BEGIN
       AlpI.Unknown => SELECT what FROM
         transID, openFileID => ERROR DBEnvironment.Aborted[t];
         ENDCASE => REJECT;
       RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
         $communications, transHandle.inst.fileStore];
       END;
     fileHandle ← AlpF.Open[transHandle, refUniversalFile^, $readWrite, [$write, $wait],
       IF noLog THEN $noLog ELSE $log, $random !
         AlpI.AccessFailed =>
           DBEnvironment.Failure[$accessDenied, transHandle.inst.fileStore]
       ].handle;
     IF NOT createdFile AND discardFileContents THEN
       fileHandle.WriteProperties[properties: LIST[[highWaterMark[highWaterMark: 0]]]];
     };
    DBStats.Stopping[AlpineFileOpen];
    RETURN [fileHandle, createdFile];
    };
  ReadFilePage: PUBLIC PROC [
    f: AlpineOpenFileHandle, p: CARDINAL, corePage: LONG POINTER] = {
    fileHandle: AlpF.Handle = NARROW[f];
    DBStats.Starting[AlpineFileReadPage];
    { ENABLE BEGIN
        AlpI.Unknown => SELECT what FROM
          transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
          ENDCASE => REJECT;
        RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
          $communications, fileHandle.trans.inst.fileStore];
        END;
      fileHandle.ReadPages[
        pageRun: [firstPage: p], pageBuffer: DESCRIPTOR [corePage, AE.wordsPerPage]];
      };
    DBStats.Stopping[AlpineFileReadPage];
    };
  WriteFilePage: PUBLIC PROC [
    f: AlpineOpenFileHandle, p: CARDINAL, corePage: LONG POINTER] = {
    fileHandle: AlpF.Handle = NARROW[f];
    DBStats.Starting[AlpineFileWritePage];
    { ENABLE BEGIN
        AlpI.Unknown => SELECT what FROM
          transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
          ENDCASE => REJECT;
        RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
          $communications, fileHandle.trans.inst.fileStore];
        END;
      fileHandle.WritePages[
        pageRun: [firstPage: p], pageBuffer: DESCRIPTOR [corePage, AE.wordsPerPage]];
      };
    DBStats.Stopping[AlpineFileWritePage];
    };
  GetSize: PUBLIC PROC [f: AlpineOpenFileHandle] RETURNS [nPages: CARDINAL] = {
    size: INT;
    fileHandle: AlpF.Handle = NARROW[f];
    DBStats.Starting[AlpineFileGetSize];
    { ENABLE BEGIN
        AlpI.Unknown => SELECT what FROM
          transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
          ENDCASE => REJECT;
        RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
          $communications, fileHandle.trans.inst.fileStore];
        END;
      size ← fileHandle.GetSize[];
      };
    DBStats.Stopping[AlpineFileGetSize];
    RETURN [size];
    };
  SetSize: PUBLIC PROC [f: AlpineOpenFileHandle, nPages: CARDINAL] = {
    fileHandle: AlpF.Handle = NARROW[f];
    DBStats.Starting[AlpineFileSetSize];
    { ENABLE BEGIN
        AlpI.Unknown => SELECT what FROM
          transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
          ENDCASE => REJECT;
        RPC.CallFailed => IF why IN [timeout .. busy] THEN ERROR DBEnvironment.Failure[
          $communications, fileHandle.trans.inst.fileStore];
        END;
      fileHandle.SetSize[size: nPages];
      fileHandle.WriteProperties[properties: LIST[[byteLength[byteLength: nPages*bytesPerPage]]]];
      };
    DBStats.Stopping[AlpineFileSetSize];
    };
  END.