-- File: DBFileAlpineImpl.mesa
-- Contents: Alpine implementation of file level of Cypress
-- Last edited by:
-- MBrown on June 7, 1983 4:40 pm
-- Kolling on May 12, 1983 3:35 pm
-- Cattell, July 28, 1983 1:45 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, CreateOptions],
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],
RPC
USING[CallFailed],
Rope
USING[ROPE, Text];

DBFileAlpineImpl: PROGRAM
IMPORTS AlpFile, AlpineInterimDirectory, AlpInstance, 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: BOOLFALSE;
haveRetried: BOOLFALSE;
transHandle: AlpTransaction.Handle;
DBStats.Starting[AlpineFileCreateTransaction];
DO
instance: AlpInstance.Handle ← AlpInstance.Create[fileStore: server ! AlpInstance.Failed =>
IF why = authenticateFailed THEN ERROR DBEnvironment.Error[BadUserPassword]
ELSE ERROR DBEnvironment.Failure[$communication, server]
];
transHandle ← AlpTransaction.Create[instance !
AlpTransaction.OperationFailed => IF why = busy THEN ERROR DBEnvironment.Failure[
$serverBusy, server];
RPC.CallFailed => TRUSTED {
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: AlpTransaction.Handle = NARROW[t];
DBStats.Starting[AlpineFileFinishTransaction];
outcome ← transHandle.Finish[
requestedOutcome: IF abort THEN abort ELSE commit,
continue: continue ! RPC.CallFailed => TRUSTED {
IF why IN [timeout .. busy] THEN
IF abort THEN {outcome← abort; CONTINUE} -- so can escape when no communication!
ELSE 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 AlpineInterimDirectory.CreateOptions =
[NewFileOnly: newOnly, OldFileOnly: oldOnly, None: none];

OpenFile: PUBLIC PROC [t: AlpineTrans, file: Rope.Text,
version: VersionOptions, discardFileContents: BOOL, nPagesInitial: INT,
readOnly: BOOL, noLog: BOOL]
RETURNS [f: AlpineOpenFileHandle, createdFile: BOOL] = {
ENABLE
AlpInstance.Unknown => SELECT what FROM
transID, openFileID => ERROR DBEnvironment.Aborted[t];
ENDCASE => REJECT;
transHandle: AlpTransaction.Handle = NARROW[t];
fileHandle: AlpFile.Handle;
refUniversalFile: REF AE.UniversalFile ← NIL;
needRetry: BOOLFALSE;
haveRetried: BOOLFALSE;
DBStats.Starting[AlpineFileOpen];
DO
[, refUniversalFile, createdFile] ← AlpineInterimDirectory.Open[
file, CreateOptionsFromVersionOptions[version], nPagesInitial*bytesPerPage !
AlpineInterimDirectory.Error =>
SELECT why FROM
authenticateFailed => ERROR DBEnvironment.Error[BadUserPassword];
damaged, ownerRecordFull => REJECT;
fileAlreadyExists => ERROR DBEnvironment.Error[AlreadyExists];
fileNotFound, ownerNotFound => ERROR DBEnvironment.Error[FileNotFound];
illegalFileName => ERROR DBEnvironment.Error[IllegalFileName];
insufficientPermission => ERROR DBEnvironment.Error[ProtectionViolation];
lockFailed, transAborted => needRetry ← TRUE;
quota => ERROR DBEnvironment.Error[QuotaExceeded];
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;
TRUSTED { ENABLE BEGIN
AlpInstance.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 ← AlpFile.Open[
transHandle: transHandle,
universalFile: refUniversalFile^,
access: IF readOnly THEN readOnly ELSE readWrite,
lock: [$write, $wait],
recoveryOption: IF noLog THEN $noLog ELSE $log,
referencePattern: $random
! AlpInstance.AccessFailed => DBEnvironment.Error[ProtectionViolation]
].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: AlpFile.Handle = NARROW[f];
DBStats.Starting[AlpineFileReadPage];
{ ENABLE BEGIN
AlpInstance.Unknown => SELECT what FROM
transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
ENDCASE => REJECT;
RPC.CallFailed => TRUSTED {
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: AlpFile.Handle = NARROW[f];
DBStats.Starting[AlpineFileWritePage];
{ ENABLE BEGIN
AlpInstance.Unknown => SELECT what FROM
transID, openFileID => TRUSTED {ERROR DBEnvironment.Aborted[fileHandle.trans]};
ENDCASE => REJECT;
RPC.CallFailed => TRUSTED {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: AlpFile.Handle = NARROW[f];
DBStats.Starting[AlpineFileGetSize];
{ ENABLE BEGIN
AlpInstance.Unknown => SELECT what FROM
transID, openFileID => ERROR DBEnvironment.Aborted[fileHandle.trans];
ENDCASE => REJECT;
RPC.CallFailed => TRUSTED { 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: AlpFile.Handle = NARROW[f];
DBStats.Starting[AlpineFileSetSize];
{ ENABLE BEGIN
AlpInstance.Unknown => SELECT what FROM
transID, openFileID => TRUSTED {
ERROR
DBEnvironment.Aborted[fileHandle.trans]};
ENDCASE => REJECT;
RPC.CallFailed => TRUSTED { 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.