Yodel: User commands
YodelUser.mesa
Last Edited by: Hagmann, January 23, 1985 5:22:33 pm PST
Rhagmann, January 24, 1985 9:39:30 am PST

DIRECTORY
AlpFile   USING[ FileID, GetSize, Handle, LockOption, Open, ReadProperties, ReadPages,
      SetSize, WritePages, WriteProperties],
AlpineEnvironment
    USING[ AccessList, ByteCount, bytesPerPage,
     NeededAccess, Outcome, OwnerName,
     PageCount, Principal, Property, PropertyValuePair, RName,
    UniversalFile, wordsPerPage],
AlpineFile  USING [ allProperties, PropertySet],
AlpineInterimDirectory
     USING[ CreateOptions, DeleteUnderTrans,
      EnumerateDirectoryUnderTrans, OpenUnderTrans],
AlpInstance  USING[ AccessFailed, Create, Handle, LockFailed ],
AlpTransaction USING[ AssertAlpineWheel, Create, Finish, Handle, Outcome],
BasicTime  USING[ GMT],
Basics    USING[ Comparison],
Buttons   USING[ Button, ButtonProc, SetDisplayStyle ],
FS     USING[ Close, ComponentPositions, Copy, Create, Delete,
      EnumerateForInfo, Error, ExpandName, GetInfo, GetName, InfoProc,
      Open, OpenFile, Read, SetByteCountAndCreatedTime, Write],
IO     USING[ int, PutF, PutFR, PutRope, rope, STREAM, text, time ],
List    USING[ Sort, Car, Cdr],
Menus   USING[ MouseButton],
RefText   USING[ TrustTextAsRope],
Rope,
RPC,
UserCredentials USING[ Get ],
ViewerClasses USING[ Viewer ],
ViewerTools  USING[ GetContents],
VM    USING[ AddressForPageNumber, Allocate, Free, Interval],
YodelData;
YodelUser: CEDAR PROGRAM
IMPORTS AlpineInterimDirectory, AlpFile, AlpInstance, AlpTransaction, Buttons, FS, IO, List, RefText, Rope, RPC, UserCredentials, ViewerTools, VM, YodelData
EXPORTS YodelData
=
BEGIN OPEN AE: AlpineEnvironment, YodelData;
ByteCount: TYPE = AE.ByteCount;
bytesPerPage: INT = AE.bytesPerPage;
PageCount: TYPE = AE.PageCount;
PropertyValuePair: TYPE = AE.PropertyValuePair;
UniversalFile: TYPE = AE.UniversalFile;
wordsPerPage: INT = AE.wordsPerPage;
PagesForBytes: PROC [byteLength: ByteCount] RETURNS [pageCount: PageCount] = {
RETURN [(byteLength+bytesPerPage-1)/bytesPerPage];
};
BytesForPages: PROC [pageCount: PageCount] RETURNS [byteLength: ByteCount] = {
RETURN [pageCount * bytesPerPage];
};

ROPE: TYPE = Rope.ROPE;
hasPattern: PUBLIC SAFE PROC [pattern: ROPE] RETURNS [BOOL] = {
RETURN [-1 # Rope.Find[s1: pattern, s2: "*"]];
};



CompareProc: SAFE PROC [ref1, ref2: REF ANY]
RETURNS [Basics.Comparison] = CHECKED {
RETURN [Rope.Compare[NARROW[ref1], NARROW[ref2], FALSE]];
};

makeSlashAWedge: Rope.TranslatorType = {
  IF old = '/ THEN RETURN['>] ELSE RETURN[old];
  };

ParseSArgs: PUBLIC PROC [ d: MyData]
RETURNS [user, password, srcServer, srcDir, srcFile: ROPE] = {

  srcFileTemp: ROPE ;
  srcServer ← ViewerTools.GetContents[d.srcServer];
-- srcFile ← ViewerTools.GetContents[d.srcFile];
  srcFileTemp ← ViewerTools.GetContents[d.srcFile] ;
IF (NOT srcFileTemp.IsEmpty[]) AND srcFileTemp.Fetch[0] = '/ THEN
  srcFileTemp ← Rope.Concat["<" ,
  srcFileTemp.Substr[start: 1, len: srcFileTemp.InlineLength[]-1]];
  srcFile ← Rope.Translate[base: srcFileTemp,
   translator: makeSlashAWedge ];
  user ← ViewerTools.GetContents[d.user];
IF (Rope.Equal[user,""] OR Rope.Equal[user,NIL]) THEN
  [user] ← UserCredentials.Get[];
    password ← ViewerTools.GetContents[d.password];
  [srcDir] ← DecomposePattern[server: srcServer, pattern: srcFile, user: user];

};

ParseDArgs: PROC [user: ROPE, d: MyData]
RETURNS [destServer, destDir, destFile: ROPE] = {

  destFileTemp: ROPE ;
  destServer ← ViewerTools.GetContents[d.destServer];
  destFileTemp ← ViewerTools.GetContents[d.destFile];
IF (NOT destFileTemp.IsEmpty[]) AND destFileTemp.Fetch[0] = '/ THEN
  destFileTemp ← Rope.Concat["<" ,
  destFileTemp.Substr[start: 1, len: destFileTemp.InlineLength[]-1]];
  destFile ← Rope.Translate[base: destFileTemp,
   translator: makeSlashAWedge ];
  [destDir] ← DecomposePattern[server: destServer, pattern: destFile, user: user];

};

listFiles: PROC [trans: AlpTransaction.Handle, server: ROPE, pattern: ROPE,
user: ROPENIL, password: ROPENIL ,
displayProperties: AlpineFile.PropertySet ← AlpineFile.allProperties,
d: MyData]
RETURNS [LIST OF REF ANY] = {
directory, restOfPattern: ROPE;
resultList: LIST OF REF ANYNIL;

AlpineEnumProc: PROC [fileName: REF TEXT,
universalFile: AlpineEnvironment.UniversalFile] RETURNS [quit: BOOL] = {
intendReadLockOption: AlpFile.LockOption = [intendRead ,
IF d.breakLocks THEN wait ELSE fail];
readLockOption: AlpFile.LockOption = [read, IF d.breakLocks THEN wait ELSE fail];
IF d.stopFlag THEN {
resultList ← CONS[first: NARROW[" ** List aborted **", ROPE], rest: NIL];
RETURN [quit: TRUE];
};
IF Rope.Match[pattern: restOfPattern,
object: RefText.TrustTextAsRope[fileName], case: FALSE] THEN {
file: AlpFile.Handle;
fileID: AlpFile.FileID;
properties: LIST OF AlpineEnvironment.PropertyValuePair;
byteLength: INT;
resultRope: ROPE ;
[file, fileID] ← AlpFile.Open[transHandle: trans, universalFile: universalFile,
lock: intendReadLockOption
! AlpInstance.LockFailed => GOTO lockError;
AlpInstance.AccessFailed => IF missingAccess = AlpineEnvironment.NeededAccess.fileRead
THEN GOTO skipFile
ELSE REJECT;
];
properties ← AlpFile.ReadProperties[handle: file, lock: readLockOption
! AlpInstance.LockFailed => GOTO lockError;
AlpInstance.AccessFailed =>
IF missingAccess = AlpineEnvironment.NeededAccess.fileRead THEN GOTO skipFile
ELSE REJECT;
];
byteLength ← NARROW[properties.first,
AlpineEnvironment.PropertyValuePair.byteLength].byteLength;
resultRope ← IO.PutFR["%g\t", IO.text[fileName]];
loop through properties and print those enabled
UNTIL properties = NIL DO
property: AlpineEnvironment.PropertyValuePair ← properties.first ;
properties ← properties.rest;
IF displayProperties[property.property] THEN BEGIN
SELECT property.property FROM
byteLength
byteLength => {
byteLength: INTNARROW[property,
AlpineEnvironment.PropertyValuePair.byteLength].byteLength;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["%g bytes ", IO.int[byteLength]]];
};
createTime
createTime => {
createTime: BasicTime.GMTNARROW[property,
AlpineEnvironment.PropertyValuePair.createTime].createTime;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["%g ", IO.time[createTime]]];
};
highWaterMark
highWaterMark => {
highWaterMark: AlpineEnvironment.PageCount ← NARROW[property,
AlpineEnvironment.PropertyValuePair.highWaterMark].highWaterMark;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["HWM: %g ", IO.int[highWaterMark]]];
};
modifyAccess
modifyAccess => {
modifyAccess: AlpineEnvironment.AccessList ← NARROW[property,
AlpineEnvironment.PropertyValuePair.modifyAccess].modifyAccess;
resultRope ← Rope.Concat[resultRope,"modify access: ("];
IF modifyAccess = NIL THEN resultRope ← Rope.Concat[resultRope,"*none*"]
ELSE BEGIN
UNTIL modifyAccess = NIL DO
accessItem: AlpineEnvironment.RName ← modifyAccess.first;
resultRope ← Rope.Concat[resultRope,accessItem];
modifyAccess ← modifyAccess.rest;
IF modifyAccess # NIL THEN resultRope ← Rope.Concat[resultRope,", "];
ENDLOOP;
END ;
resultRope ← Rope.Concat[resultRope,") "];
};
owner
owner => {
owner: AlpineEnvironment.OwnerName ← NARROW[property,
AlpineEnvironment.PropertyValuePair.owner].owner;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["owner: %g ", IO.rope[owner]]];
};
readAccess
readAccess => {
readAccess: AlpineEnvironment.AccessList ← NARROW[property,
AlpineEnvironment.PropertyValuePair.readAccess].readAccess;
resultRope ← Rope.Concat[resultRope,"read access: ("];
IF readAccess = NIL THEN resultRope ← Rope.Concat[resultRope,"*none*"]
ELSE BEGIN
UNTIL readAccess = NIL DO
accessItem: AlpineEnvironment.RName ← readAccess.first;
resultRope ← Rope.Concat[resultRope,accessItem];
readAccess ← readAccess.rest;
IF readAccess # NIL THEN resultRope ← Rope.Concat[resultRope,", "];
ENDLOOP;
END ;
resultRope ← Rope.Concat[resultRope,") "];
};
stringName
stringName => {
stringName: ROPENARROW[property,
AlpineEnvironment.PropertyValuePair.stringName].stringName;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["stringName: %g ", IO.rope[stringName]]];
};
version
version => {
version: LONG INTEGERNARROW[property,
AlpineEnvironment.PropertyValuePair.version].version;
resultRope ← Rope.Concat[resultRope,
IO.PutFR["version: %g ", IO.int[version]]];
};
ENDCASE;
END;
ENDLOOP;
resultList ← CONS[first: resultRope, rest: resultList];
EXITS
lockError => {
resultRope ← IO.PutFR["%g has lock set", IO.text[fileName]];
resultList ← CONS[first: resultRope, rest: resultList];
};
skipFile => {};
};
RETURN [quit: FALSE];
};
FSEnumProc: FS.InfoProc = {
  -- PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT,
  -- keep: CARDINAL] RETURNS [continue: BOOLEAN];
resultRope: ROPE ;
fName: ROPE ;
componentPositions: FS.ComponentPositions ;

IF d.stopFlag THEN {
 resultList ← CONS[first: NARROW[" ** List aborted **", ROPE], rest: NIL];
RETURN [continue: FALSE];
   };
[fullFName: fName, cp: componentPositions] ← FS.ExpandName[name: fullFName];
resultRope ← IO.PutFR["%g\t", IO.rope[fName]];
resultRope ← Rope.Concat[resultRope,
IO.PutFR["%g bytes ", IO.int[bytes]]];
resultRope ← Rope.Concat[resultRope,
IO.PutFR["\t%g ", IO.time[created]]];

resultList ← CONS[first: resultRope, rest: resultList];

RETURN [continue: TRUE];
};
resultRope: ROPENIL;
outcome: AlpTransaction.Outcome;
failureName: ROPE ← "" ;
{
[directory, restOfPattern] ← DecomposePattern[server, pattern, user];
IF trans # NIL THEN {
alpine files to list
bangPos: INT ;
IF d.assertWheel THEN trans.AssertAlpineWheel[TRUE];
IF (bangPos ← Rope.Find[restOfPattern,"!"]) >= 0 THEN {
IF bangPos = 0
THEN restOfPattern ← "*"
ELSE restOfPattern ← Rope.Substr[base: restOfPattern, len: bangPos];
};
AlpineInterimDirectory.EnumerateDirectoryUnderTrans[
transHandle: trans,
directoryName: Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]],
enumProc: AlpineEnumProc];
outcome ← AlpTransaction.Finish[trans, commit];
}
ELSE {
FS or IFS files to list
pattern: ROPE;
pattern ← Rope.Concat[Rope.Concat[Rope.Concat[Rope.Concat ["[", server], "]"], directory], restOfPattern];
FS.EnumerateForInfo[pattern: pattern, proc: FSEnumProc ];
};
};
resultList ← List.Sort[resultList, CompareProc];
RETURN [resultList];
};

DecomposePattern: PUBLIC PROC [server: ROPE, pattern: ROPE, user: ROPE]
RETURNS [directory, restOfPattern: ROPE] = {
rightAngleBracket: INT;
dir: ROPE ;
isAlpine: BOOL ;
IF Rope.Match[pattern: "*.alpine", object: server, case: FALSE] THEN {
 isAlpine ← TRUE;
 dir ← user;
 }
ELSE {
 isAlpine ← FALSE;
IF server.IsEmpty[] THEN dir ← NIL
ELSE {
   IF Rope.Match[pattern: "*.*", object: user, case: FALSE] THEN
    dir ← user.Substr[start: 0, len: user.SkipTo[1, "."]]
   ELSE dir ← user;
  };
 };
IF NOT Rope.Match[pattern: "<*>*", object: pattern, case: FALSE] THEN
BEGIN
directory ← Rope.Concat["<", dir] ;
  directory ← Rope.Concat[directory,">"];
restOfPattern ← pattern ;
IF restOfPattern.IsEmpty[] THEN restOfPattern ← "*";
END
ELSE BEGIN
rightAngleBracket ← pattern.SkipTo[1, ">"];
IF rightAngleBracket > 1
THEN directory ← pattern.Substr[start: 0, len: rightAngleBracket+1]
ELSE directory ← Rope.Concat["<>", dir] ;
  restOfPattern ← pattern.Substr[start: rightAngleBracket+1];
IF restOfPattern.IsEmpty[] THEN restOfPattern ← "*";
END
};


ListFilesProc: PUBLIC Buttons.ButtonProc=
BEGIN
  resultList: LIST OF REF ANYNIL;
  d: MyData = NARROW[clientData];
  server, user, file, password: ROPE;
  directory, restOfPattern: ROPE;
  printedSomething: BOOLFALSE;

  callList: YodelData.PerformProc = {

RETURN[listFiles[trans, server, file, user, password, d.displayProperties, d]];
};


  d.stopFlag ← FALSE;

  [user, password, server, directory, file] ← ParseSArgs[d];

  [directory, restOfPattern] ← DecomposePattern[server: server, pattern: file, user: user];

  d.out.PutF["\nList of [%g]%g%g\n",
IO.rope[server], IO.rope[directory], IO.rope[restOfPattern]];
  resultList ← PerformOp[performProc: callList,
  server: server, user: user, password: password];
DO
  nowRope: ROPENARROW[List.Car[resultList]];
IF resultList = NIL THEN EXIT;
  resultList ← List.Cdr[resultList];
  d.out.PutF[" %g\n", IO.rope[nowRope]];
  printedSomething ← TRUE ;
ENDLOOP;
IF NOT printedSomething THEN d.out.PutRope[" ** no files match the pattern **\n"];
END;



fileDelete: PROC [trans: AlpTransaction.Handle, server: ROPE, file: ROPE,
user: ROPENIL, password: ROPENIL, d: MyData]
RETURNS[resultList:LIST OF REF ANYNIL] = {
 directory, restOfPattern, directoryName: ROPE;

AlpineDeleteEnumProc: PROC [fileName: REF TEXT,
universalFile: AlpineEnvironment.UniversalFile] RETURNS [quit: BOOL] = {
IF d.stopFlag THEN {
 resultList ← CONS[first: NARROW[" ** Delete aborted **", ROPE], rest: NIL];
RETURN [quit: TRUE];
  };
IF Rope.Match[pattern: restOfPattern,
object: RefText.TrustTextAsRope[fileName], case: FALSE] THEN {
 fileN: ROPE;
 fileN ← Rope.Concat[directoryName,Rope.FromRefText[fileName]];
 AlpineInterimDirectory.DeleteUnderTrans[transHandle: trans,
  fileName: fileN];
 resultRope ← IO.PutFR["%g deleted", IO.text[fileName]];
 resultList ← CONS[first: resultRope, rest: resultList];
 };
RETURN [quit: FALSE];
};

FSDeleteEnumProc: FS.InfoProc = {
   -- PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT,
  -- keep: CARDINAL] RETURNS [continue: BOOLEAN];
  IF d.stopFlag THEN {
 resultList ← CONS[first: NARROW[">> ** Delete aborted **", ROPE], rest: NIL];
RETURN [continue: FALSE];
   };
  FS.Delete[ name: fullFName, wantedCreatedTime: created];
  resultRope ← IO.PutFR["%g deleted", IO.rope[fullFName]];
resultList ← CONS[first: resultRope, rest: resultList];
RETURN [continue: TRUE];
};

 resultRope: ROPENIL;
 outcome: AlpTransaction.Outcome;
 failureName: ROPE ← "" ;
 {
  [directory, restOfPattern] ← DecomposePattern[server, file, user];
  IF trans # NIL THEN {
    -- alpine files to delete
  bangPos: INT ;
IF d.assertWheel THEN trans.AssertAlpineWheel[TRUE];
IF (bangPos ← Rope.Find[restOfPattern,"!"]) >= 0 THEN {
   IF bangPos = 0 THEN restOfPattern ← "*"
    ELSE restOfPattern ← Rope.Substr[base: restOfPattern, len: bangPos];
   };
  directoryName ← Rope.Concat["[",Rope.Concat[server,Rope.Concat["]",directory]]];
  AlpineInterimDirectory.EnumerateDirectoryUnderTrans[
  transHandle: trans,
  directoryName: directoryName,
  enumProc: AlpineDeleteEnumProc];
  outcome ← AlpTransaction.Finish[trans, commit];
IF outcome # commit THEN {
 resultList ← CONS[
  first: Rope.Concat["Alpine transaction aborted -- deletes NOT done",
IF resultList = NIL THEN NIL ELSE "; log before abort is:"],
  rest: resultList];
   };
  }
ELSE {
    -- FS or IFS files to delete
 pattern: ROPE;
 pattern ← Rope.Concat[Rope.Concat[Rope.Concat[Rope.Concat
  ["[", server], "]"], directory], restOfPattern];
IF pattern.Find["!"] < 0 THEN pattern ← Rope.Concat[pattern, "!l"];
FS.EnumerateForInfo[pattern: pattern, proc: FSDeleteEnumProc ];
 };
};

resultList ← List.Sort[resultList, CompareProc];
RETURN [resultList];
};



DeleteFilesProc: PUBLIC Buttons.ButtonProc=
BEGIN
  d: MyData = NARROW[clientData];
  resultList: LIST OF REF ANYNIL;
  srcServer, srcFile, srcDir: ROPE;
  user, password: ROPE;
  directory, restOfPattern: ROPE;
  printedSomething: BOOLFALSE;

  callDelete: YodelData.PerformProc = {

RETURN[fileDelete[trans: trans, server: srcServer, file: srcFile,
user: user, password: password, d: d]];
};


  d.stopFlag ← FALSE;
  [user, password, srcServer, srcDir, srcFile] ← ParseSArgs[d];
  [directory, restOfPattern] ← DecomposePattern[server: srcServer, pattern: srcFile,
  user: user];

IF Rope.IsEmpty[srcFile] THEN {
  d.out.PutF["\nDelete of [%g]%g%g NOT done -- MUST have explicit pattern\n",
   IO.rope[srcServer], IO.rope[srcDir], IO.rope[restOfPattern]];
   RETURN;
  };
 d.out.PutF["\nDelete of [%g]%g%g\n",
IO.rope[srcServer], IO.rope[srcDir], IO.rope[restOfPattern]];
 resultList ← PerformOp[performProc: callDelete,
  server: srcServer, user: user, password: password];
DO
  nowRope: ROPENARROW[List.Car[resultList]];
IF resultList = NIL THEN EXIT;
  resultList ← List.Cdr[resultList];
  d.out.PutF[" %g\n", IO.rope[nowRope]];
  printedSomething ← TRUE ;
ENDLOOP;
IF NOT printedSomething THEN d.out.PutRope[" ** no files match the pattern **\n"];

END;


-- excessBytes is only used if expunging = TRUE.
fileCopy: PROC [trans: AlpTransaction.Handle, to: ROPE, from: ROPE,
fullCopy: BOOL, user: ROPENIL, password: ROPENIL ,
expunging: BOOLEANFALSE, excessBytes: AlpineEnvironment.ByteCount ← 0,
caller: AlpineEnvironment.Principal, key: RPC.EncryptionKey,
d: MyData]
RETURNS [copyResult: LIST OF REF ANYNIL] =
{

FileStateObject: TYPE = RECORD [
SELECT type: {alpine, fs} FROM
alpine => [fileHandle: AlpFile.Handle],
fs => [openFile: FS.OpenFile],
ENDCASE];

AlpineFileStateObject: TYPE = FileStateObject.alpine;
FSFileStateObject: TYPE = FileStateObject.fs;
FileState: TYPE = REF FileStateObject;
AlpineFileState: TYPE = REF AlpineFileStateObject;
FSFileState: TYPE = REF FSFileStateObject;

CreateFileState: PROC [file: ROPE, createOptions: AlpineInterimDirectory.CreateOptions,
createLength: INT ← 0, trans: AlpTransaction.Handle ← transHandle]
RETURNS [FileState] = {
  -- test for alpine files
IF Rope.Match[pattern: "[*.alpine]*", object: file, case: FALSE] THEN {
universalFile: UniversalFile;
createdFile: BOOLEAN;
alpineFileState: AlpineFileState;
rightSquareBracket: INT ← file.Find["]", 1];
IF rightSquareBracket IN [-1..1]
THEN BEGIN IF expunging
THEN ERROR
ELSE ERROR ;
END;
[universalFile, createdFile] ← AlpineInterimDirectory.OpenUnderTrans[
trans, file, createOptions, createLength];
alpineFileState ← NEW[AlpineFileStateObject ← [alpine[fileHandle:
AlpFile.Open[trans, universalFile,
IF createOptions = oldOnly THEN readOnly ELSE readWrite,
[IF createOptions = oldOnly THEN read ELSE write, wait],
log, sequential].handle]]];
IF createOptions # oldOnly -- this is the "to" file.
THEN BEGIN
IF ((expunging) AND (NOT createdFile))
THEN BEGIN
alpineFileState.fileHandle.WriteProperties[LIST[[highWaterMark[0]]]];
IF (trans.Finish[requestedOutcome: commit, continue: TRUE] #
commit) THEN ERROR;
END;
alpineFileState.fileHandle.SetSize[PagesForBytes[createLength + (IF expunging
THEN excessBytes ELSE 0)]];
END;
RETURN [alpineFileState];
}
ELSE {
openFile: FS.OpenFile;
fsFileState: FSFileState;
IF createOptions = oldOnly THEN {
 openFile ← FS.Open[file];
 }
ELSE {
 openFile ← FS.Create[
  name: file,
  setPages: TRUE,
  pages: PagesForBytes[createLength]];
 };
fsFileState ← NEW[FSFileStateObject ←
[fs[openFile: openFile]]];
RETURN [fsFileState];
};
};


CleanupFileState: PROC [fileState: FileState] = {
WITH fileState SELECT FROM
alpineFileState: AlpineFileState => {
};
fsFileState: FSFileState => {
 fsFileState.openFile.Close[];
 };
ENDCASE => ERROR;

 };

GetFileProperties: PROC [fileState: FileState]
RETURNS [ byteLength: AlpineEnvironment.ByteCount, createTime: BasicTime.GMT,
mutableAlpineProperites: LIST OF AlpineEnvironment.PropertyValuePair ← NIL, highWaterMark: AlpineEnvironment.PageCount ← 0, alpineSize: INT ← 0] = {
WITH fileState SELECT FROM
alpineFileState: AlpineFileState => {
properties: LIST OF PropertyValuePair ;
alpineSize ← alpineFileState.fileHandle.GetSize[];
properties ← alpineFileState.fileHandle.ReadProperties[];
WHILE properties # NIL DO
property: AlpineEnvironment.PropertyValuePair ← properties.first ;
properties ← properties.rest;
SELECT property.property FROM
byteLength => {
byteLength ← NARROW[property,
AlpineEnvironment.PropertyValuePair.byteLength].byteLength;
mutableAlpineProperites ← CONS[[byteLength[byteLength]], mutableAlpineProperites];
};
createTime => {
createTime ← NARROW[property,
AlpineEnvironment.PropertyValuePair.createTime].createTime;
mutableAlpineProperites ← CONS[[createTime[createTime]], mutableAlpineProperites];
};
highWaterMark => {
highWaterMark ← NARROW[property,
AlpineEnvironment.PropertyValuePair.highWaterMark].highWaterMark;
mutableAlpineProperites ← CONS[[highWaterMark[highWaterMark]], mutableAlpineProperites];
};
modifyAccess => {
modifyAccess: AlpineEnvironment.AccessList ← NARROW[property,
AlpineEnvironment.PropertyValuePair.modifyAccess].modifyAccess;
mutableAlpineProperites ← CONS[[modifyAccess[modifyAccess]], mutableAlpineProperites];
};
readAccess => {
readAccess: AlpineEnvironment.AccessList ← NARROW[property,
AlpineEnvironment.PropertyValuePair.readAccess].readAccess;
mutableAlpineProperites ← CONS[[readAccess[readAccess]], mutableAlpineProperites];
};
ENDCASE;
ENDLOOP;
};
fsFileState: FSFileState => {
[bytes: byteLength, created: createTime] ← fsFileState.openFile.GetInfo[];
};
ENDCASE => ERROR;
};
SetFileProperties: PROC [fileState: FileState,
byteLength: AlpineEnvironment.ByteCount, createTime: BasicTime.GMT,
alpineProperties: LIST OF AlpineEnvironment.PropertyValuePair ← NIL] = {
WITH fileState SELECT FROM
alpineFileState: AlpineFileState => {
IF fullCopy THEN alpineFileState.fileHandle.WriteProperties[alpineProperties]
ELSE alpineFileState.fileHandle.WriteProperties[LIST[[byteLength[byteLength]],
[createTime[createTime]]]];
};
fsFileState: FSFileState => {
 fsFileState.openFile.SetByteCountAndCreatedTime[bytes: byteLength,
  created: createTime];
  };
ENDCASE => ERROR;
};

FillBuffer: PROC [fromFile: FileState, pagesToMove: CARDINAL] = {
WITH fromFile SELECT FROM
alpineFileState: AlpineFileState => {
TRUSTED BEGIN alpineFileState.fileHandle.ReadPages[
pageRun: [firstPage: pagesCopied, count: pagesToMove],
pageBuffer: DESCRIPTOR [
bufferPtr, pagesToMove*wordsPerPage]]; END;
};
fsFileState: FSFileState => {
TRUSTED BEGIN
 fsFileState.openFile.Read[from: pagesCopied, nPages: pagesToMove, to: bufferPtr];
END;
};
ENDCASE => ERROR;
};

EmptyBuffer: PROC [toFile: FileState, pagesToMove: CARDINAL] = {
WITH toFile SELECT FROM
alpineFileState: AlpineFileState => {
TRUSTED BEGIN alpineFileState.fileHandle.WritePages[
pageRun: [firstPage: pagesCopied, count: pagesToMove],
pageBuffer: DESCRIPTOR [
bufferPtr, pagesToMove*wordsPerPage]]; END;
};
fsFileState: FSFileState => {
TRUSTED BEGIN
 fsFileState.openFile.Write[to: pagesCopied, nPages: pagesToMove, from: bufferPtr];
END;
};
ENDCASE => ERROR;
};

moveTheBits: PROC [] RETURNS [aborted: BOOLFALSE]= {
pagesCopied ← 0;
UNTIL pagesCopied = pageCount DO
pagesLeft: CARDINAL ← pageCount-pagesCopied;
pagesToMove: CARDINALMIN [bufferLen, pagesLeft];
IF d.stopFlag THEN {
WITH toFile SELECT FROM
  alpineFileState: AlpineFileState => {
  [] ← transHandle.Finish[requestedOutcome: abort, continue: FALSE] ;
  IF secondTransHandle # NIL THEN
   [] ← secondTransHandle.Finish[requestedOutcome: abort, continue: FALSE] ;
  };
   fsFileState: FSFileState => {
   fullF: ROPE;
   created: BasicTime.GMT ;
   [fullFName: fullF] ← fsFileState.openFile.GetName[];
   [created: created] ← fsFileState.openFile.GetInfo[];
   fsFileState.openFile.Close[];
   FS.Delete[name: fullF, wantedCreatedTime: created];
   };
ENDCASE => ERROR;
RETURN[TRUE];
 };
FillBuffer[fromFile, pagesToMove];
EmptyBuffer[toFile, pagesToMove];
pagesCopied ← pagesCopied + pagesToMove;
ENDLOOP;
};


transHandle: AlpTransaction.Handle ← trans;
secondInst: AlpInstance.Handle ← NIL;
secondTransHandle: AlpTransaction.Handle ← NIL;
toFile, fromFile: FileState;
byteLength: AlpineEnvironment.ByteCount;
createTime: BasicTime.GMT;
bufferLen: CARDINAL = 12;
pageCount: PageCount ;
bufferInterval: VM.Interval ← TRASH;
bufferAllocated: BOOLFALSE ;
bufferPtr: LONG POINTER;
pagesCopied: INT;
cp: FS.ComponentPositions;
toServer, fromServer: ROPE ;
fullN: ROPE ;
IFSResult: BOOL ;
alpineSource: BOOL ;
alpineResult: BOOL ;
alpineSourceAndResult: BOOL;
twoDifferentAlpine: BOOLFALSE;
alpineProperties: LIST OF AlpineEnvironment.PropertyValuePair ← NIL;
highWaterMark: AlpineEnvironment.PageCount ← 0;
createLength: AlpineEnvironment.ByteCount;
alpineSize: INT;

{
ENABLE UNWIND => {
IF transHandle # NIL THEN [] ← AlpTransaction.Finish[transHandle, abort
  ! RPC.CallFailed => CONTINUE];
IF secondTransHandle # NIL THEN [] ← AlpTransaction.Finish[secondTransHandle, abort
  ! RPC.CallFailed => CONTINUE];
IF bufferAllocated THEN TRUSTED {VM.Free[bufferInterval]};
 bufferAllocated ← FALSE;
IF fromFile # NIL THEN CleanupFileState[fromFile ! FS.Error => CONTINUE];
IF toFile # NIL THEN CleanupFileState[toFile ! FS.Error => CONTINUE];
 };

IF transHandle # NIL AND d.assertWheel THEN transHandle.AssertAlpineWheel[TRUE];
fromFile ← CreateFileState[file: from, createOptions: oldOnly];
[byteLength, createTime, alpineProperties, highWaterMark, alpineSize] ←
GetFileProperties[fromFile];


  [fullFName: fullN, cp: cp] ← FS.ExpandName[name: to];
  toServer ← Rope.Substr[fullN,cp.server.start, cp.server.length];
  alpineResult ← Rope.Match[pattern: "*.alpine", object: toServer, case: FALSE] ;
[fullFName: fullN, cp: cp] ← FS.ExpandName[name: from];
fromServer ← Rope.Substr[fullN,cp.server.start, cp.server.length];
alpineSource ← Rope.Match[pattern: "*.alpine", object: fromServer, case: FALSE] ;
alpineSourceAndResult ← alpineResult AND alpineSource ;

IF byteLength < 0 AND (~alpineSourceAndResult OR ~fullCopy) THEN RETURN[CONS[
NARROW["The source file has negative length. Copy aborted.",ROPE]
,NIL]];

IF alpineSourceAndResult AND fullCopy THEN {
 createLength ← MAX[byteLength, BytesForPages[highWaterMark], alpineSize];
 }
ELSE createLength ← byteLength;

IF Rope.IsEmpty[toServer] OR alpineResult THEN {
 IFSResult ← FALSE;
IF alpineSourceAndResult AND
  ~Rope.Equal[ s1: fromServer, s2: toServer, case: FALSE] THEN {
  twoDifferentAlpine ← TRUE;
  secondInst ← AlpInstance.Create[fileStore: toServer, caller: caller, key: key];
  secondTransHandle ← AlpTransaction.Create[secondInst];
  IF d.assertWheel THEN secondTransHandle.AssertAlpineWheel[TRUE];
  toFile ← CreateFileState[file: to, createOptions: none, createLength: createLength,
   trans: secondTransHandle];
  }
ELSE toFile ← CreateFileState[file: to, createOptions: none, createLength: createLength];
 }
ELSE {
 IFSResult ← TRUE;
 toFile ← CreateFileState[file: "///temp/qqqscratch.yodel", createOptions: none,
  createLength: createLength];
 };

-- toFile ← CreateFileState[file: to, createOptions: none, createLength: byteLength];

pageCount ← PagesForBytes[createLength];

TRUSTED BEGIN
 bufferInterval ← VM.Allocate[count: bufferLen];
 bufferAllocated ← TRUE ;
 bufferPtr ← VM.AddressForPageNumber[bufferInterval.page];
END;
IF moveTheBits[] THEN RETURN[CONS[
NARROW["Copy aborted ",ROPE]
,NIL]];
SetFileProperties[toFile, byteLength, createTime, alpineProperties];
CleanupFileState[fromFile];
CleanupFileState[toFile];
IF twoDifferentAlpine THEN {
IF (secondTransHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit)
THEN RETURN[CONS[
NARROW[
 "Transaction on destination server failed to commit - validity of copy is in question",
ROPE]
 ,NIL]];
 }; TRUSTED BEGIN
   VM.Free[bufferInterval];
   bufferAllocated ← FALSE ;
  END;
IF IFSResult THEN {
FS.Copy[from: "///temp/qqqscratch.yodel", to: to ];
FS.Delete[name: "///temp/qqqscratch.yodel"];
 };
IF transHandle # NIL AND
(transHandle.Finish[requestedOutcome: commit, continue: FALSE] # commit)
THEN RETURN[CONS[
NARROW["Transaction on source server failed to commit - validity of copy is in question",ROPE]
,NIL]];
};
};

CopyFilesProc: PUBLIC Buttons.ButtonProc = {
InnerCopyFilesProc[FALSE, parent, clientData, mouseButton, shift, control];
};
FullCopyFilesProc: PUBLIC Buttons.ButtonProc = {
InnerCopyFilesProc[TRUE, parent, clientData, mouseButton, shift, control];
};
InnerCopyFilesProc: PROC [fullCopy: BOOL, parent: REF ANY, clientData: REF ANYNIL,
mouseButton: Menus.MouseButton ← red, shift, control: BOOLFALSE]=
BEGIN
  d: MyData = NARROW[clientData];
  resultList: LIST OF REF ANYNIL;
  result: ROPE;
  server, srcServer, srcFile, srcDir, destServer, destFile, destDir: ROPE;
  user, password: ROPE;
  directory, sRestOfPattern, dRestOfPattern: ROPE;
  toFile, fromFile: ROPE;

  callCopy: YodelData.PerformProc = {

RETURN[fileCopy[trans: trans, to: toFile, from: fromFile,
fullCopy: fullCopy, user: user, password: password, caller: caller, key: key, d: d ]];
};


  d.stopFlag ← FALSE;
  [user, password, srcServer, srcDir, srcFile] ← ParseSArgs[d];
  [destServer, destDir, destFile] ← ParseDArgs[user, d];
  [directory, sRestOfPattern] ← DecomposePattern[server: srcServer, pattern: srcFile,
  user: user];
IF (Rope.Equal[srcServer,""] OR Rope.Equal[srcServer,NIL]) THEN BEGIN
IF srcFile.InlineLength[]>0 AND srcFile.InlineFetch[0] = '<
THEN fromFile ← Rope.Concat["[]", srcFile]
ELSE fromFile ← srcFile;
END
ELSE BEGIN
  fromFile ← Rope.Concat["[",Rope.Concat[srcServer,Rope.Concat["]",
  Rope.Concat[directory,sRestOfPattern]]]];
END;
  [directory, dRestOfPattern] ← DecomposePattern[server: destServer, pattern: destFile,
  user: user];
IF (Rope.Equal[destServer,""] OR Rope.Equal[destServer,NIL]) THEN BEGIN
IF destFile.InlineLength[]>0 AND destFile.InlineFetch[0] = '<
THEN toFile ← Rope.Concat["[]", destFile]
ELSE toFile ← destFile;
END
ELSE BEGIN
  toFile ← Rope.Concat["[",Rope.Concat[destServer,Rope.Concat["]",
  Rope.Concat[directory,dRestOfPattern]]]];
END;

IF hasPattern[sRestOfPattern] OR hasPattern[dRestOfPattern] THEN BEGIN
  d.out.PutF["\nCopy only works on a single file to a single file\n"];
END
ELSE BEGIN
  d.out.PutF["\nCopy of %g to %g\n",
IO.rope[fromFile], IO.rope[toFile]];

IF Rope.Match[pattern: "*.alpine", object: srcServer, case: FALSE]
   THEN server ← srcServer
   ELSE server ← destServer ;
  resultList ← PerformOp[performProc: callCopy,
  server: server, user: user, password: password];
IF resultList = NIL THEN BEGIN
  d.out.PutF[" Copy seems OK.\n"];
END
ELSE BEGIN
  result ← NARROW[resultList.first];
  d.out.PutF[" %g\n", IO.rope[result]];
END;
END;

END;


OptionsProc: PUBLIC Buttons.ButtonProc=
BEGIN
  d: MyData = NARROW[clientData];
  p: ViewerClasses.Viewer = NARROW[parent];
  d.displayOptions ← NOT d.displayOptions ;
  CreateButtons[d, p.parent.parent];
END;
ChangeOptionsProc: PUBLIC Buttons.ButtonProc = BEGIN
d: MyData = NARROW[clientData];
p: ViewerClasses.Viewer = NARROW[parent];
count: INT ;
option: AlpineEnvironment.Property;
loop through all the properties and find the one selected
comparison is by Rope equivalent to option!
FOR count IN [0..NumberOfAlpineProperties) DO
propertyName: ROPE ;
[option, propertyName] ← PropertySetToRopeArray[count];
IF Rope.Equal[propertyName, p.name] THEN BEGIN
d.displayProperties[option] ← NOT d.displayProperties[option] ;
IF d.displayProperties[option]
THEN Buttons.SetDisplayStyle[p, $WhiteOnBlack]
ELSE Buttons.SetDisplayStyle[p, $BlackOnWhite];
EXIT;
END;
ENDLOOP;
END;



END.