-- File: ClientFileTool.mesa
-- Last edit by Russ Atkinson: May 13, 1982 3:14 pm
-- Schmidt: 25-Jul-81 12:09:21
-- NOTE: change the create date message when changing this beast
DIRECTORY
Ascii USING [CR],
Directory USING
[CreateFile, DeleteFile, Error, GetDefaultContext, GetNext, GetProps, GetWD,
Handle, Lookup, ModifyContext],
DirectoryExtras USING [ForgetVolumes],
Event USING[Notify, Reason],
File USING [
Capability, GetAttributes,
GetSize, grow, nullCapability, PageCount,
Permissions, read, SetSize, shrink, write],
FormSW USING [
AllocateItemDescriptor, ClientItemsProcType, CommandItem,
line0, line1, line2, nextPlace, ProcType, StringItem],
HeapString USING [AppendChar, AppendString],
Inline USING [LowHalf],
Put USING [Char, Blanks, Date, LongDecimal, Text],
Space USING [
CopyOut, Create, Delete, Handle, Map, Unmap, virtualMemory],
Storage USING [FreeString, String],
String USING [AppendLongDecimal, AppendString, EquivalentString],
Time USING [Current, Packed],
Tool USING [Create, MakeFormSW, MakeFileSW, MakeSWsProc],
ToolDriver USING [Address, NoteSWs],
Volume USING [
Close, GetAttributes, GetLabelString, GetNext, GetType, ID,
maxNameLength, nullID, PageCount, Type, TypeSet, systemID],
VolumeExtras USING [OpenVolume],
Window USING [Handle],
TTY USING [ResetUserAbort, UserAbort];
ClientFileTool: PROGRAM
IMPORTS
Directory, DirectoryExtras, Event, File, FormSW, HeapString, Inline,
Put, Space, Storage, String, Time, Tool, ToolDriver,
TTY, Volume, VolumeExtras
= BEGIN OPEN HeapString;
Bool: TYPE = BOOLEAN;
Char: TYPE = CHARACTER;
CommandDesc: TYPE = RECORD [cpName, localName: STRING ← NIL];
CommandType: TYPE = {get, list, delete, name, getboth};
copier: CommandDesc ← CommandDesc[NIL, NIL];
clientVolume: Volume.ID ← Volume.nullID;
volumeOpened: BOOLEAN ← FALSE; -- some slight protection here
window, cpMsgSW, cpFormSW: Window.Handle ← NIL;
-- Executive Commands Invoked from Menu
GetFileFromClientVolume: FormSW.ProcType = {
DoCommand[get]};
DeleteFilesFromClientVolume: FormSW.ProcType = {
DoCommand[delete]};
ListFilesOnClientVolume: FormSW.ProcType = {
DoCommand[list]};
NameFilesOnClientVolume: FormSW.ProcType = {
DoCommand[name]};
GetBothFilesOnClientVolume: FormSW.ProcType = {
DoCommand[getboth]};
DoCommand: PROC [command: CommandType] = {
OPEN HeapString;
volumeLabel: STRING ← "Client"L;
fileName, pathName, localName, workingDir, scratch: STRING ← NIL;
oldContext: Directory.Handle ← Directory.GetDefaultContext[];
fileCount, pageCount: LONG CARDINAL ← 0;
useLocal: Bool ← FALSE;
IF volumeOpened THEN
{DisplayMsg["Client volume already opened!"L]; RETURN};
IF clientVolume = Volume.nullID THEN
clientVolume ← MapClientVolumeNameToID[volumeLabel];
IF clientVolume = Volume.nullID THEN
{DisplayMsg["Client volume not found!"L]; RETURN};
fileName ← Storage.String[60];
pathName ← Storage.String[60];
localName ← Storage.String[60];
workingDir ← Storage.String[60];
scratch ← Storage.String[60];
Directory.GetWD[oldContext, workingDir];
AppendChar[@pathName, '<];
AppendString[@pathName, volumeLabel];
AppendString[@pathName, ">SysDir>"L];
IF copier.cpName # NIL THEN AppendString[@fileName, copier.cpName];
IF command = get AND copier.localName # NIL AND copier.localName.length > 0 THEN
{AppendString[@localName, copier.localName];
useLocal ← TRUE};
{single: Bool ← SingleFilePattern[fileName];
nextName: STRING ← [80];
pathNameLen: NAT ← pathName.length;
triedBcd: BOOL ← FALSE;
triedMesa: BOOL ← FALSE;
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];
DisplayNums
["-- Client volume -- #total pages: "L, volSize,
", #free pages: "L, 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 TTY.UserAbort[] THEN {
TTY.ResetUserAbort[];
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.Lookup
[fileName: scratch,
permissions: File.read+File.write+File.grow+File.shrink
! Directory.Error --[type]-- =>
{IF type # fileNotFound THEN
{DisplayMsg[localName, " has a problem"L]; LOOP};
CONTINUE}];
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];
Event.Notify[Event.Reason[newFiles]];
};
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: Time.Packed;
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;
IF lim < 36 THEN Put.Blanks[cpMsgSW, 36-lim];
Put.Text[cpMsgSW, size];
size.length ← 0;
String.AppendLongDecimal[size, pages];
IF size.length < 5 THEN Put.Blanks[cpMsgSW, 5-size.length];
Put.Text[cpMsgSW, size];
};
Put.Text[cpMsgSW, "p "L];
Put.Date[cpMsgSW, createDate];
};
name =>
{IF fileCount = 0 THEN Put.Char[cpMsgSW, Ascii.CR];
Put.Char[cpMsgSW, ' ];
FOR i: NAT IN [pathNameLen..nextName.length) DO
Put.Char[cpMsgSW, nextName[i]];
ENDLOOP;
};
ENDCASE => ERROR;
fileCount ← fileCount + 1;
pageCount ← pageCount + pages;
IF single AND command # getboth THEN EXIT;
ENDLOOP;
EXITS out => {}};
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];
{lead: STRING ←
SELECT command FROM
get, getboth => "-- Copied #files: "L,
delete => "-- Deleted #files: "L,
list => "-- Listed #files: "L,
name => "-- Named #files: "L,
ENDCASE => ERROR;
DisplayNums[lead, fileCount, ", #pages: "L, pageCount]};
};
DisplayNums: PROC [s1: STRING ← NIL, d1: LONG CARDINAL ← 0,
s2: STRING ← NIL, d2: LONG CARDINAL ← 0] = {
Put.Char[cpMsgSW, Ascii.CR];
IF s1 # NIL THEN
{Put.Text[cpMsgSW, s1]; Put.LongDecimal[cpMsgSW, d1]};
IF s2 # NIL THEN
{Put.Text[cpMsgSW, s2]; Put.LongDecimal[cpMsgSW, d2]};
};
SingleFilePattern: PROC [pattern: STRING] RETURNS [BOOLEAN] = {
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] = {
-- 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] = {
DO
c1: Char ← IF 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[LOOPHOLE[LOOPHOLE[c,CARDINAL]+40B]]
ELSE RETURN[c]};
-- Error Reporting Procedures
DisplayMsg: PROCEDURE [s1, s2: STRING ← NIL] = {
Put.Char[cpMsgSW, Ascii.CR];
IF s1 # NIL THEN Put.Text[cpMsgSW, s1];
IF s2 # NIL THEN Put.Text[cpMsgSW, s2]};
-- Initialization and Window Management
MakeSWs: Tool.MakeSWsProc = {
OPEN Tool;
addresses: ARRAY [0..2) OF ToolDriver.Address;
cpFormSW ← MakeFormSW[window: window, formProc: MakeForm];
cpMsgSW ← MakeFileSW[window: window, name: "ClientFileTool.log"L, h: 360];
addresses ← [["cpFormSW"L, cpFormSW], ["cpMsgSW"L, cpMsgSW]];
ToolDriver.NoteSWs["Executive"L, DESCRIPTOR[addresses]]};
MakeForm: FormSW.ClientItemsProcType = {
OPEN FormSW;
items ← AllocateItemDescriptor[7];
items[0]
← CommandItem
[tag: "Get"L, place: [20, line0],
proc: GetFileFromClientVolume];
items[1]
← CommandItem
[tag: "GetBoth"L, place: [-30, line0],
proc: GetBothFilesOnClientVolume];
items[2]
← CommandItem
[tag: "List"L, place: [-30, line0],
proc: ListFilesOnClientVolume];
items[3]
← CommandItem
[tag: "Names"L, place: [-30, line0],
proc: NameFilesOnClientVolume];
items[4]
← CommandItem
[tag: "DELETE!"L, place: [-50, line0],
proc: DeleteFilesFromClientVolume];
items[5] ← StringItem[
tag: "ClientFile"L, place: [0,line1], string: @copier.cpName, inHeap: TRUE];
items[6] ← StringItem[
tag: "LocalFile"L, place: [0,line2], string: @copier.localName, inHeap: TRUE];
RETURN[items: items, freeDesc: TRUE];
};
-- copied for now (will get exported from CascadeExec)
MapClientVolumeNameToID: PROCEDURE [name: STRING] RETURNS [volumeID: Volume.ID] = {
myType: Volume.Type = Volume.GetType[Volume.systemID];
debuggers: Volume.TypeSet ← [];
volumeID ← Volume.nullID;
IF myType = FIRST[Volume.Type] THEN RETURN;
debuggers[PRED[myType]] ← TRUE;
UNTIL (volumeID ← Volume.GetNext[volumeID, debuggers]) = Volume.nullID DO
volumeLabel: STRING ← [Volume.maxNameLength];
Volume.GetLabelString[volumeID, volumeLabel];
IF String.EquivalentString[volumeLabel, name] THEN EXIT;
ENDLOOP};
-- Initialization
Init: PROC = {
window ← Tool.Create
[name: "ClientFile Tool of May 13, 1982"L,
makeSWsProc: MakeSWs,
initialBox: [[300,100],[400,360]]];
Put.Text[cpMsgSW, "ClientFileTool started at "L];
Put.Date[cpMsgSW, Time.Current[]];
Put.Char[cpMsgSW, Ascii.CR];
};
Init[];
END.