-- File: ClientFileTool.mesa
-- Last edit by Russ Atkinson: January 19, 1983 9:44 pm
-- Schmidt: 25-Jul-81 12:09:21
-- Andrew Birrell: March 9, 1983 3:09 pm
-- Paul Rovner: April 12, 1983 10:36 pm
-- NOTE: change the create date message when changing this beast

DIRECTORY
Boot USING[ LVBootFiles ],
Buttons USING[ ButtonProc, Create, Destroy ],
Commander USING[ CommandProc, Register ],
Containers USING[ ChildXBound, ChildYBound, Create ],
ConvertUnsafe USING[ AppendRope ],
Directory USING
[CreateFile, DeleteFile, Error, GetDefaultContext, GetNext, GetProps, GetWD,
Handle, Lookup, LookupUnlimited, ModifyContext, RemoveFile],
DirectoryExtras USING [ForgetVolumes],
File USING [
Capability, GetAttributes,
GetSize, nullCapability, nullID, PageCount,
Permissions, read, SetSize],
HeapString USING [AppendChar, AppendString],
Inline USING [LowHalf],
IO USING [PutChar, PutF, STREAM],
KernelFile USING [MakeTemporary],
Rope USING [Length, ROPE],
RTFiles USING [IsFileInUse],
Rules USING[ Create ],
Space USING [
CopyOut, Create, Delete, Handle, Map, Unmap, virtualMemory],
SpecialVolume USING[ GetLogicalVolumeBootFiles ],
Storage USING [FreeString, String],
String USING [AppendLongDecimal, AppendString],
System USING [GreenwichMeanTime],
TypeScript USING [Create],
ViewerClasses USING [Viewer],
ViewerIO USING [CreateViewerStreams],
ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection],
Volume USING [
Close, GetAttributes, GetLabelString, GetNext, GetType, ID,
maxNameLength, nullID, PageCount, Type, TypeSet, systemID],
VolumeExtras USING [OpenVolume];

ClientFileTool: CEDAR MONITOR
IMPORTS
Buttons, Commander, Containers, ConvertUnsafe, Directory, DirectoryExtras, File, HeapString, Inline, IO,
KernelFile, Rope, RTFiles, Rules, Space, SpecialVolume, Storage, String, TypeScript, ViewerIO, ViewerTools, Volume, VolumeExtras
= BEGIN OPEN HeapString;

CommandType: TYPE = {get, list, delete, name, getboth};

volumeOpened: BOOLEANFALSE; -- some slight protection here

-- Commands Invoked from Menu

GetFileFromClientVolume: Buttons.ButtonProc = {
DoCommand[clientData, get]};

DeleteFilesFromClientVolume: Buttons.ButtonProc = {
DoCommand[clientData, delete]};

ListFilesOnClientVolume: Buttons.ButtonProc = {
DoCommand[clientData, list]};

NameFilesOnClientVolume: Buttons.ButtonProc = {
DoCommand[clientData, name]};

GetBothFilesOnClientVolume: Buttons.ButtonProc = {
DoCommand[clientData, getboth]};

Stop: Buttons.ButtonProc = {
data: MyData = NARROW[clientData]; data.stopWanted ← TRUE };

DoCommand: PROC [clientData: REF ANY, command: CommandType] = TRUSTED {
OPEN HeapString;
data: MyData = NARROW[clientData];
cpText: Rope.ROPE = ViewerTools.GetContents[data.cpText];
localText: Rope.ROPE = ViewerTools.GetContents[data.localText];
cpName: STRING ← Storage.String[cpText.Length[]];
localName: STRING ← Storage.String[localText.Length[]];
DisplayMsg: PROCEDURE [s1, s2: STRINGNIL] = TRUSTED {
data.out.PutF["\n%g%g", [string[s1]], [string[s2]] ] };
volumeLabel: STRING ← "Client"L;
fileName, pathName, workingDir, scratch: STRINGNIL;
oldContext: Directory.Handle ← Directory.GetDefaultContext[];
fileCount, pageCount: LONG CARDINAL ← 0;
useLocal: BOOLFALSE;
ConvertUnsafe.AppendRope[cpName, cpText];
ConvertUnsafe.AppendRope[localName, localText];
IF volumeOpened THEN
{DisplayMsg["Client volume already opened!"L]; RETURN};
FindClientVolume[];
IF clientVolume = Volume.nullID THEN
{DisplayMsg["Client volume not found!"L]; RETURN};

fileName ← Storage.String[60];
pathName ← Storage.String[60];
workingDir ← Storage.String[60];
scratch ← Storage.String[60];
Directory.GetWD[oldContext, workingDir];

AppendChar[@pathName, '<];
AppendString[@pathName, clientLabel];
AppendString[@pathName, ">SysDir>"L];
IF cpName # NIL THEN AppendString[@fileName, cpName];
IF command = get AND localName # NIL AND localName.length > 0 THEN useLocal ← TRUE;
{single: BOOL ← SingleFilePattern[fileName];
nextName: STRING ← [80];
pathNameLen: NAT ← pathName.length;
triedBcd: BOOLFALSE;
triedMesa: BOOLFALSE;
IF useLocal THEN
{IF NOT single THEN
{DisplayMsg["Sorry, client patterns require blank local name."L];
GO TO out};
IF NOT SingleFilePattern[localName] THEN
{DisplayMsg["Sorry, patterns not allowed in local name."L];
GO TO out}};
VolumeExtras.OpenVolume
[volume: clientVolume, readOnly: command # delete
! ANY => {DisplayMsg["Client volume could not be opened!"L]; GO TO out}];
volumeOpened ← TRUE;
Directory.ModifyContext[oldContext, pathName];
DisplayMsg[""L];
{root: File.Capability;
volSize,freePages: Volume.PageCount;
[volSize, freePages, root] ← Volume.GetAttributes[clientVolume];
data.out.PutF["\nClient volume has %g total pages, %g free pages",
[cardinal[volSize]], [cardinal[freePages]] ]};
IF fileName.length = 0 THEN
{DisplayMsg["No client file name."L]; GO TO out};
DO
localFile: File.Capability ← File.nullCapability;
pages: File.PageCount ← 0;
cap: File.Capability ← File.nullCapability;
lagName: STRING ← [80];
IF data.stopWanted THEN {
data.stopWanted ← FALSE;
DisplayMsg["!!Aborted!!"];
GO TO out;
};
IF command = delete THEN String.AppendString[lagName, nextName];
IF single
THEN
{nextName.length ← 0;
String.AppendString[nextName, pathName];
String.AppendString[nextName, fileName];
SELECT FALSE FROM
command = getboth =>
{};
triedBcd =>
{String.AppendString[nextName, ".bcd"];
triedBcd ← TRUE};
triedMesa =>
{String.AppendString[nextName, ".mesa"];
triedMesa ← TRUE};
ENDCASE => EXIT;
cap ← Directory.Lookup
[fileName: nextName, permissions: File.read
! Directory.Error --[type]-- =>
{IF type # fileNotFound
THEN DisplayMsg[nextName, " has a problem!"L]
ELSE DisplayMsg[nextName, " could not be found!"L];
EXIT}]}
ELSE
cap ← Directory.GetNext
[pathName, nextName, nextName
! ANY => CONTINUE];
IF nextName.length = 0 OR cap = File.nullCapability THEN EXIT;
IF NOT Match[fileName, 0, nextName, pathNameLen, command = getboth] THEN LOOP;

pages ← File.GetSize[cap];
SELECT command FROM
get, getboth =>
{scratch.length ← 0;
AppendString[@scratch, workingDir];
IF pages > 8191 THEN
{DisplayMsg[nextName, " is too big!"L]; LOOP};
IF NOT useLocal
THEN {-- skip over path name to get local name
pos: NAT ← pathNameLen;
localName.length ← 0;
FOR pos: NAT IN [pathNameLen..nextName.length) DO
AppendChar[@localName, nextName[pos]];
ENDLOOP};
AppendString[@scratch, localName];
localFile ←
Directory.LookupUnlimited
[fileName: scratch
! Directory.Error --[type]-- =>
{IF type # fileNotFound THEN
{DisplayMsg[localName, " has a problem"L]; LOOP};
CONTINUE}];
IF localFile # File.nullCapability AND RTFiles.IsFileInUse[localFile]
THEN BEGIN
DisplayMsg[scratch, " was in use: making old version temporary"L];
Directory.RemoveFile[scratch, localFile];
KernelFile.MakeTemporary[localFile];
localFile ← File.nullCapability;
END;
IF localFile = File.nullCapability
THEN localFile ←
Directory.CreateFile
[fileName: scratch,
fileType: File.GetAttributes[cap].type,
size: pages
! ANY =>
{DisplayMsg[fileName, " can't be created locally"L]; LOOP}]
ELSE File.SetSize[localFile, pages];
DisplayMsg["Copy "L, nextName];
{chunk: CARDINAL ← Inline.LowHalf[pages];
space: Space.Handle;
base: File.PageCount ← 0;
IF chunk > 64 THEN chunk ← 64;
IF chunk > 0 THEN
space ← Space.Create
[chunk, Space.virtualMemory
! ANY => {DisplayMsg
["Could not create VM space for "L, nextName];
LOOP}];
WHILE base < pages DO
-- loop to transfer file (avoid LARGE spaces)
Space.Map[space, [cap, base]];
Space.CopyOut[space, [localFile, base]];
Space.Unmap[space];
base ← base + chunk;
ENDLOOP;
Space.Delete[space]};
DisplayMsg[" to "L, scratch];
};
delete =>
{Directory.DeleteFile
[nextName
! ANY =>
{DisplayMsg[nextName, " could not be deleted!"L]; LOOP}];
DisplayMsg[nextName, " deleted."L];
nextName.length ← 0;
String.AppendString[nextName, lagName];
};
list =>
{readDate, writeDate, createDate: System.GreenwichMeanTime;
byteLength: LONG CARDINAL;
parent: File.Capability;
[readDate, writeDate, createDate, byteLength, parent]
← Directory.GetProps
[cap, lagName
! ANY => {DisplayMsg[nextName, " ???"L]; LOOP}];
DisplayMsg[lagName, " "L];
{size: STRING ← [12];
lim: NAT ← lagName.length + 1;
String.AppendLongDecimal[size, byteLength];
lim ← lim + size.length;
THROUGH (lim..36] DO data.out.PutChar[' ] ENDLOOP;
data.out.PutF["%g", [string[size]]];
size.length ← 0;
String.AppendLongDecimal[size, pages];
THROUGH (size.length..5] DO data.out.PutChar[' ] ENDLOOP;
data.out.PutF["%g", [string[size]]];
};
data.out.PutF["p %g", [time[createDate]] ];
};
name =>
{IF fileCount = 0 THEN data.out.PutChar['\n];
data.out.PutChar[' ];
FOR i: NAT IN [pathNameLen..nextName.length) DO
data.out.PutChar[nextName[i]];
ENDLOOP;
};
ENDCASE => ERROR;

fileCount ← fileCount + 1;
pageCount ← pageCount + pages;
IF single AND command # getboth THEN EXIT;
ENDLOOP;

data.stopWanted ← FALSE;
EXITS out => {
data.stopWanted ← FALSE}};
IF volumeOpened THEN {
IF workingDir # NIL AND workingDir.length > 0 THEN {
Directory.ModifyContext[oldContext, workingDir]};
DirectoryExtras.ForgetVolumes[];
Volume.Close[clientVolume];
volumeOpened ← FALSE};
Storage.FreeString[fileName];
Storage.FreeString[pathName];
Storage.FreeString[localName];
Storage.FreeString[workingDir];
Storage.FreeString[scratch];
data.out.PutF["\n%g %g files, %g pages",
[rope[SELECT command FROM
get, getboth => "Copied",
delete => "Deleted",
list => "Listed",
name => "Named",
ENDCASE => ERROR]],
[cardinal[fileCount]],
[cardinal[pageCount]] ];
};

SingleFilePattern: PROC [pattern: STRING] RETURNS [BOOLEAN] = TRUSTED {
patlen: NAT = IF pattern = NIL THEN 0 ELSE pattern.length;
pos: NAT ← 0;
FOR pos IN [pos..patlen) DO
c: CHAR = pattern[pos];
IF c = '* OR c = '# OR c = ' THEN RETURN [FALSE];
ENDLOOP;
RETURN [TRUE]};

Match: PROC [pattern: STRING, patpos: NAT, object: STRING, objpos: NAT,
isGetBoth: BOOL] RETURNS [BOOL] = TRUSTED {
-- return TRUE if the object matches the pattern,
-- FALSE if it does not match. The pattern may contain
-- * characters which will match 0 or more characters
-- in the object. Case does not matter.
-- If isGetBoth, then .bcd and .mesa extensions also match.
-- Blanks in the pattern separate alternate matches

submatch: PROC
[i1: NAT, len1: INTEGER, i2: NAT, len2: INTEGER, isGetBoth: BOOLEAN]
RETURNS [BOOLEAN] = TRUSTED {
DO
c1: CHARIF len1 > 0 THEN Lower[pattern[i1]] ELSE ' ;
IF c1 = ' THEN
{IF len2 = 0 THEN RETURN [TRUE];
IF NOT isGetBoth THEN RETURN [FALSE];
RETURN [Match[".mesa"L, 0, object, i2, FALSE]
OR Match[".bcd"L, 0, object, i2, FALSE]]};
IF c1 = '* THEN {
-- quick kill for * at end of pattern
IF len1 = 1 THEN RETURN [TRUE];
-- else must take all combinations
{j1: NAT ← i1 + 1;
nlen1: INTEGER ← len1 - 1;
j2: NAT ← i2;
nlen2: INTEGER ← len2;
WHILE nlen2 >= 0 DO
IF submatch[j1, nlen1, j2, nlen2, isGetBoth] THEN RETURN [TRUE];
j2 ← j2 + 1;
nlen2 ← nlen2 - 1;
ENDLOOP};
RETURN [FALSE];
};
IF len2 <= 0 THEN RETURN [FALSE];
-- at this point demand an exact match in both strings
{c2: CHAR ← Lower[object[i2]];
IF c1 # c2 AND c1 # '# THEN
-- mismatch, so reject this alternative
RETURN [FALSE];
};
i1 ← i1 + 1;
len1 ← len1 - 1;
i2 ← i2 + 1;
len2 ← len2 - 1;
ENDLOOP;
}; -- submatch
patlen: CARDINAL = IF pattern = NIL THEN 0 ELSE pattern.length;
objlen: CARDINAL = IF object = NIL THEN 0 ELSE object.length;

DO -- cycle through all of the alternatives in the pattern
WHILE patpos < patlen AND pattern[patpos] = ' DO
-- skip over blanks
patpos ← patpos + 1;
ENDLOOP;
IF patpos >= patlen THEN RETURN [FALSE];
IF submatch[patpos, patlen-patpos, objpos, objlen-objpos, isGetBoth]
THEN RETURN [TRUE];
WHILE patpos < patlen AND pattern[patpos] # ' DO
-- skip over non-blanks
patpos ← patpos + 1;
ENDLOOP;
ENDLOOP;
};

Lower: PROC [c: CHAR] RETURNS [CHAR] = INLINE {
IF c IN ['A..'Z]
THEN RETURN['a + (c-'A)]
ELSE RETURN[c]};

-- Initialization and Window Management

MyData: TYPE = REF MyDataRec;

MyDataRec: TYPE = RECORD[
out: IO.STREAM,
cpText, localText: ViewerClasses.Viewer,
stopWanted: BOOLFALSE];

Create: Commander.CommandProc = TRUSTED {
--PROC [cmd: Handle];
maxW: INTEGER;
v: ViewerClasses.Viewer = Containers.Create[
info: [name: "ClientFileTool", column: right, scrollable: FALSE, iconic: FALSE]];
data: MyData = NEW[MyDataRec];
child: ViewerClasses.Viewer ← NIL;
x: INTEGER ← 2;
y: INTEGER ← 0;
CommandButton: PROC
[name: Rope.ROPE, proc: Buttons.ButtonProc, newline: BOOL] = TRUSTED {
child ← Buttons.Create[
info: [name: name, parent: v, border: TRUE,
wy: y, wx: x, ww: maxW],
proc: proc,
clientData: data,
fork: TRUE,
paint: TRUE];
x ← IF newline THEN 2 ELSE child.wx + maxW + 2;
y ← IF newline THEN child.wy + child.wh + 2 ELSE child.wy;
};
LabelText: PROC[name, data: Rope.ROPE]
RETURNS[ViewerClasses.Viewer] = TRUSTED {
child ← ViewerTools.MakeNewTextViewer[
info: [parent: v, ww: 999, wh: child.wh, scrollable: FALSE,
data: data,
border: FALSE,
wx: x + maxW + 2, wy: y],
paint: TRUE ];
Containers.ChildXBound[v, child];
[] ← Buttons.Create[
info: [name: name, parent: v,
border: FALSE, wx: x, wy: y],
proc: TextLabelProc, clientData: child, fork: FALSE, paint: TRUE];
x ← 2;
y ← child.wy + child.wh + 2;
RETURN[child]
};
Rule: PROC = TRUSTED {
child ← Rules.Create[
info: [parent: v, border: FALSE,
wy: y, wx: 0, ww: v.ww, wh: 1],
paint: TRUE ];
Containers.ChildXBound[v, child];
x ← 2;
y ← child.wy + child.wh + 2;
};
{
-- kludge to find max button size! --
temp: ViewerClasses.Viewer = Buttons.Create[
info: [name: "ClientFile:", parent: v, border: FALSE,
wx: 0, wy: 0],
proc: NIL, clientData: NIL, fork: FALSE, paint: FALSE];
maxW ← temp.ww;
Buttons.Destroy[temp];
};
CommandButton["Stop", Stop, FALSE];
CommandButton["Get", GetFileFromClientVolume, FALSE];
CommandButton["GetBoth", GetBothFilesOnClientVolume, FALSE];
CommandButton["List", ListFilesOnClientVolume, FALSE];
CommandButton["Names", NameFilesOnClientVolume, FALSE];
CommandButton["Delete", DeleteFilesFromClientVolume, TRUE];
data.cpText ← LabelText["ClientFile:", NIL];
data.localText ← LabelText["LocalFile:", NIL];
Rule[];
child ← TypeScript.Create[
info: [parent: v, wh: v.ch - y, ww: v.cw,
border: FALSE,
wy: y, wx: x] ];
data.out ← ViewerIO.CreateViewerStreams[NIL, child].out;
Containers.ChildXBound[v, child];
Containers.ChildYBound[v, child];
};

TextLabelProc: Buttons.ButtonProc =
-- parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton, shift, control: BOOL
BEGIN
text: ViewerClasses.Viewer = NARROW[clientData];
SELECT mouseButton FROM
red => ViewerTools.SetSelection[text, NIL];
blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] };
yellow => NULL;
ENDCASE => ERROR;
END;

-- copied for now (will get exported from CascadeExec)

clientVolume: Volume.ID ← Volume.nullID;
clientLabel: STRING = [Volume.maxNameLength];

FindClientVolume: ENTRY PROCEDURE = TRUSTED {
myType: Volume.Type = Volume.GetType[Volume.systemID];
clientType: Volume.TypeSet ← [];
IF clientVolume # Volume.nullID THEN RETURN;
IF myType = FIRST[Volume.Type] THEN RETURN;
clientType[PRED[myType]] ← TRUE;
UNTIL (clientVolume ← Volume.GetNext[clientVolume, clientType]) = Volume.nullID DO
bootFiles: Boot.LVBootFiles;
SpecialVolume.GetLogicalVolumeBootFiles[clientVolume, @bootFiles];
IF bootFiles[pilot].fID ~= File.nullID
AND Volume.GetAttributes[clientVolume].volumeSize > 1000 -- to ignore othello, booter! --
THEN { Volume.GetLabelString[clientVolume, clientLabel]; EXIT };
ENDLOOP;
};

-- Initialization

Commander.Register[key: "ClientFileTool", proc: Create,
doc: "Access to files on the client volume"];

END.