FSFileCommandsImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) September 23, 1985 7:20:31 pm PDT
McCreight, January 30, 1986 5:37:08 pm PST
DIRECTORY
BasicTime USING [GMT, nullGMT],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [ArgumentVector, GetProp, Failed, Parse],
Convert USING [Error, IntFromRope],
DFUtilities USING [DateToStream],
FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, DirectoryContaining, GetShortName, HomeDirectory, IsADirectory, IsAPattern, ResolveRelativePath, StripVersionNumber],
FS USING [Close, Copy, Delete, EnumerateForNames, Error, ErrorDesc, ExpandName, FileInfo, Open, NameProc, nullOpenFile, OpenFile, Rename, SetKeep, StreamOpen],
FSBackdoor USING [Flush, EntryPtr, Enumerate, EnumerateCacheForNames, MakeFName, NameProc, ScavengeDirectoryAndCache, SetFreeboard, TextFromTextRep, VolumePages],
FSPseudoServers USING [DeletePseudoServer, GetPseudoServers, InsertPseudoServer, Lookup, PseudoServerFromRope, PseudoServerList],
IO USING [Close, EndOf, EndOfStream, Error, GetBlock, PutBlock, PutChar, PutF, PutFR, PutFR1, PutRope, PutText, Reset, STREAM, TextFromTOS, TOS],
List USING [PutAssoc],
Process USING [CheckForAbort],
ProcessProps USING [GetPropList],
ReadEvalPrint USING [Handle],
Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, Index, Length, Match, Replace, ROPE, Run, SkipTo, Substr, Text],
ViewerClasses USING [Viewer],
ViewerOps USING [PaintViewer];
FSFileCommandsImpl: CEDAR PROGRAM
IMPORTS Commander, CommandTool, Convert, DFUtilities, FileNames, FS, FSBackdoor, FSPseudoServers, IO, List, Process, ProcessProps, Rope, ViewerOps
= BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
FSErrorMsg: PROC [error: FS.ErrorDesc] RETURNS [ROPE] = {
SELECT error.group FROM
lock => RETURN[" -- locked!\n"];
ENDCASE =>
IF error.code = $unknownFile
THEN RETURN [" -- not found!\n"]
ELSE RETURN[Rope.Cat[" -- FS.Error: ", error.explanation, "\n"]];
};
FSErrorMsg1: PROC [error: FS.ErrorDesc] RETURNS [ROPE] = {
SELECT error.group FROM
lock => RETURN[" -- locked!"];
ENDCASE =>
IF error.code = $unknownFile
THEN RETURN [" -- not found!"]
ELSE RETURN[Rope.Concat["\n -- FS.Error: ", error.explanation]];
};
createKeep: CARDINAL ← 2;
DeleteFiles: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
out: STREAM = cmd.out;
exactLevelMatch: BOOLFALSE;
anglesRequired: INT ← 0;
deleteIt: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
Process.CheckForAbort[];
continue ← TRUE;
IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN;
IO.PutRope[out, " deleting "];
IO.PutRope[out, fullFName];
FS.Delete[fullFName
! FS.Error =>
IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}
];
IO.PutRope[out, "\n"];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
FOR i: NAT IN [1..argv.argc) DO
pattern: ROPE ← argv[i];
IF Rope.Match["-*", pattern] THEN {
This is a switch, not a file name
sense: BOOLTRUE;
FOR i: INT IN [1..Rope.Length[pattern]) DO
c: CHAR = Rope.Fetch[pattern, i];
SELECT c FROM
'~ => {sense ← NOT sense; LOOP};
'x, 'X => exactLevelMatch ← sense;
ENDCASE;
sense ← TRUE;
ENDLOOP;
LOOP;
};
{ENABLE FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GO TO Die};
pattern ← FileNames.ResolveRelativePath[pattern];
pattern ← FS.ExpandName[pattern].fullFName;
IF pattern.Length[] = 0 THEN LOOP;
IF exactLevelMatch THEN anglesRequired ← CountAngles[pattern];
IF Rope.Find[pattern, "!"] = -1 THEN pattern ← Rope.Concat[pattern, "!L"];
IF Rope.Find[pattern, "*"] = -1
THEN [] ← deleteIt[pattern]
ELSE FS.EnumerateForNames[pattern, deleteIt];
};
Process.CheckForAbort[];
ENDLOOP;
EXITS
Die => result ← $Failure;
};
TypeFile: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
block: REF TEXTNEW[TEXT[256]];
nBytesRead: NAT ← 0;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc < 2 THEN GOTO Usage;
FOR i: NAT IN [1..argv.argc) DO
fileStream: STREAMFS.StreamOpen[FileNames.ResolveRelativePath[argv[i]]
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GO TO Die}];
WHILE NOT IO.EndOf[fileStream] DO
nBytesRead ← IO.GetBlock[fileStream, block, 0, 256 !
IO.EndOfStream => EXIT;
IO.Error => EXIT;
];
IO.PutBlock[cmd.out, block, 0, nBytesRead !
IO.EndOfStream => EXIT;
IO.Error => EXIT;
];
Process.CheckForAbort[];
ENDLOOP;
IF fileStream # NIL THEN IO.Close[fileStream];
ENDLOOP;
EXITS
Die => result ← $Failure;
Usage => RETURN[$Failure, "Usage: Type list-of-patterns\n"];
};
PrintFSCacheInfo: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
volName: ROPENIL;
size, free, freeboard: INT;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc > 2 THEN {msg ← "Usage: PrintFSCacheInfo {volumeName}"; GO TO Die};
IF argv.argc = 2 THEN volName ← argv[1];
[size, free, freeboard] ← FSBackdoor.VolumePages[volName
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GOTO Die}];
msg ← IO.PutFR[" size = %g, free = %g, freeboard = %g\n",
[integer[size]], [integer[free]], [integer[freeboard]] ];
EXITS
Die => result ← $Failure;
};
SetKeep: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
pattern: ROPE;
keep: INT;
bangIndex: PROC [r: ROPE] RETURNS [INT] = {
FOR i: INT DECREASING IN [0 .. Rope.Length[r]) DO
IF Rope.Fetch[r, i] = '! THEN RETURN [i];
ENDLOOP;
RETURN [Rope.Length[r]];
};
setIt: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
fullFName ← Rope.Substr[fullFName, 0, bangIndex[fullFName]];
cmd.out.PutF[" processing %g", [rope[fullFName]]];
FS.SetKeep[fullFName, keep];
cmd.out.PutChar['\n];
RETURN[NOT AbortRequested[cmd]];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc # 3 THEN GO TO Usage;
keep ← Convert.IntFromRope[argv[1] ! Convert.Error => GO TO Usage];
pattern ← Rope.Replace[base: argv[2], start: bangIndex[argv[2]], with: "!H"];
FS.EnumerateForNames[pattern, setIt
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GO TO Die}];
EXITS
Die => result ← $Failure;
Usage => RETURN[$Failure, "Usage: SetKeep keep pattern"];
};
SetFSFreeboard: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
freeBoard: INT;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc # 2 THEN GOTO Usage;
freeBoard ← Convert.IntFromRope[argv[1] ! Convert.Error => GOTO Usage];
FSBackdoor.SetFreeboard[freeBoard
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GOTO Die;}];
EXITS
Usage => RETURN[$Failure, "Usage: SetFreeboard nPages"];
Die => result ← $Failure;
};
PrintWorkingDirectory: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
RETURN[NIL, FileNames.CurrentWorkingDirectory[]];
};
ChangeWorkingDirectory: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
wDir: ROPE;
root: BOOLFALSE;
push: BOOLFALSE;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO oops}];
list: LIST OF ROPENARROW[CommandTool.GetProp[cmd, $WorkingDirectoryStack]];
SELECT cmd.procData.clientData FROM
$CDR => root ← TRUE;
$PDR => root ← push ← TRUE;
$PD => push ← TRUE;
ENDCASE;
IF push THEN list ← CONS[FileNames.CurrentWorkingDirectory[], list];
SELECT argv.argc FROM
1 => wDir ← IF root THEN "///" ELSE FileNames.HomeDirectory[];
2 => {
wDir ← argv[1];
IF root THEN wDir ← FS.ExpandName[wDir, "///"
! FS.Error => {msg ← FSErrorMsg[error]; GO TO oops};
].fullFName;
};
ENDCASE => {msg ← "Usage: ChangeWorkingDirectory directoryName"; GO TO oops};
[result, msg] ← SetWD[cmd, wDir];
IF result # $Failure AND push THEN
cmd.propertyList ← List.PutAssoc[$WorkingDirectoryStack, list, cmd.propertyList];
EXITS oops => result ← $Failure;
};
PopWD: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
list: LIST OF ROPENARROW[CommandTool.GetProp[cmd, $WorkingDirectoryStack]];
IF list = NIL THEN RETURN[NIL, FileNames.CurrentWorkingDirectory[]];
[result, msg] ← SetWD[cmd, list.first];
list ← list.rest;
cmd.propertyList ← List.PutAssoc[key: $WorkingDirectoryStack, val: list, aList: cmd.propertyList];
};
FindExcessVersionsCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
Laggards: TYPE = RECORD [
s: SEQUENCE size: NAT OF RECORD [
name: ROPE,
bang: INT
]
];
laggards: REF Laggards ← NIL;
out: STREAM = cmd.out;
keep: (0..LAST[NAT]] ← 1;
lagIdx: INT ← 0; -- current interesting laggard is laggards[lagIdx]
inner: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
bang: INT ← Rope.Length[fullFName];
pos: INT ← bang;
continue ← TRUE;
WHILE pos > 0 DO
IF Rope.Fetch[fullFName, pos ← pos - 1] = '! THEN {bang ← pos; EXIT};
ENDLOOP;
IF laggards[lagIdx].bang = bang AND Rope.Run[laggards[lagIdx].name, 0, fullFName, 0, FALSE] >= bang THEN {
We have a duplicate file name that presumably differs only in version number
SELECT TRUE FROM
NOT kill => {
just list the name to the command output
IO.PutRope[cmd.out, laggards[lagIdx].name];
IO.PutRope[cmd.out, " "];
};
justCache => {
IO.PutRope[out, "\nflushing "];
IO.PutRope[out, laggards[lagIdx].name];
FSBackdoor.Flush[laggards[lagIdx].name
! FS.Error =>
IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}];
};
ENDCASE => {
IO.PutRope[cmd.out, "\ndeleting "];
IO.PutRope[cmd.out, laggards[lagIdx].name];
FS.Delete[laggards[lagIdx].name
! FS.Error =>
IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}];
};
};
laggards[lagIdx].name ← fullFName;
laggards[lagIdx].bang ← bang;
lagIdx ← (lagIdx+1) MOD laggards.size
};
kill: BOOLFALSE;
justCache: BOOLTRUE;
DoPattern: PROC [pattern: ROPE] = {
laggards ← NEW[Laggards[keep]];
FOR i: INT IN [0..laggards.size) DO laggards[i] ← [name: "", bang: -1] ENDLOOP;
lagIdx ← 0;
IF justCache THEN
FSBackdoor.EnumerateCacheForNames[pattern: pattern, proc: inner
! FS.Error =>
IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}]
ELSE
FS.EnumerateForNames[pattern: pattern, proc: inner
! FS.Error => IF error.group # bug THEN {
IO.PutRope[cmd.out, FSErrorMsg1[error]];
CONTINUE;
}];
IO.PutRope[cmd.out, "\n"];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO bogus}];
argBase: INT ← 1;
didPattern: BOOLFALSE;
WITH cmd.procData.clientData SELECT FROM
opt: ATOM =>
SELECT opt FROM
$FindReal => {kill ← FALSE; justCache ← FALSE};
$KillReal => {kill ← TRUE; justCache ← FALSE};
$FindCached => {kill ← FALSE; justCache ← TRUE};
$KillCached => {kill ← TRUE; justCache ← TRUE};
ENDCASE => ERROR;
ENDCASE;
WHILE (argv # NIL) AND (argBase < argv.argc) DO
arg: ROPE = argv[argBase];
IF Rope.Length[arg] > 0 AND Rope.Fetch[arg, 0] = '- THEN {
FOR j: INT IN [1..Rope.Length[arg]) DO
SELECT Rope.Fetch[arg, j] FROM
'k, 'K -- keep -- => {
k: INT;
IF NOT ((argBase ← argBase+1) < argv.argc) THEN {
msg ← "Missing number of versions to keep"; GO TO bogus};
k ← Convert.IntFromRope[argv[argBase] ! Convert.Error => {
msg ← "Can't parse number of versions to keep"; GO TO bogus}];
IF NOT (k IN [1..100]) THEN {
msg ← "Can't keep less than 1 nor more than 100 versions"; GO TO bogus};
keep ← k;
};
ENDCASE => NULL;
ENDLOOP; -- j
}
ELSE {DoPattern[arg]; didPattern ← TRUE};
argBase ← argBase+1;
ENDLOOP;
IF NOT didPattern THEN DoPattern["*"];
EXITS
bogus => result ← $Failure;
};
FSFlushCache: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
out: STREAM = cmd.out;
pattern: ROPE;
flushIt: FSBackdoor.NameProc = {
out.PutF[" flushing %g", [rope[fullGName]]];
FSBackdoor.Flush[fullGName ! FS.Error => {
IF error.group = $lock THEN {
out.PutRope[" -- Locked!"];
CONTINUE;
}
ELSE out.PutRope[" -- Error!\n"];
}];
out.PutRope["\n"];
RETURN[NOT AbortRequested[cmd]];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
FOR i: NAT IN [1..argv.argc) DO
pattern ← FileNames.ResolveRelativePath[argv[i]];
{ENABLE FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GO TO Die};
IF Rope.Find[pattern, "*"] = -1
THEN [] ← flushIt[pattern]
ELSE FSBackdoor.EnumerateCacheForNames[flushIt, NIL, pattern];
};
IF AbortRequested[cmd] THEN EXIT;
ENDLOOP;
EXITS
Die => result ← $Failure;
};
FSListBTree: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
out: STREAM ← cmd.out;
pattern: Rope.Text ← NIL;
printLocal: BOOLFALSE;
printAttached: BOOLFALSE;
printCached: BOOLTRUE;
anySwitch: BOOLFALSE;
localText: REF TEXTNEW[TEXT[64]];
localOut: STREAMIO.TOS[localText];
accept: PROC RETURNS [stop: BOOL] = {
IO.PutText[out, IO.TextFromTOS[localOut]];
RETURN[stop: AbortRequested[cmd]];
};
print: UNSAFE PROC
[entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOLFALSE] = UNCHECKED {
name: ROPE = FSBackdoor.MakeFName[
FSBackdoor.TextFromTextRep[@entry[entry.nameBody]], entry.version];
IO.Reset[localOut];
WITH e: entry^ SELECT FROM
local => IF printLocal THEN {
IO.PutF[localOut, "L %g\n", [rope[name]]];
accept ← TRUE;
};
attached => IF printAttached THEN {
toName: ROPE = FSBackdoor.TextFromTextRep[@entry[e.attachedTo]];
IO.PutF[localOut, "A %-30g (%g)\n", [rope[name]], [rope[toName]]];
accept ← TRUE;
};
cached => IF printCached THEN {
IO.PutF[localOut, "C %-40g ", [rope[name]]];
DFUtilities.DateToStream[localOut, [explicit, e.used] ];
IO.PutRope[localOut, "\n"];
accept ← TRUE;
};
ENDCASE => ERROR;
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc = 1
THEN
FSBackdoor.Enumerate[
volName: NIL, nameBodyPattern: "[*", localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: print, acceptProc: accept]
ELSE
FOR i: NAT IN [1..argv.argc) DO
IF argv[i].Fetch[0] = '- THEN {
IF NOT anySwitch THEN {
printLocal ← FALSE;
printAttached ← FALSE;
printCached ← FALSE;
anySwitch ← TRUE;
};
FOR j: INT IN [1..argv[i].Length[]) DO
SELECT argv[i].Fetch[j] FROM
'c => printCached ← TRUE;
'l => printLocal ← TRUE;
'a => printAttached ← TRUE;
ENDCASE;
ENDLOOP;
Process.CheckForAbort[];
LOOP;
};
pattern ← Rope.Flatten[FileNames.ResolveRelativePath[argv[i]]];
FSBackdoor.Enumerate[
volName: NIL, nameBodyPattern: pattern, localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: print, acceptProc: accept];
IF AbortRequested[cmd] THEN EXIT;
ENDLOOP;
EXITS
Die => result ← $Failure;
};
FSLoad: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
out: STREAM ← cmd.out;
fetch: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
file: FS.OpenFile ← FS.nullOpenFile;
out.PutRope[" loading "];
out.PutRope[fullFName];
{
file ← FS.Open[fullFName, read
! FS.Error =>
IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; GO TO err}
];
FS.Close[file];
EXITS err => {}};
out.PutRope["\n"];
RETURN[NOT AbortRequested[cmd]];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
FOR i: NAT IN [1..argv.argc) DO
pattern: ROPE ← FileNames.ResolveRelativePath[argv[i]];
IF pattern.Find["!"] = -1 THEN pattern ← Rope.Concat[pattern, "!H"];
FS.EnumerateForNames[pattern, fetch
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg1[error]; GO TO Die}];
IF AbortRequested[cmd] THEN EXIT;
ENDLOOP;
EXITS
Die => result ← $Failure;
};
FSScavenge: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
volName: ROPENIL;
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd, TRUE
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
IF argv.argc > 1 THEN volName ← argv[1];
FSBackdoor.ScavengeDirectoryAndCache[volName
! FS.Error => {msg ← FSErrorMsg[error]; GO TO Die}];
EXITS
Die => result ← $Failure;
};
There will be three forms of Copy:
Copy newFile ← oldFile
copy a single file to a new name
Copy directory ← list-of-files and patterns
copy a group of files to a new directory each with the same shortname as before
If the directory ← is omitted, files will be copied to the current working directory
Copy -c -- Copy, don't use Attach
CopyAndRename: Commander.CommandProc = {
[cmd: REF CommandObject] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [
in, out, err: STREAM, commandLine, command: ROPE,
propertyList: List.AList, procData: CommandProcHandle]
forceCopy: BOOLFALSE;
destinationDirectory: ROPENIL;
leftArrowExists: BOOL;
doACopy: BOOL ← cmd.procData.clientData = $Copy;
doStore: BOOL ← cmd.procData.clientData = $Store;
dirBeforeStar: ROPE;
dirBeforeStarLength: INT;
retainStructure: BOOLFALSE;
updateOnly: BOOLFALSE;
exactLevelMatch: BOOLFALSE;
anglesRequired: INT ← 0;
HandleAFile: PROC [to, from: ROPE] = {
Process.CheckForAbort[];
cmd.out.PutF[" %g ← %g", [rope[to]], [rope[from]]];
{ENABLE
FS.Error =>
IF error.group # bug THEN {msg ← FSErrorMsg1[error]; GO TO skipIt};
IF updateOnly THEN {
sourceTime: BasicTime.GMT ← BasicTime.nullGMT;
destTime: BasicTime.GMT ← BasicTime.nullGMT;
sourceTime ← FS.FileInfo[from].created;
destTime ← FS.FileInfo[to ! FS.Error => IF error.group # bug THEN CONTINUE].created;
IF sourceTime = destTime AND sourceTime # BasicTime.nullGMT THEN {
This file does not need a copy, since it has the same create date as the destination file. We have been instructed to trust the create date.
cmd.out.PutRope["\n -- not copied, create dates match"];
GO TO skipIt;
};
};
IF doACopy
THEN [] ← FS.Copy[from: from, to: to, keep: createKeep, attach: NOT forceCopy]
ELSE FS.Rename[from: from, to: to];
EXITS
skipIt => {};
};
cmd.out.PutRope["\n"];
};
argv: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO Die}];
nArgs: NAT ← argv.argc;
{
Process switches
i: NAT ← 1;
length: INT;
Bump: PROC [scratch: NAT] = {
FOR j: NAT IN (scratch..nArgs) DO
argv[j - 1] ← argv[j];
ENDLOOP;
nArgs ← nArgs - 1;
};
WHILE i < nArgs DO
length ← argv[i].Length[];
SELECT TRUE FROM
length = 0 => Bump[i];
argv[i].Fetch[0] = '- => {
FOR j: INT IN [1..length) DO
SELECT argv[i].Fetch[j] FROM
'c, 'C => forceCopy ← TRUE;
'r, 'R => retainStructure ← TRUE;
'u, 'U => updateOnly ← TRUE;
'x, 'X => exactLevelMatch ← TRUE;
ENDCASE;
ENDLOOP;
Bump[i];
};
ENDCASE => i ← i + 1;
ENDLOOP;
}; -- end of switch processing
First find out whether there is a ← anywhere. If there is, it must be the second arg.
leftArrowExists ← nArgs >= 3 AND Rope.Equal[argv[2], "←"];
FOR i: NAT IN [1..nArgs) DO
argv[i] ← FileNames.ConvertToSlashFormat[FileNames.ResolveRelativePath[argv[i]]];
ENDLOOP;
IF leftArrowExists
THEN {
IF FileNames.IsADirectory[argv[1]]
THEN destinationDirectory ← argv[1]
ELSE {
IF nArgs # 4 OR FileNames.IsAPattern[argv[1]] OR FileNames.IsAPattern[argv[3]]
THEN RETURN[$Failure, "Bad syntax for copying a file"];
HandleAFile[from: argv[3], to: argv[1]];
RETURN[IF msg # NIL THEN $Failure ELSE NIL, msg];
};
}
ELSE destinationDirectory ←
FileNames.ConvertToSlashFormat[FileNames.CurrentWorkingDirectory[]];
If we get here, then for each of the filenames and patterns, copy the file to the destination directory.
FOR i: NAT IN [(IF leftArrowExists THEN 3 ELSE 1) .. nArgs) DO
IF FileNames.IsADirectory[argv[i]] THEN {
msg ← Rope.Concat["Cannot copy a directory: ", argv[i]];
GO TO Die;
};
IF FileNames.IsAPattern[argv[i]]
THEN {
pattern: ROPE ← argv[i];
handleIt: FS.NameProc = {
[fullFName: ROPE] RETURNS [continue: BOOL]
to: ROPE;
short: ROPENIL;
continue ← TRUE;
IF SkipFunny[fullFName] THEN RETURN;
IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN;
fullFName ← FileNames.ConvertToSlashFormat[fullFName];
IF retainStructure
THEN to ← FileNames.StripVersionNumber[fullFName.Substr[dirBeforeStarLength]]
ELSE to ← FileNames.GetShortName[fullFName];
to ← Rope.Concat[destinationDirectory, to];
HandleAFile[from: fullFName, to: to];
RETURN[msg = NIL];
};
IF pattern.Find["!"] = -1 THEN pattern ← Rope.Concat[pattern, "!H"];
IF pattern.Fetch[0] # '/ THEN
pattern ← Rope.Concat[FileNames.CurrentWorkingDirectory[], pattern];
IF exactLevelMatch THEN anglesRequired ← CountAngles[pattern];
dirBeforeStar ← FileNames.DirectoryContaining[
path: pattern, pos: Rope.Index[s1: pattern, s2: "*"]];
dirBeforeStarLength ← dirBeforeStar.Length[];
Count the number of slashes after the first *.
FS.EnumerateForNames[pattern, handleIt
! FS.Error => IF error.group # $bug THEN {msg ← FSErrorMsg[error]; GO TO Die}];
}
ELSE {
to: ROPE ← Rope.Concat[destinationDirectory, FileNames.GetShortName[argv[i]]];
HandleAFile[from: argv[i], to: to];
IF msg # NIL THEN GO TO Die;
};
Process.CheckForAbort[];
ENDLOOP;
EXITS
Die => result ← $Failure;
};
FSPseudoServer Commands
PseudoServerAddCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
args: CommandTool.ArgumentVector ← CommandTool.Parse[cmd
! CommandTool.Failed => {msg ← errorMsg; GO TO failed}];
list: FSPseudoServers.PseudoServerList ← NIL;
server: ROPENIL;
IF args.argc <= 1 THEN {
msg ← "Usage: PseudoServerAdd server write read ...";
GO TO failed};
IF args.argc = 2 THEN {
Really trying to delete this server
server ← args[1];
IF FSPseudoServers.DeletePseudoServer[server] # NIL
THEN msg ← IO.PutFR1["'%g' deleted from pseudo server list.", [rope[server]]]
ELSE msg ← IO.PutFR1["'%g' not in pseudo server list.", [rope[server]]];
RETURN};
[msg, list] ← FSPseudoServers.PseudoServerFromRope[cmd.commandLine];
IF msg # NIL THEN GO TO failed;
IF list = NIL THEN {msg ← "empty??"; GO TO failed};
server ← list.first.server;
IF FSPseudoServers.Lookup[server] # NIL
THEN msg ← IO.PutFR1["'%g' updated in pseudo server list.", [rope[server]]]
ELSE msg ← IO.PutFR1["'%g' added to pseudo server list.", [rope[server]]];
FSPseudoServers.InsertPseudoServer[list];
EXITS
failed => {result ← $Failure};
};
PseudoServerPrintCommand: Commander.CommandProc = {
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...]
serverList: FSPseudoServers.PseudoServerList ← FSPseudoServers.GetPseudoServers[];
out: STREAM = cmd.out;
PutName: PROC [name: ROPE] = {
IO.PutRope[out, " "];
IF Rope.Length[name] = 0
THEN IO.PutRope[out, "$"]
ELSE IO.PutRope[out, name];
};
FOR each: FSPseudoServers.PseudoServerList ← serverList, each.rest WHILE each # NIL DO
IF each.first.server = NIL THEN LOOP;
IO.PutRope[out, " "];
PutName[each.first.server];
IF each.first.avoidCheck THEN PutName[" -r"];
PutName[each.first.write];
FOR readers: LIST OF ROPE ← each.first.read, readers.rest WHILE readers # NIL DO
PutName[readers.first];
ENDLOOP;
IO.PutRope[out, "\n"];
ENDLOOP;
};
Utility routines
SetWD: PROC [cmd: Commander.Handle, wDir: ROPE] RETURNS [result: REF ANYNIL, msg: ROPENIL] = {
wDir ← FileNames.ResolveRelativePath[FileNames.ConvertToSlashFormat[wDir]];
Do a bit of checking!
IF wDir.Length[] = 0 THEN RETURN[$Failure, "empty working directory"];
IF wDir.Fetch[0] # '/ THEN
wDir ← Rope.Concat[FileNames.CurrentWorkingDirectory[], wDir];
IF wDir.Fetch[wDir.Length[] - 1] # '/ THEN wDir ← Rope.Concat[wDir, "/"];
IF wDir.Length[] < 3 THEN RETURN[NIL];
msg ← wDir;
[] ← List.PutAssoc[key: $WorkingDirectory, val: wDir, aList: ProcessProps.GetPropList[]];
Now change the commander herald, if there is one
WITH CommandTool.GetProp[cmd, $ReadEvalPrintHandle] SELECT FROM
rep: ReadEvalPrint.Handle => {
name: ROPE ← Rope.Concat["CommandTool: WD = ", wDir];
root: ViewerClasses.Viewer ← rep.viewer;
IF root # NIL THEN {
root.name ← name;
ViewerOps.PaintViewer[viewer: root, hint: caption, clearClient: FALSE];
FOR v: ViewerClasses.Viewer ← root.link, v.link WHILE v # NIL AND v # root DO
v.name ← name;
ViewerOps.PaintViewer[viewer: v, hint: caption, clearClient: FALSE];
ENDLOOP;
};
};
ENDCASE;
};
SkipFunny: PROC [fullFName: ROPE] RETURNS [BOOL] = {
lastPoint, bangIndex: INT ← 0;
FOR i: INT DECREASING IN [0 .. fullFName.Length[]) DO
SELECT fullFName.Fetch[i] FROM
'! => bangIndex ← i;
'> => {lastPoint ← i; EXIT};
ENDCASE;
ENDLOOP;
RETURN[ bangIndex = lastPoint + 1 ]; -- skip [..]<..>!1
};
CountAngles: PROC [pattern: ROPE] RETURNS [count: INT ← 0] = {
This routine counts either angle brackets or slashes (not counting a slash in the first position, which does not correspond to a slash). The idea is that this gives a measure of the number of levels in a path name.
len: INT = Rope.Length[pattern];
pos: INT ← 0;
WHILE pos < len DO
pos ← Rope.SkipTo[pattern, pos+1, ">/"];
count ← count + 1;
ENDLOOP;
IF Rope.Match["/*", pattern] THEN
The count is too high by 1 (we skipped the first char anyway), since the first two slashes indicate server delimiters rather than directory element delimiters.
count ← count - 1;
};
AbortRequested: PROC [cmd: Commander.Handle] RETURNS [BOOL] = {
Process.CheckForAbort[];
RETURN [FALSE];
};
Initialization
Init: PROCEDURE = {
docAdd: ROPE = "add pseudo-server translation\n\t-r: assume replicated files (avoid remote check)";
docPrint: ROPE = "print pseudo-server translations";
Commander.Register[
"///Commands/Copy", CopyAndRename,
"Copy newFile ← oldFile, or Copy directory ← {pattern}*\n -c: force copy, -r: retain structure, -u: update only, -x: eXact level match", $Copy];
Commander.Register[
"///Commands/Del", DeleteFiles,
"Delete {switch | pattern}*\n -x: eXact level match"];
Commander.Register[
"///Commands/Delete", DeleteFiles,
"Delete {switch | pattern}*\n -x: eXact level match"];
Commander.Register[
"///Commands/Rename", CopyAndRename,
"Rename newFile ← oldFile, or Rename directory ← {pattern}*\n -r: retain structure, -u: update only, -x: eXact level match"];
Commander.Register[
"///Commands/Type", TypeFile,
"Type fileName (in executive window)"];
Commander.Register[
"///Commands/PrintFSCacheInfo", PrintFSCacheInfo,
"PrintFSCacheInfo {volumeName}, Print FS cache info"];
Commander.Register[
"///Commands/SetKeep", SetKeep,
"SetKeep keep {pattern}*"];
Commander.Register[
"///Commands/SetFreeboard", SetFSFreeboard,
"SetFreeboard nPages -- set FS cache limit"];
Commander.Register[
"///Commands/PrintWorkingDirectory", PrintWorkingDirectory,
"Print working directory"];
Commander.Register[
"///Commands/PWD", PrintWorkingDirectory,
"Print working directory"];
Commander.Register[
"///Commands/ChangeWorkingDirectory", ChangeWorkingDirectory,
"Change working directory", $CD];
Commander.Register[
"///Commands/CD", ChangeWorkingDirectory,
"Change working directory", $CD];
Commander.Register[
"///Commands/CDR", ChangeWorkingDirectory,
"Change working directory (root relative)", $CDR];
Commander.Register[
"///Commands/PushWorkingDirectory", ChangeWorkingDirectory,
"Push working directory", $PD];
Commander.Register[
"///Commands/Push", ChangeWorkingDirectory,
"Push working directory", $PD];
Commander.Register[
"///Commands/PushR", ChangeWorkingDirectory,
"Push working directory (root relative)", $PDR];
Commander.Register[
"///Commands/PopWorkingDirectory", PopWD,
"Pop working directory"];
Commander.Register[
"///Commands/Pop", PopWD,
"Pop working directory"];
Commander.Register[
"///Commands/FlushCache", FSFlushCache,
"FlushCache {pattern}* -- flush FS cache"];
Commander.Register[
"///Commands/ListBTree", FSListBTree,
"ListBTree {pattern}* -- list FS local directory and cache"];
Commander.Register[
"///Commands/FSLoad", FSLoad, "Load {pattern}* -- retrieve files"];
Commander.Register[
"///Commands/Scavenge", FSScavenge,
"Scavenge {volumeName} -- FS scavenge"];
Commander.Register[
"///Commands/CacheFindExcessVersions", FindExcessVersionsCommand,
"lists excess versions of files to the command output.",
$FindCached];
Commander.Register[
"///Commands/CacheKillExcessVersions", FindExcessVersionsCommand,
"deletes excess versions of files.",
$KillCached];
Commander.Register[
"///Commands/FindExcessVersions", FindExcessVersionsCommand,
"lists excess versions of files to the command output.",
$FindReal];
Commander.Register[
"///Commands/KillExcessVersions", FindExcessVersionsCommand,
"deletes excess versions of files.",
$KillReal];
Commander.Register[
"///Commands/PSAdd", PseudoServerAddCommand, docAdd];
Commander.Register[
"///Commands/PseudoServerAdd", PseudoServerAddCommand, docAdd];
Commander.Register[
"///Commands/PSPrint", PseudoServerPrintCommand, docPrint];
Commander.Register[
"///Commands/PseudoServerPrint", PseudoServerPrintCommand, docPrint];
};
Init[];
END.