FileTransferCommandsImpl.mesa
Last edit by Michael Plass on May 2, 1983 1:34 pm
DIRECTORY
CIFS,
Commander,
Environment,
FileIO,
FileLookup,
FtpMan,
IO,
Process,
RefText,
DirectoryList,
Rope,
System,
UserCredentials;
FileTransferCommandsImpl: CEDAR MONITOR
IMPORTS CIFS, Commander, FileIO, FileLookup, FtpMan, IO, Process, RefText, DirectoryList, Rope, UserCredentials
~ BEGIN
defaultStoreTextServer: Rope.ROPE ← "Maxc";
defaultPublicServer: Rope.ROPE ← "Indigo";
defaultPersonalServer: Rope.ROPE ← "Ivy";
pauseAmount: Process.Milliseconds ← 1000;
DoOperationProc: TYPE ~ PROC [host, directory, filename: Rope.ROPE, log: IO.STREAM] RETURNS [quit: BOOLEANFALSE];
StoreTextCommand: Commander.CommandProc ~ {DoOpForEachFile[StoreTextOp, cmd, defaultStoreTextServer, defaultStoreTextServer, local]};
StoreCommand: Commander.CommandProc ~ {DoOpForEachFile[StoreOp, cmd, defaultPublicServer, defaultPersonalServer, local]};
RetrieveCommand: Commander.CommandProc ~ {DoOpForEachFile[RetrieveOp, cmd, defaultPublicServer, defaultPersonalServer, remote]};
ListRemoteCommand: Commander.CommandProc ~ {DoOpForEachFile[ListRemoteOp, cmd, defaultPublicServer, defaultPersonalServer, remoteAll]};
ListVerboseCommand: Commander.CommandProc ~ {DoOpForEachFile[ListVerboseOp, cmd, defaultPublicServer, defaultPersonalServer, remoteAll]};
StoreTextOp: ENTRY DoOperationProc ~ TRUSTED {
ENABLE UNWIND => NULL; {ENABLE CIFS.Error => {log.PutRope[" "]; log.PutRope[error]; log.PutRope["\n"]; GOTO Quit};
tempStream: IO.STREAM ← FileIO.Open["StoreText$$$", overwrite];
source: IO.STREAM ← FileIO.Open[filename ! FileIO.OpenFailed => CHECKED {
log.PutRope[fileName];
IF why = fileNotFound THEN log.PutRope[" not found.\n"]
ELSE IF why = illegalFileName THEN log.PutRope[": illegal file name.\n"]
ELSE log.PutRope[": open error.\n"];
GOTO Quit
}];
text: REF TEXT ← RefText.ObtainScratch[1024];
version: INT;
WHILE NOT source.EndOf DO
nBytesRead: NAT ← source.GetBlock[text];
tempStream.PutBlock[text, 0, nBytesRead];
ENDLOOP;
tempStream.Close;
source.Close;
RefText.ReleaseScratch[text];
log.PutF["Storing text into [%g]%g%g", IO.rope[host], IO.rope[directory], IO.rope[filename]];
version ← FtpMan.Store[host, PutInSlashes[directory].Concat[filename], FileIO.CapabilityFromStream[FileIO.Open["StoreText$$$"]]];
log.PutF["!%g\n", IO.int[version]];
EXITS Quit => {quit←TRUE}}};
StoreOp: DoOperationProc ~ TRUSTED {
ENABLE UNWIND => NULL; {ENABLE CIFS.Error => {log.PutRope[" "]; log.PutRope[error]; log.PutRope["\n"]; GOTO Quit};
source: CIFS.OpenFile ← CIFS.Open[filename, CIFS.read];
version: INT;
log.PutF["Storing [%g]%g%g", IO.rope[host], IO.rope[directory], IO.rope[filename]];
version ← FtpMan.Store[host, PutInSlashes[directory].Concat[filename], CIFS.GetFC[source]];
source.Close;
log.PutF["!%g\n", IO.int[version]];
EXITS Quit => {quit←TRUE}}};
RetrieveOp: DoOperationProc ~ TRUSTED {
ENABLE UNWIND => NULL; {ENABLE CIFS.Error => {log.PutRope[" "]; log.PutRope[error]; log.PutRope["\n"]; GOTO Quit};
dest: CIFS.OpenFile ← CIFS.Open[RemoveVersionNumber[filename], CIFS.replace];
log.PutF["Retrieving [%g]%g%g . . . ", IO.rope[host], IO.rope[directory], IO.rope[filename]];
FtpMan.Retrieve[host, PutInSlashes[directory].Concat[filename], CIFS.GetFC[dest]];
dest.Close;
log.PutF["Done.\n"];
EXITS Quit => {quit←TRUE}}};
ListRemoteOp: DoOperationProc ~ {
log.PutF["[%g]%g%g\n", IO.rope[host], IO.rope[directory], IO.rope[filename]];
};
paddedNameLength: INT ← 41;
ListVerboseOp: DoOperationProc ~ {
result: FileLookup.Result;
version: CARDINAL;
create: System.GreenwichMeanTime;
count: LONG CARDINAL;
nameLength: INT ~ host.Length + directory.Length + filename.Length + 2;
log.PutF["[%g]%g%g", IO.rope[host], IO.rope[directory], IO.rope[filename]];
THROUGH [nameLength..paddedNameLength) DO log.PutChar[' ] ENDLOOP;
[result: result, version: version, create: create, count: count] ← FileLookup.LookupFile[host, directory.Concat[filename]];
SELECT result FROM
noResponse => log.PutF["no response from %g.\n", IO.rope[host]];
noSuchName => log.PutRope["does not exist.\n"];
noSuchPort => log.PutRope["no such port.\n"];
ok => log.PutF[" Of %g --%5d\n", IO.time[create], IO.card[count]];
ENDCASE => ERROR;
};
ParsePath: PROC [name: Rope.ROPE] RETURNS [host, directory, filename: Rope.ROPENIL] ~ {
hostStart, hostEnd, directoryStart, directoryEnd: INT ← 0;
filenameStart, filenameEnd: INT ← name.Length;
The interprtation of state is as follows:
0 [ 1 ] 2 < 3 > 4
state: NAT ← 0;
loc: INT ← 0;
Action: PROC [c: CHAR] RETURNS [BOOLFALSE] ~ {
SELECT state FROM
0 => IF c='[ THEN {hostStart ← loc+1; state ← 1}
ELSE IF c='< THEN {directoryStart ← loc; directoryEnd ← name.Length; state ← 3}
ELSE {directoryStart ← directoryEnd ← filenameStart ← loc; state ← 3};
1 => IF c='] THEN {hostEnd ← loc; state ← 2} ELSE IF NOT Rope.Letter[c] THEN RETURN [TRUE];
2 => IF c='< THEN {directoryStart ← loc; directoryEnd ← name.Length; state ← 3}
ELSE {directoryStart ← directoryEnd ← filenameStart ← loc; state ← 3};
3 => IF c='> THEN {directoryEnd ← filenameStart ← loc+1};
ENDCASE => ERROR;
loc ← loc+1;
};
IF name.Map[action: Action] OR state # 3 THEN RETURN;
host ← name.Substr[start: hostStart, len: hostEnd-hostStart];
directory ← name.Substr[start: directoryStart, len: directoryEnd-directoryStart];
filename ← name.Substr[start: filenameStart, len: filenameEnd-filenameStart];
};
PutInSlashes: PROC [dir: Rope.ROPE] RETURNS [Rope.ROPE] ~ {
Translator: PROC [old: CHAR] RETURNS [new: CHAR] ~ {
new ← IF old = '> THEN '/ ELSE old;
};
RETURN [dir.Translate[start: 1, translator: Translator]];
};
TakeOutSlashes: PROC [dir: Rope.ROPE] RETURNS [Rope.ROPE] ~ {
Translator: PROC [old: CHAR] RETURNS [new: CHAR] ~ {
new ← IF old = '/ THEN '> ELSE old;
};
RETURN [Rope.Concat["<", dir.Translate[translator: Translator]]];
};
GetUserName: PROC RETURNS [Rope.ROPE] ~ {
name: Rope.ROPE ← UserCredentials.GetUserCredentials[].name;
length: INT ← 0;
WHILE length<name.Length AND name.Fetch[length] # '. DO length ← length + 1 ENDLOOP;
RETURN [name.Substr[len: length]];
};
SupplyTopLevel: PROC [directory: Rope.ROPE] RETURNS [Rope.ROPE] ~ {
RETURN [IF directory.Length = 0 OR directory.Fetch[0] # '< THEN Rope.Cat["<", GetUserName[], ">", directory] ELSE directory]
};
RemoveVersionNumber: PROC [filename: Rope.ROPE] RETURNS [Rope.ROPE] ~ {
FOR i: INT IN [0..filename.Length) DO
IF filename.Fetch[i] = '! THEN RETURN [filename.Substr[len: i]];
ENDLOOP;
RETURN [filename];
};
CompareFileNames: PROC [a, b: Rope.ROPE] RETURNS [Environment.Comparison] = {
versionStart: INT ← -1;
FOR i: INT IN [0..MIN[a.Length, b.Length]) DO
aChar: CHAR ~ a.Fetch[i];
bChar: CHAR ~ b.Fetch[i];
IF Rope.Upper[aChar] < Rope.Upper[bChar] THEN RETURN[less];
IF Rope.Upper[aChar] > Rope.Upper[bChar] THEN RETURN[greater];
IF aChar = '! THEN {versionStart ← i+1; EXIT};
ENDLOOP;
IF versionStart < 0 THEN RETURN[SELECT a.Length FROM < b.Length => less, = b.Length => equal, > b.Length => greater, ENDCASE => ERROR]
ELSE {
aVersion: INT ← ValueOf[a, versionStart];
bVersion: INT ← ValueOf[b, versionStart];
RETURN[SELECT aVersion FROM < bVersion => less, = bVersion => equal, > bVersion => greater, ENDCASE => ERROR]
};
};
EqualNames: PROC [a, b: Rope.ROPE] RETURNS [BOOLEAN] = {
FOR i: INT IN [0..MIN[a.Length, b.Length]) DO
aChar: CHAR ~ a.Fetch[i];
bChar: CHAR ~ b.Fetch[i];
IF Rope.Upper[aChar] # Rope.Upper[bChar] THEN RETURN[FALSE];
IF aChar = '! THEN RETURN[TRUE];
ENDLOOP;
RETURN[a.Length = b.Length];
};
IsNat: PROC [rope: Rope.ROPE] RETURNS [BOOLEAN] = {
IsNonDigit: PROC [c: CHAR] RETURNS [BOOL] ~ {
RETURN[NOT (c IN ['0..'9])]
};
RETURN [rope.Length IN [1..4] AND NOT rope.Map[action: IsNonDigit]];
};
ValueOf: PROC [rope: Rope.ROPE, start: INT ← 0] RETURNS [value: NAT ← 0] = {
Action: PROC [c: CHAR] RETURNS [BOOLFALSE] ~ {
d: [0..10) ← c - '0;
value ← value * 10 + d;
};
[] ← rope.Map[action: Action, start: start];
};
HasVersion: PROC [filename: Rope.ROPE] RETURNS [BOOLEAN] ~ {
Action: PROC [c: CHAR] RETURNS [BOOL] ~ {
RETURN[c = '!]
};
RETURN [filename.Map[action: Action]]
};
HasWildCard: PROC [filename: Rope.ROPE] RETURNS [BOOLEAN] ~ {
Action: PROC [c: CHAR] RETURNS [BOOL] ~ {
RETURN[c = '*]
};
RETURN [filename.Map[action: Action]]
};
WildcardExpansion: TYPE ~ {local, remote, remoteAll};
DoOpForEachFile: PROC [DoOperation: DoOperationProc, cmd: Commander.Handle, defaultPublicServer: Rope.ROPE, defaultPersonalServer: Rope.ROPE, wildcardExpansion: WildcardExpansion] ~ {
offset: INT ← 0;
log: IO.STREAM ← cmd.out;
commands: IO.STREAMIO.CreateInputStreamFromRope[cmd.commandLine];
Separators: IO.BreakProc ~ {RETURN[SELECT char FROM
IO.SP, IO.CR, IO.TAB, ',, '; => sepr,
ENDCASE => other
]};
UNTIL commands.EndOf OR UserAbort[cmd] DO
name: Rope.ROPE ~ commands.GetToken[Separators];
host, directory, filename: Rope.ROPE;
IF name.Length = 0 THEN EXIT;
[host, directory, filename] ← ParsePath[name];
IF filename = NIL THEN log.PutF["Invalid name: %g\n", IO.rope[name]]
ELSE {
IF host.Length = 0 THEN host ← IF directory.Length = 0 OR directory.Fetch[0] # '< THEN defaultPersonalServer ELSE defaultPublicServer;
directory ← SupplyTopLevel[directory];
IF HasWildCard[directory] OR HasWildCard[filename] OR wildcardExpansion # local THEN {
IF wildcardExpansion = local THEN {
IF HasWildCard[directory] THEN
log.PutF["%g: illegal wildcard in directory.\n", IO.rope[directory]]
ELSE {
AbortProc: PROC RETURNS [BOOLEAN] ~ {RETURN [UserAbort[cmd]]};
matchingFiles: LIST OF Rope.ROPE ← DirectoryList.Local[filename, AbortProc];
FOR r: LIST OF Rope.ROPE ← matchingFiles, r.rest UNTIL r=NIL OR UserAbort[cmd] DO
fname: Rope.ROPE ← r.first;
IF DoOperation[host, directory, fname, log] THEN IO.SetUserAbort[cmd.in];
ENDLOOP;
};
}
ELSE {
searchPattern: Rope.ROPE ← directory.Concat[filename];
AbortProc: PROC RETURNS [BOOLEAN] ~ {RETURN [UserAbort[cmd]]};
matchingFiles: LIST OF Rope.ROPE;
errorMsg: Rope.ROPE;
IF wildcardExpansion # remoteAll AND NOT HasVersion[filename] THEN
searchPattern ← searchPattern.Concat["!h"];
[matchingFiles, errorMsg] ← DirectoryList.Remote[host, searchPattern, AbortProc];
IF errorMsg.Length > 0 THEN {
log.PutF["[%g]%g: %g\n", IO.rope[host], IO.rope[searchPattern], IO.rope[errorMsg]];
}
ELSE {
FOR r: LIST OF Rope.ROPE ← matchingFiles, r.rest UNTIL r=NIL OR UserAbort[cmd] DO
fullName: Rope.ROPE ← r.first;
dir, fname: Rope.ROPE;
[directory: dir, filename: fname] ← ParsePath[fullName];
IF DoOperation[host, dir, fname, log] THEN IO.SetUserAbort[cmd.in];
ENDLOOP
}
}
}
ELSE IF DoOperation[host, directory, filename, log] THEN IO.SetUserAbort[cmd.in];
};
ENDLOOP;
IF commands.EndOf THEN log.PutRope["Done."] ELSE log.PutRope["Aborted."];
};
DelVerCommand: Commander.CommandProc ~ {
offset: INT ← 0;
log: IO.STREAM ← cmd.out;
commands: IO.STREAMIO.CreateInputStreamFromRope[cmd.commandLine];
keepRope: Rope.ROPE;
keep: NAT;
Separators: IO.BreakProc ~ {RETURN[SELECT char FROM
IO.SP, IO.CR, IO.TAB, ',, '; => sepr,
ENDCASE => other
]};
keepRope ← commands.GetToken[Separators];
IF NOT IsNat[keepRope] OR (keep ← ValueOf[keepRope]) = 0 THEN {
log.PutRope["First parameter is the number of versions to keep, and must be positive."];
RETURN;
};
UNTIL commands.EndOf OR UserAbort[cmd] DO
name: Rope.ROPE ~ commands.GetToken[Separators];
host, directory, filename: Rope.ROPE;
IF name.Length = 0 THEN EXIT;
[host, directory, filename] ← ParsePath[name];
IF host.Length = 0 OR directory.Length = 0 OR directory.Fetch[0] # '< THEN {
log.PutF["%g: DelVerKeep requires explicit host and top-level directory\n", IO.rope[name]];
}
ELSE {
pattern: Rope.ROPE ← RemoveVersionNumber[directory.Concat[filename]].Concat["*!*"];
filesToDelete: LIST OF Rope.ROPE;
errorMsg: Rope.ROPE;
AbortProc: PROC RETURNS [BOOLEAN] ~ {RETURN [UserAbort[cmd]]};
log.PutRope["Enumerating remote directory . . . "];
[filesToDelete, errorMsg] ← DirectoryList.Remote[host, pattern, AbortProc];
IF errorMsg.Length # 0 THEN {
log.PutF["%g: %g\n", IO.rope[name], IO.rope[errorMsg]];
}
ELSE {
log.PutRope["done.\n"];
filesToDelete ← SortRopeList[filesToDelete];
filesToDelete ← RemoveHighestVersions[filesToDelete, keep];
IF filesToDelete = NIL THEN log.PutF["%g: No old versions to delete.\n", IO.rope[name]];
UNTIL filesToDelete = NIL OR UserAbort[cmd] DO
file: Rope.ROPE ← filesToDelete.first;
filesToDelete ← filesToDelete.rest;
log.PutF["Deleting [%g]%g ", IO.rope[host], IO.rope[file]];
Process.Pause[Process.MsecToTicks[pauseAmount]];
IF NOT UserAbort[cmd] THEN {
msg: Rope.ROPE ← "done.";
log.PutRope[". . . "];
TRUSTED {FtpMan.Delete[host, PutInSlashes[file] ! CIFS.Error => {msg ← error; CONTINUE}]};
log.PutRope[msg];
log.PutRope["\n"];
};
ENDLOOP;
};
};
ENDLOOP;
IF commands.EndOf THEN log.PutRope["Done."] ELSE log.PutRope["Aborted."];
};
SortRopeList: PROC [l: LIST OF Rope.ROPE] RETURNS [LIST OF Rope.ROPE] ~ {
The sort is destructive and NOT stable, that is, the relative positions in the result of
nodes with equal keys is unpredictible. For a nondestructive sort, copy l first.
The sort does one CONS.
a, b, mergeTo: LIST OF Rope.ROPE;
mergeToCons: LIST OF Rope.ROPE = CONS[NIL, NIL];
max: CARDINAL = 22; --number of bits in word-address space minus 2
lists: ARRAY [0..max) OF LIST OF Rope.ROPE ← ALL [NIL];
lists[0] is a sorted list of length 2 or NIL, lists[1] is a sorted list of length
4 or NIL, lists[2] is a sorted list of length 8 or NIL, etc.
When Sort returns, lists = ALL [NIL] again (we do some extra work to assure this).
x: CARDINAL; --[0..max]
xMax: CARDINAL ← 0; --[0..max)
make each pair of consecutive elements of l into a sorted list of length 2,
then merge it into lists.
UNTIL (a ← l) = NIL OR (b ← a.rest) = NIL DO
l ← b.rest;
IF CompareFileNames[a.first, b.first] = less THEN {
a.rest ← b; b.rest ← NIL }
ELSE {
b.rest ← a; a.rest ← NIL; a ← b };
x ← 0;
DO
IF (b ← lists[x]) = NIL THEN {
lists[x] ← a; EXIT }
ELSE { --merge (equal length) lists a and b
lists[x] ← NIL;
mergeTo ← mergeToCons;
DO --assert a#NIL, b#NIL
IF CompareFileNames[a.first, b.first] = less THEN {
mergeTo.rest ← a; mergeTo ← a;
IF (a ← a.rest) = NIL THEN { mergeTo.rest ← b; EXIT } }
ELSE { 
mergeTo.rest ← b; mergeTo ← b;
IF (b ← b.rest) = NIL THEN { mergeTo.rest ← a; EXIT } }
ENDLOOP;
a ← mergeToCons.rest;
x ← x+1;
IF x > xMax AND (xMax ← x) = max THEN ERROR }
ENDLOOP;
ENDLOOP;
xMax now contains the largest x such that lists[x] # NIL.
if l's length was even, a = NIL; if l's length was odd, a = single element list.
merge a and elements of lists into result (held in a).
x ← 0;
IF a = NIL THEN {
try to make a # NIL.
UNTIL (lists[x] # NIL OR x = xMax) DO x ← x+1 ENDLOOP;
a ← lists[x]; lists[x] ← NIL; x ← x+1 };
a # NIL OR x > xMax.
UNTIL x > xMax DO
IF (b ← lists[x]) # NIL THEN {
lists[x] ← NIL;
mergeTo ← mergeToCons;
DO
a#NIL AND b#NIL
IF CompareFileNames[a.first, b.first] = less THEN {
mergeTo.rest ← a; mergeTo ← a;
IF (a ← a.rest) = NIL THEN { mergeTo.rest ← b; EXIT } }
ELSE { 
mergeTo.rest ← b; mergeTo ← b;
IF (b ← b.rest) = NIL THEN { mergeTo.rest ← a; EXIT } }
ENDLOOP;
a ← mergeToCons.rest };
x ← x+1;
ENDLOOP;
RETURN [a]
};--SortRopeList
Reverse: PROC [list: LIST OF Rope.ROPE] RETURNS [LIST OF Rope.ROPE] ~ {
new: LIST OF Rope.ROPENIL;
WHILE list # NIL DO
current: LIST OF Rope.ROPE ← list;
list ← list.rest;
current.rest ← new;
new ← current;
ENDLOOP;
RETURN [new]
};
RemoveHighestVersions: PROC [files: LIST OF Rope.ROPE, numberToKeep: NAT] RETURNS [LIST OF Rope.ROPE] ~ {
list: LIST OF Rope.ROPE ← Reverse[files];
delete: LIST OF Rope.ROPENIL;
keep: LIST OF Rope.ROPENIL;
n: NAT ← 0;
UNTIL list = NIL DO
current: LIST OF Rope.ROPE ← list;
list ← list.rest;
current.rest ← NIL;
IF keep = NIL OR NOT EqualNames[keep.first, current.first] THEN {
n ← 1; current.rest ← keep; keep ← current;
}
ELSE IF n < numberToKeep THEN {
n ← n + 1; current.rest ← keep; keep ← current;
}
ELSE {
current.rest ← delete; delete ← current;
};
ENDLOOP;
RETURN [delete];
};
UserAbort: PROC [cmd: Commander.Handle] RETURNS [BOOLEAN] ~ {
RETURN [cmd.in.UserAbort]
};
Commander.Register["StoreText", StoreTextCommand, "Store the text portion of Tioga files on a remote server."];
Commander.Register["Store", StoreCommand, "Store the files onto remote servers."];
Commander.Register["Retrieve", RetrieveCommand, "Retrieve the files from remote servers."];
Commander.Register["ListRemote", ListRemoteCommand, "List files on remote servers."];
Commander.Register["ListVerbose", ListVerboseCommand, "List info for files on remote servers."];
Commander.Register["DelVerKeep", DelVerCommand, "Delete old versions of files on remote servers."];
END.