-- File: AlpineCmds.mesa
-- Last edited by:
-- MBrown on March 16, 1983 5:19 pm
-- Kolling on April 27, 1983 2:51 pm

-- Procs that might usefully be called from the interpreter.
-- Procs for use by any Alpine user:
-- Copy: PROC [to: ROPE, from: ROPE] ("to" and "from" may Alpine or local files).
-- Delete: PROC [file: ROPE] ("file" must be an Alpine file)
-- List: PROC [pattern: ROPE] RETURNS [LIST OF REF ANY] (* allowed in "pattern")
-- ReadQuota: PROC [directory: ROPE] RETURNS [pageLimit, spaceInUse: PageCount]
-- Procs for use by administrators:
-- CreateOwner: PROC [directory: ROPE, pageLimit: PageCount]
-- WriteQuota: PROC [directory: ROPE, pageLimit: PageCount]
-- ListOwners: PROC [fileStore: ROPE] RETURNS [LIST OF REF ANY]
-- ReorganizeOwnerDB: PROC [fileStore: ROPE, nEntries: NAT]


DIRECTORY
AlpFile
USING[Handle, Open, ReadPages, ReadProperties, SetSize, WritePages, WriteProperties],
AlpineEnvironment
USING[ByteCount, bytesPerPage, FileID, FileStore, nullVolumeGroupID,
OwnerPropertyValuePair, Outcome, PageCount, PropertyValuePair, TransID, VolumeID,
wordsPerPage],
AlpineInterimDirectory
USING[Delete, EnumerateDirectory, OpenUnderTrans],
AlpInstance
USING[Create, Handle],
AlpTransaction
USING[AssertAlpineWheel, Create, CreateOwner, CreateWithTransID, Finish,
GetNextVolumeGroup, Handle, ReadNextOwner, ReadOwnerProperties,
ReorganizeOwnerDB, WriteOwnerProperties],
Directory
USING[GetProperty, PutProperty],
Environment
USING[Comparison],
File
USING[Capability, SetSize],
FileIO
USING[CapabilityFromStream, CreateOptions, minimumStreamBufferParms, Open],
IO
USING[Close, PutFR, STREAM, text],
List
USING[Cons, Sort],
PropertyTypes
USING[tByteLength, tCreateDate],
RefText
USING[TrustTextAsRope],
Rope
USING[Compare, Equal, Fetch, Find, IsEmpty, Match, ROPE, SkipTo, Substr],
Space
USING[CopyIn, CopyOut, Create, Delete, Handle, LongPointer, Map, Unmap,
virtualMemory],
System
USING[GreenwichMeanTime];

AlpineCmds: PROGRAM
IMPORTS
AE: AlpineEnvironment, AlpF: AlpFile, AlpI: AlpInstance, AlpT: AlpTransaction,
Directory, File, FileIO, AlpineInterimDirectory, IO, Lists: List, RefText, Rope, Space
= BEGIN

ROPE: TYPE = Rope.ROPE;
CreateOptions: TYPE = FileIO.CreateOptions;

VolumeID: TYPE = AE.VolumeID;
FileID: TYPE = AE.FileID;
PageCount: TYPE = AE.PageCount;
ByteCount: TYPE = AE.ByteCount;
wordsPerPage: INT = AE.wordsPerPage;
bytesPerPage: INT = AE.bytesPerPage;
PropertyValuePair: TYPE = AE.PropertyValuePair;

PagesForBytes: PROC [byteLength: ByteCount] RETURNS [pageCount: PageCount] = {
RETURN [(byteLength+bytesPerPage-1)/bytesPerPage];
};

Copy: PROC [to: ROPE, from: ROPE] = {
coordinator: AE.FileStore ← "";
coordinatorTransHandle: AlpT.Handle ← NIL;
transHandle: AlpT.Handle ← NIL;
-- Inner procs use transHandle.

FileStateObject: TYPE = RECORD [
SELECT type: {alpine, pilot} FROM
alpine => [fileHandle: AlpF.Handle],
pilot => [fc: File.Capability],
ENDCASE];

AlpineFileStateObject: TYPE = FileStateObject.alpine;
PilotFileStateObject: TYPE = FileStateObject.pilot;
FileState: TYPE = REF FileStateObject;
AlpineFileState: TYPE = REF AlpineFileStateObject;
PilotFileState: TYPE = REF PilotFileStateObject;

CreateFileState: PROC [file: ROPE, createOptions: CreateOptions, createLength: INT ← 0]
RETURNS [FileState] = {
IF file.Fetch[0] = '[ THEN {
volumeID: VolumeID; fileID: FileID;
alpineFileState: AlpineFileState;
fileStore: AE.FileStore;
leftSquareBracket: INT ← file.Find["]", 1];
IF leftSquareBracket IN [-1..1] THEN ERROR Error[illegalFileName];
fileStore ← file.Substr[start: 1, len: leftSquareBracket - 1];
IF coordinator.IsEmpty[]
THEN BEGIN
transHandle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore],
createLocalWorker: TRUE];
coordinator ← fileStore;
coordinatorTransHandletransHandle;
END

ELSE BEGIN
IF NOT Rope.Equal[fileStore, coordinator, FALSE]
THEN BEGIN
transHandle ← AlpT.CreateWithTransID[instHandle: AlpI.Create[fileStore:
fileStore], transID: coordinatorTransHandle.transID, createLocalWorker:
TRUE]
END;
END;
[volumeID, fileID, ] ← AlpineInterimDirectory.OpenUnderTrans[
transHandle, file, createOptions, createLength];
alpineFileState ← NEW[AlpineFileStateObject ← [alpine[fileHandle:
AlpF.Open[transHandle, volumeID, fileID,
IF createOptions = oldOnly THEN readOnly ELSE readWrite,
[IF createOptions = oldOnly THEN read ELSE write, wait],
log, sequential]]]];
IF createOptions # oldOnly THEN
alpineFileState.fileHandle.SetSize[PagesForBytes[createLength]];
RETURN [alpineFileState];
}
ELSE {
stream: IO.STREAM;
pilotFileState: PilotFileState;
stream ← FileIO.Open[
fileName: file,
accessOptions: IF createOptions = oldOnly THEN read ELSE overwrite,
createOptions: createOptions,
createLength: createLength,
streamBufferParms: FileIO.minimumStreamBufferParms];
pilotFileState ← NEW[PilotFileStateObject ←
[pilot[fc: FileIO.CapabilityFromStream[stream]]]];
stream.Close[];
IF createOptions # oldOnly THEN
File.SetSize[pilotFileState.fc, 1+PagesForBytes[createLength]];
RETURN [pilotFileState];
};
};

GetFileProperties: PROC [fileState: FileState] RETURNS [
byteLength: ByteCount, createTime: System.GreenwichMeanTime] = {
WITH fileState SELECT FROM
alpineFileState: AlpineFileState => {
fileProperties: LIST OF PropertyValuePair =
alpineFileState.fileHandle.ReadProperties[[byteLength: TRUE, createTime: TRUE]];
byteLength ← NARROW[
fileProperties.first, PropertyValuePair.byteLength].byteLength;
createTime ← NARROW[
fileProperties.rest.first, PropertyValuePair.createTime].createTime;
};
pilotFileState: PilotFileState => {
Directory.GetProperty[file: pilotFileState.fc, property: PropertyTypes.tByteLength,
propertyValue: DESCRIPTOR[
LOOPHOLE[LONG[@byteLength], LONG POINTER TO UNSPECIFIED],
SIZE[ByteCount]]];
Directory.GetProperty[file: pilotFileState.fc, property: PropertyTypes.tCreateDate,
propertyValue: DESCRIPTOR[
LOOPHOLE[LONG[@createTime], LONG POINTER TO UNSPECIFIED],
SIZE[System.GreenwichMeanTime]]];
};
ENDCASE => ERROR;
};

SetFileProperties: PROC [fileState: FileState,
byteLength: ByteCount, createTime: System.GreenwichMeanTime] = {
WITH fileState SELECT FROM
alpineFileState: AlpineFileState => {
alpineFileState.fileHandle.WriteProperties[LIST[[byteLength[byteLength]],
[createTime[createTime]]]];
};
pilotFileState: PilotFileState => {
Directory.PutProperty[file: pilotFileState.fc, property: PropertyTypes.tByteLength,
propertyValue: DESCRIPTOR[
LOOPHOLE[LONG[@byteLength], LONG POINTER TO UNSPECIFIED],
SIZE[ByteCount]]];
Directory.PutProperty[file: pilotFileState.fc, property: PropertyTypes.tCreateDate,
propertyValue: DESCRIPTOR[
LOOPHOLE[LONG[@createTime], LONG POINTER TO UNSPECIFIED],
SIZE[System.GreenwichMeanTime]]];
};
ENDCASE => ERROR;
};

buffer: Space.Handle;
bufferPtr: LONG POINTER;
pagesCopied: INT;

FillBuffer: PROC [fromFile: FileState, pagesToMove: CARDINAL] = {
WITH fromFile SELECT FROM
alpineFileState: AlpineFileState => {
alpineFileState.fileHandle.ReadPages[
pageRun: [firstPage: pagesCopied, count: pagesToMove],
pageBuffer: DESCRIPTOR [
bufferPtr, pagesToMove*wordsPerPage]];
};
pilotFileState: PilotFileState => {
Space.CopyIn[buffer, [pilotFileState.fc, pagesCopied+1]];
};
ENDCASE => ERROR;
};

EmptyBuffer: PROC [toFile: FileState, pagesToMove: CARDINAL] = {
WITH toFile SELECT FROM
alpineFileState: AlpineFileState => {
alpineFileState.fileHandle.WritePages[
pageRun: [firstPage: pagesCopied, count: pagesToMove],
pageBuffer: DESCRIPTOR [
bufferPtr, pagesToMove*wordsPerPage]];
};
pilotFileState: PilotFileState => {
Space.CopyOut[buffer, [pilotFileState.fc, pagesCopied+1]];
};
ENDCASE => ERROR;
};


toFile, fromFile: FileState;
byteLength: ByteCount; createTime: System.GreenwichMeanTime;
fromFile ← CreateFileState[file: from, createOptions: oldOnly];
[byteLength, createTime] ← GetFileProperties[fromFile];
toFile ← CreateFileState[file: to, createOptions: none, createLength: byteLength];

{
pageCount: PageCount = PagesForBytes[byteLength];
bufferLen: CARDINAL = 12;
buffer ← Space.Create[bufferLen, Space.virtualMemory];
bufferPtr ← Space.LongPointer[buffer];
Space.Map[buffer];
pagesCopied ← 0;
UNTIL pagesCopied = pageCount DO
pagesLeft: CARDINAL ← pageCount-pagesCopied;
pagesToMove: CARDINALMIN [bufferLen, pagesLeft];
FillBuffer[fromFile, pagesToMove];
EmptyBuffer[toFile, pagesToMove];
pagesCopied ← pagesCopied + pagesToMove;
ENDLOOP;
Space.Unmap[buffer];
Space.Delete[buffer];
SetFileProperties[toFile, byteLength, createTime];
IF (coordinatorTransHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit)
THEN ERROR;
};
};

Delete: PROC [file: ROPE] = {
AlpineInterimDirectory.Delete[file];
};

List: PROC [pattern: ROPE] RETURNS [LIST OF REF ANY] = {
directory, restOfPattern: ROPE;
resultList: LIST OF REF ANYNIL;
EnumProc: PROC [fileName: REF TEXT, volumeID: VolumeID, fileID: FileID]
RETURNS [quit: BOOL] = {
IF Rope.Match[pattern: restOfPattern,
object: RefText.TrustTextAsRope[fileName], case: FALSE] THEN {
resultRope: ROPEIO.PutFR["%g", IO.text[fileName]];
resultList ← CONS[first: resultRope, rest: resultList];
};
RETURN [quit: FALSE];
};
CompareProc: SAFE PROC [ref1, ref2: REF ANY]
RETURNS [Environment.Comparison] = CHECKED {
RETURN [Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]];
};
resultRope: ROPENIL;
[directory, restOfPattern] ← DecomposePattern[pattern];
AlpineInterimDirectory.EnumerateDirectory[
directoryName: directory, enumProc: EnumProc];
resultList ← Lists.Sort[resultList, CompareProc];
RETURN [resultList];
};

DecomposePattern: PROC [pattern: ROPE]
RETURNS [directory, restOfPattern: ROPE] = {
rightAngleBracket: INT;
IF NOT Rope.Match[pattern: "[*]<*>*", object: pattern, case: FALSE] THEN
ERROR Error[illegalFileName];
rightAngleBracket ← pattern.SkipTo[1, ">"];
directory ← pattern.Substr[start: 0, len: rightAngleBracket+1];
restOfPattern ← pattern.Substr[start: rightAngleBracket+1];
IF restOfPattern.IsEmpty[] THEN restOfPattern ← "*";
};

Error: ERROR [what: ErrorType] = CODE;
ErrorType: TYPE = {illegalFileName};

OwnerPropertyValuePair: TYPE = AE.OwnerPropertyValuePair;
OwnerPropertyValuePairList: TYPE = LIST OF OwnerPropertyValuePair;

CreateOwner: PROC [directory: ROPE, pageLimit: PageCount] = {
p: REF OwnerPropertyValuePair = NEW[
OwnerPropertyValuePair ← [quota[quota: pageLimit]]]; -- crock to make it compile
properties: OwnerPropertyValuePairList = LIST[[quota[pageLimit]]];
transHandle: AlpT.Handle;
fileStore, owner: ROPE;
[fileStore, owner] ← DecomposeDirectory[directory];
transHandle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore], createLocalWorker:
TRUE];
transHandle.AssertAlpineWheel[TRUE];
[] ← transHandle.CreateOwner[
volumeGroupID: transHandle.GetNextVolumeGroup[previousGroup:
AE.nullVolumeGroupID, lock: [none, wait]],
owner: owner,
properties: properties];
IF (transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN ERROR;
};

DecomposeDirectory: PROC [directory: ROPE]
RETURNS [fileStore, owner: ROPE] = {
rightSquareBracket, rightAngleBracket: INT;
restOfFileName: ROPE;
IF NOT Rope.Match[pattern: "[*]<*>*", object: directory, case: FALSE] THEN
ERROR Error[illegalFileName];
rightSquareBracket ← directory.SkipTo[1, "]"];
fileStore ← directory.Substr[start: 1, len: rightSquareBracket-1];
rightAngleBracket ← directory.SkipTo[1, ">"];
owner ← directory.Substr[
start: rightSquareBracket+2, len: rightAngleBracket-rightSquareBracket-2];
restOfFileName ← directory.Substr[start: rightAngleBracket+1];
IF fileStore.IsEmpty[] OR owner.IsEmpty[] OR NOT restOfFileName.IsEmpty[] THEN
ERROR Error[illegalFileName];
};

ReadQuota: PROC [directory: ROPE] RETURNS [pageLimit, spaceInUse: PageCount] = {
properties: OwnerPropertyValuePairList;
transHandle: AlpT.Handle;
fileStore, owner: ROPE;
[fileStore, owner] ← DecomposeDirectory[directory];
transHandle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore], createLocalWorker:
TRUE];
properties ← transHandle.ReadOwnerProperties[
volumeGroupID: transHandle.GetNextVolumeGroup[previousGroup:
AE.nullVolumeGroupID, lock: [none, wait]],
owner: owner,
desiredProperties: [quota: TRUE, spaceInUse: TRUE]];
UNTIL properties = NIL DO -- because ReadOwnerProperties does not sort them yet ...
WITH properties.first SELECT FROM
q: OwnerPropertyValuePair.quota => pageLimit ← q.quota;
s: OwnerPropertyValuePair.spaceInUse => spaceInUse ← s.spaceInUse;
ENDCASE => ERROR;
properties ← properties.rest;
ENDLOOP;
IF (transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN ERROR;
};

WriteQuota: PROC [directory: ROPE, pageLimit: PageCount] = {
properties: OwnerPropertyValuePairList = LIST[[quota[pageLimit]]];
transHandle: AlpT.Handle;
fileStore, owner: ROPE;
[fileStore, owner] ← DecomposeDirectory[directory];
transHandle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore], createLocalWorker:
TRUE];
transHandle.AssertAlpineWheel[TRUE];
transHandle.WriteOwnerProperties[
volumeGroupID: transHandle.GetNextVolumeGroup[previousGroup:
AE.nullVolumeGroupID, lock: [none, wait]],
owner: owner,
properties: properties];
IF (transHandle.Finish[requestedOutcome: commit, continue: FALSE]) # commit THEN ERROR;
};

ListOwners: PROC [fileStore: ROPE] RETURNS [LIST OF REF ANY] = {
CompareProc: SAFE PROC [ref1, ref2: REF ANY]
RETURNS [Environment.Comparison] = CHECKED {
RETURN [Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]];
};
result: LIST OF REF ANYNIL;
owner: ROPE;
transHandle: AlpT.Handle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore],
createLocalWorker: TRUE];
transHandle.AssertAlpineWheel[TRUE];
owner ← NIL;
DO
[owner, ] ← transHandle.ReadNextOwner[
volumeGroupID: transHandle.GetNextVolumeGroup[previousGroup:
AE.nullVolumeGroupID, lock: [none, wait]],
previousOwner: owner,
desiredProperties: ALL [FALSE]];
IF owner = NIL THEN EXIT;
result ← Lists.Cons[owner, result];
ENDLOOP;
IF transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit THEN ERROR;
RETURN [Lists.Sort[result, CompareProc]];
};

ReorganizeOwnerDB: PROC [fileStore: ROPE, nEntries: NAT] = {
transHandle: AlpT.Handle ← AlpT.Create[instHandle: AlpI.Create[fileStore: fileStore],
createLocalWorker: TRUE];
transHandle.AssertAlpineWheel[TRUE];
[] ← transHandle.ReorganizeOwnerDB[
volumeGroupID: transHandle.GetNextVolumeGroup[previousGroup:
AE.nullVolumeGroupID, lock: [none, wait]],
nEntries: nEntries];
IF (transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit) THEN ERROR;
};


END
.