NSCopyImpl.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Bill Jackson August 29, 1985 8:52:51 pm PDT
Dave Rumph, February 3, 1986 6:12:09 pm PST
Note: The vast majority of this code is taken from FSFileCommandsImpl since I'm trying to emulate the "built-in" Copy command.
This guy know about NSAgent entries in your profile, e.g.:
NSAgent.Server: "Thunderbird"
NSAgent.CHName: "Bill Jackson:PARC:Xerox"
DIRECTORY
NSFiler,
NSFilerRpcControl,
BasicTime USING [GMT, nullGMT],
Commander USING [CommandProc, Handle, Register],
CommandTool USING [ArgumentVector, Failed, Parse],
File USING [GetVolumeName, SystemVolume],
FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, DirectoryContaining, GetShortName, IsADirectory, IsAPattern, ResolveRelativePath, StripVersionNumber],
FS USING [ComponentPositions, EnumerateForNames, Error, ErrorDesc, ExpandName, NameProc],
FSPseudoServers USING [TranslateForRead, TranslateForWrite],
IO USING [PutF, PutRope, STREAM],
Process USING [CheckForAbort],
Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, Index, Length, Match, ROPE, SkipTo, Substr],
RPC USING [ImportFailed, ShortROPE],
ThisMachine USING [Name],
UserCredentials USING [Get],
UserProfile USING [Token];
NSCopyImpl: CEDAR PROGRAM
IMPORTS NSFiler, NSFilerRpcControl, Commander, CommandTool, File, FileNames, FS, FSPseudoServers, IO, Process, Rope, RPC, ThisMachine, UserCredentials, UserProfile
= BEGIN
ROPE: TYPE = Rope.ROPE;
ShortROPE: TYPE = RPC.ShortROPE;
STREAM: TYPE = IO.STREAM;
huhMsg: ARRAY NSFiler.Huh OF ROPE ← ["badCredentials", "fromNotFound", "cantWrite", "whoKnows", "noError"];
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"]];
};
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;
};
createKeep: CARDINAL ← 2;
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;
FixUpFilename: PROC [name: ROPENIL, copyFrom: BOOL] RETURNS [newName: ROPE] = {
body: ROPE;
platformHost: ROPE ← ThisMachine.Name[];
platformVolume: ROPE ← File.GetVolumeName[File.SystemVolume[]];
prefix: ROPE;
cp: FS.ComponentPositions;
[fullFName: newName, cp: cp] ← FS.ExpandName[name];
prefix ← Rope.Substr[newName, 0, 4];
body ← Rope.Substr[newName, 4, Rope.Length[newName]];
IF Rope.Equal[prefix, "[]<>"] THEN {
newName ← Rope.Cat["[", platformHost, "]<", platformVolume, ">"];
newName ← Rope.Cat[newName, body];
}
ELSE newName ← Rope.Cat[
"[",
IF copyFrom THEN
FSPseudoServers.TranslateForRead[Rope.Substr[newName, cp.server.start, cp.server.length]].first
ELSE
FSPseudoServers.TranslateForWrite[Rope.Substr[newName, cp.server.start, cp.server.length]],
"]<",
Rope.Substr[newName, cp.dir.start, Rope.Length[newName]]];
};
DoCopy: PROC [from, to: ROPE,
setKeep: BOOLFALSE, keep: CARDINAL ← 1,
wantedCreatedTime: BasicTime.GMT ← BasicTime.nullGMT,
remoteCheck: BOOLTRUE,
attach: BOOLFALSE,
wDir: ROPENIL
] RETURNS [toFName: ROPE] =
{
fromString: LONG STRINGNIL;
toString: LONG STRINGNIL;
x1, x2: ROPE;
log: STREAM ← cmd.out;
noBind: BOOLEANFALSE;
gvName: LONG STRINGNIL;
gvPasswd: LONG STRINGNIL;
chName: LONG STRINGNIL;
chPasswd: LONG STRINGNIL;
credentialsName: ROPE;
credentialsPasswd: ROPE;
server: ShortROPE ← UserProfile.Token["NSAgent.Server"];
x1 ← FixUpFilename[name: to, copyFrom: FALSE];
toString ← LOOPHOLE[Rope.Flatten[x1], LONG STRING];
x2 ← FixUpFilename[name: from, copyFrom: TRUE];
fromString ← LOOPHOLE[Rope.Flatten[x2], LONG STRING];
[credentialsName, credentialsPasswd] ← UserCredentials.Get[];
gvName ← LOOPHOLE[Rope.Flatten[credentialsName], LONG STRING];
gvPasswd ← LOOPHOLE[Rope.Flatten[credentialsPasswd], LONG STRING];
chName ← LOOPHOLE[Rope.Flatten[UserProfile.Token["NSAgent.CHName", credentialsName]], LONG STRING];
chPasswd ← LOOPHOLE[Rope.Flatten[credentialsPasswd], LONG STRING];
NSFilerRpcControl.ImportInterface[[NIL, server] !
RPC.ImportFailed =>
{
noBind ← TRUE;
IO.PutRope[log, "\nNSFilerRpcControl.ImportInterface from "];
IO.PutRope[log, server];
IO.PutRope[log, " failed: "];
SELECT why FROM
communications => IO.PutRope[log, "communications"];
badInstance => IO.PutRope[log, "badInstance"];
badVersion => IO.PutRope[log, "badVersion"];
wrongVersion => IO.PutRope[log, "wrongVersion"];
unbound => IO.PutRope[log, "unbound"];
stubProtocol => IO.PutRope[log, "stubProtocol"];
ENDCASE => IO.PutRope[log, "??"];
IO.PutRope[log, ".\n"];
CONTINUE;
}];
IF noBind THEN RETURN;
IO.PutRope[log, " Copying.."];
TRUSTED{NSFiler.Copy[from: fromString, to: toString, gvname: gvName, gvpasswd: gvPasswd, chname: chName, chpasswd: chPasswd]};
IO.PutRope[log, ".."];
TRUSTED{NSFilerRpcControl.UnimportInterface[]};
IO.PutRope[log, "done.\n"];
};
HandleAFile: PROC [to, from: ROPE] = {
Process.CheckForAbort[];
cmd.out.PutF[" %g ← %g", [rope[to]], [rope[from]]];
{ENABLE
NSFiler.Error =>
IF reason # noError THEN {msg ← huhMsg[reason]; GO TO skipIt};
IF updateOnly THEN {
sourceTime: BasicTime.GMT ← BasicTime.nullGMT;
destTime: BasicTime.GMT ← BasicTime.nullGMT;
sourceTime ← NSFiler.FileInfo[from].created;
destTime ← NSFiler.FileInfo[to ! NSFiler.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;
};
cmd.out.PutRope["\n updateOnly NOT IMPLEMENTED"];
};
IF doACopy
THEN [] ← DoCopy[from: from, to: to, keep: createKeep, attach: NOT forceCopy]
ELSE cmd.out.PutRope["\n Rename NOT IMPLEMENTED"];
ELSE NSFiler.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;
};
Init: PROCEDURE = {
doc: ROPE = "NSCopy -- Copy file between PUP & NS Lands.\n NSCopy newFile ← oldFile, or \n NSCopy directory ← {pattern}*\n -c: force copy, -r: retain structure, -u: update only, -x: eXact level match";
Commander.Register[
key: "///Commands/NSCopy",
key is the name of the command. Registering the command under the "///Commands/" directory indicates that this command should be globally known and useful. If no directory is given, the command is registered under the current working directory, which is the proper place for more specific commands.
proc: CopyAndRename,
proc is called to execute the command.
doc: doc,
doc gives the brief documentation for the command. This should be as brief as possible to remind the user of the function and options.
clientData: $Copy,
clientData is passed to the command through cmd.procData.clientData. This is useful when various commands are registered with the same procedure, since the clientData can be used to tell the invocations apart.
interpreted: TRUE
interpreted indicates whether or not the arguments should be scanned for special characters. The default is TRUE, which implies that I/O redicrection characters should be interpreted by the CommandTool.
];
};
Initialization
Init[];
END...