DoTCCopy: Commander.CommandProc = {
destinationDirectory: PATH ¬ NIL;
leftArrowExists: BOOL ¬ FALSE;
compsBeforeStar: INT ¬ 0;
forceCopy: BOOL ¬ TRUE;
retainStructure: BOOL ¬ FALSE;
updateOnly: BOOL ¬ FALSE;
exactLevelMatch: BOOL ¬ FALSE;
componentsRequired: INT ¬ 0;
HandleAFile:
PROC [to, from:
PATH] = {
sourceID: PFS.UniqueID ¬ PFS.FileInfo[from].uniqueID;
toName: ROPE ~ PFS.RopeFromPath[to];
fromName: ROPE ~ PFS.RopeFromPath[from];
Process.CheckForAbort[];
cmd.out.PutF[" %g ← %g", [rope[toName]], [rope[fromName]]];
{
ENABLE
PFS.Error =>
IF error.group # bug THEN {msg ¬ PFSErrorMsg1[error]; GO TO skipIt};
IF updateOnly
THEN {
destID: PFS.UniqueID ¬ PFS.nullUniqueID;
destID ¬ PFS.FileInfo[to ! PFS.Error => IF error.group # bug THEN CONTINUE].uniqueID;
IF sourceID = destID
AND sourceID #
PFS.nullUniqueID
THEN {
This file does not need a copy, since it has the same ID as the destination file. We have been instructed to trust the ID.
cmd.out.PutRope["\n -- not copied, create dates match"];
GO TO skipIt;
};
};
BEGIN
outputStream:
IO.
STREAM ~
PFS.StreamOpen[fileName: to, accessOptions: create, wantedUniqueID: sourceID
!
PFS.Error =>
IF error.group = user
THEN {
msg ¬ IO.PutFR["Cannot create %s: %s\n", [rope[toName]], [rope[error.explanation]]];
GO TO sigh;
}];
pipeBufferLength: INT ~ 2048;
toPipe, fromPipe: IO.STREAM;
Fetcher:
PROC ~ {
pfsRemoteStreamProc:
PFS.RetrieveConfirmProc ~ {
RETURN [toPipe];
};
PFS.Retrieve[name: from, proc: pfsRemoteStreamProc];
IO.Close[toPipe];
};
Storer:
PROC ~
TRUSTED {
buffer: PACKED ARRAY [0..pipeBufferLength) OF CHAR;
block: Basics.UnsafeBlock ¬ [base: LOOPHOLE[LONG[@buffer]], startIndex: 0, count: pipeBufferLength];
count: INT;
DO
count ¬ IO.UnsafeGetBlock[fromPipe, block];
IF count < pipeBufferLength
THEN {
block.count ¬ count;
IO.UnsafePutBlock[outputStream, block];
IO.Close[outputStream];
RETURN;
}
ELSE
IO.UnsafePutBlock[outputStream, block];
ENDLOOP;
};
[toPipe, fromPipe] ¬ IOClasses.CreatePipe[pipeBufferLength];
TRUSTED {
SharedErrors.Fork[
LIST[Storer, Fetcher]
!
PFS.Error =>
IF error.group = user
THEN {
msg ¬ IO.PutFR["Cannot read %s: %s\n", [rope[fromName]], [rope[error.explanation]]];
GO TO sigh;
}];
};
END;
};
cmd.out.PutRope["\n"];
};
argStream: IO.STREAM ~ IO.RIS[cmd.commandLine];
head: LIST OF ROPE ~ LIST[NIL];
last: LIST OF ROPE ¬ head;
nArgs: NAT ¬ 0;
args: LIST OF ROPE ¬ NIL;
sources: LIST OF ROPE ¬ NIL;
DO
arg: Token ~ GetCmdToken[argStream];
length: INT ~ Rope.Length[arg.value];
SELECT
TRUE
FROM
(arg.value = NIL) => EXIT;
(length = 0) => NULL;
(Rope.Fetch[arg.literal, 0] = '-) => {
sense: BOOL ¬ TRUE;
FOR j:
INT
IN [1..length)
DO
SELECT arg.literal.Fetch[j]
FROM
'~ => {sense ¬ NOT sense; LOOP };
'r, 'R => retainStructure ¬ sense;
'u, 'U => updateOnly ¬ sense;
'x, 'X => exactLevelMatch ¬ sense;
ENDCASE => {
msg ¬ Rope.Cat["Unknown switch: ", arg.literal.Substr[0, j], " ", arg.literal.Substr[j]];
GOTO Die;
};
sense ¬ TRUE;
ENDLOOP;
};
(nArgs = 1
AND (Rope.Equal[arg.literal, "←"]
OR Rope.Equal[arg.literal, "¬"])) => {
leftArrowExists ¬ TRUE;
};
ENDCASE => { nArgs ¬ nArgs + 1; last ¬ last.rest ¬ LIST[arg.value] };
ENDLOOP;
args ¬ head.rest;
IF leftArrowExists
THEN {
target: PATH ¬ PFS.PathFromRope[args.first];
sources ¬ args.rest;
IF target.IsADirectory[]
THEN destinationDirectory ¬ target
ELSE {
IF nArgs # 2
OR Rope.Find[args.first, "*"] >= 0
OR Rope.Find[args.rest.first, "*"] >= 0
THEN RETURN[$Failure, "Bad syntax for copying a file"];
HandleAFile[from: PFS.PathFromRope[args.rest.first], to: target];
RETURN[IF msg # NIL THEN $Failure ELSE NIL, msg];
};
}
ELSE {
destinationDirectory ¬ PFS.GetWDir[];
sources ¬ args;
};
If we get here, then for each of the filenames and patterns, copy the file to the destination directory.
FOR tail:
LIST
OF
ROPE ¬ sources, tail.rest
UNTIL tail =
NIL
DO
source: PATH ¬ PFS.PathFromRope[tail.first];
IF source.IsADirectory[]
THEN {
msg ¬ Rope.Concat["Cannot copy a directory: ", tail.first];
GO TO Die;
};
IF Rope.Find[tail.first, "*"] >= 0
THEN {
pattern: PATH ¬ source;
handleIt:
PFS.NameProc = {
[name: PATH] RETURNS [continue: BOOL]
to: PATH;
short: ROPE ¬ NIL;
continue ¬ TRUE;
IF exactLevelMatch AND componentsRequired # name.ComponentCount[] THEN RETURN [TRUE];
IF retainStructure
THEN to ¬ name.SubName[compsBeforeStar]
ELSE to ¬ name.SubName[start~name.ComponentCount[]-1, count~1];
to ¬ to.StripVersionNumber[];
to ¬ PFSNames.Cat[destinationDirectory, to];
HandleAFile[from: name, to: to];
RETURN[msg = NIL];
};
IF pattern.ShortName[].version = [none] THEN pattern ¬ pattern.SetVersionNumber[[highest]];
pattern ¬ PFS.AbsoluteName[pattern];
IF exactLevelMatch THEN componentsRequired ¬ PFSNames.ComponentCount[pattern];
IF retainStructure
THEN {
goOn: BOOL ¬ TRUE;
findStar: PFSNames.ComponentProc ~ {
IF goOn AND Rope.Find[comp.ComponentRope[], "*"] >= 0 THEN goOn ¬ FALSE ELSE compsBeforeStar ¬ compsBeforeStar+1;
};
dummySeparatorProc: PFSNames.SeparatorProc ~ {};
PFSNames.Map[pattern, findStar, dummySeparatorProc, NIL];
};
PFS.EnumerateForNames[pattern, handleIt
! PFS.Error => IF error.group # $bug THEN {msg ¬ PFSErrorMsg[error]; GO TO Die}];
}
ELSE {
from: PATH ¬ source;
to: PATH ¬ PFSNames.StripVersionNumber[from.SubName[from.ComponentCount[]-1]];
to ¬ PFSNames.Cat[destinationDirectory, to];
HandleAFile[from: from, to: to];
IF msg # NIL THEN GO TO Die;
};
Process.CheckForAbort[];
ENDLOOP;
EXITS
Die => result ¬ $Failure;
};
TCFileInfo:
PROC[name:
PATH]
RETURNS[uid:
PFS.UniqueID ¬
PFS.nullUniqueID] ~ {
don't have FileInfo in TC interface
Enum:
PFS.InfoProc ~ {
uid ¬ uniqueID;
RETURN[FALSE];
};
comp: PFSNames.Component ¬ PFSNames.ShortName[name];
IF comp.version.versionKind = none THEN
name ¬ PFSNames.SetVersionNumber[name, [highest, 0 ]];
PFS.EnumerateForInfo[name, Enum];
};