<<>> <> <> <> <> <<>> DIRECTORY Basics, Commander USING [CommandProc, Register], IO, IOClasses, PFS, PFSNames, Process USING [CheckForAbort], Rope, SharedErrors; TCCopyImpl: CEDAR MONITOR IMPORTS Commander, IO, IOClasses, PFS, PFSNames, Process, Rope, SharedErrors = BEGIN ROPE: TYPE = Rope.ROPE; PATH: TYPE = PFSNames.PATH; STREAM: TYPE = IO.STREAM; QuotedStringError: ERROR = CODE; CmdTokenBreak: PROC [char: CHAR] RETURNS [IO.CharClass] = { IF char = '" THEN RETURN [break]; IF char = ' OR char = '\t OR char = ', OR char = '\l OR char = '\r THEN RETURN [sepr]; RETURN [other]; }; Token: TYPE = RECORD [value, literal: ROPE]; GetCmdToken: PROC [stream: IO.STREAM] RETURNS [token: Token ¬ [NIL, NIL]] = { token.value ¬ token.literal ¬ IO.GetTokenRope[stream, CmdTokenBreak ! IO.EndOfStream => CONTINUE].token; IF Rope.Equal[token.literal, "\""] THEN { ref: REF; IO.Backup[self: stream, char: '"]; ref ¬ IO.GetRefAny[stream ! IO.Error, IO.EndOfStream => ERROR QuotedStringError]; WITH ref SELECT FROM rope: ROPE => token.value ¬ rope; ENDCASE => ERROR QuotedStringError; }; }; <> 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 { <> 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; }]; }; EXITS sigh => {}; END; EXITS skipIt => {}; }; 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; }; <> 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] ~ { <> 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]; }; PFSErrorMsg: PROC [error: PFS.ErrorDesc] RETURNS [ROPE] = { IF error.code = $unknownFile THEN RETURN [" -- not found!\n"] ELSE RETURN[Rope.Cat[" -- PFS.Error: ", error.explanation, "\n"]]; }; PFSErrorMsg1: PROC [error: PFS.ErrorDesc] RETURNS [ROPE] = { IF error.code = $unknownFile THEN RETURN [" -- not found!"] ELSE RETURN[Rope.Concat["\n -- PFS.Error: ", error.explanation]]; }; <> copyDoc: ROPE = "newFile _ oldFile | directory _ {pattern}* Copies a file or files. -c force copy -r retain structure -u update only -x exact level match"; Init: PROC = { Commander.Register["TCCopy", DoTCCopy, copyDoc, $Copy]; }; Init[]; END.