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: BOOLEAN ← FALSE];
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.
ROPE ←
NIL] ~ {
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 [
BOOL←
FALSE] ~ {
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 [
BOOL←
FALSE] ~ {
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.STREAM ← IO.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.STREAM ← IO.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.ROPE ← NIL;
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.ROPE ← NIL;
keep: LIST OF Rope.ROPE ← NIL;
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.