<<>> <> <> <> <> <> <> <> <> DIRECTORY BasicTime USING [GMT, minutesPerHour, MonthOfYear, nullGMT, OutOfRange, Period, TimeParametersNotKnown, Unpack, Unpacked, unspecifiedZone, Zone], Commander USING [CommandProc, CommandProcHandle, Register], Convert USING [Base, CardFromRope], IO, List USING [AList, PutAssoc], PFS USING [AbsoluteName, Attach, Copy, Delete, EnumerateForInfo, EnumerateForNames, Error, ErrorDesc, ErrorGroup, FileInfo, FileType, GetWDir, InfoProc, NameProc, NameConfirmProc, nullUniqueID, PathFromRope, Rename, Retrieve, RetrieveConfirmProc, RopeFromPath, StreamOpen, tDirectory, tText, tUnspecified, UniqueID], PFSNames USING [Cat, Compare, ComponentCount, ComponentProc, ComponentRope, Equal, IsADirectory, Map, NarrowPath, Parent, PATH, SeparatorProc, SetVersionNumber, ShortName, StripVersionNumber, SubName, VersionKind], PFSPrefixMap USING [Delete, Entry, EntryList, GetMap, Insert, Lookup, Translate], PriorityQueue USING [Create, Insert, Item, Ref, Remove, Size, SortPred], Process USING [CheckForAbort], ProcessProps USING [AddPropList, GetProp], Rope USING [Cat, Concat, Equal, Fetch, Find, Length, Match, ROPE, Substr], RuntimeError USING [UNCAUGHT]; PFSCommandsImpl: CEDAR MONITOR IMPORTS BasicTime, Commander, Convert, IO, List, PFS, PFSNames, PFSPrefixMap, PriorityQueue, Process, ProcessProps, Rope, RuntimeError = BEGIN GMT: TYPE = BasicTime.GMT; LORA: TYPE = LIST OF REF ANY; 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; }; }; GetPath: PROC [stream: IO.STREAM] RETURNS [PATH] = { RETURN [PFS.PathFromRope[GetCmdToken[stream].value]] }; PrefixMapAdd: Commander.CommandProc ~ TRUSTED { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> ENABLE QuotedStringError => {msg _ "Mismatched quotes"; GO TO failed}; translateFirst, checkPrev: BOOL ¬ FALSE; argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; prefix, translation: PATH; first: ROPE ¬ GetCmdToken[argStream].value; DO IF first = NIL THEN EXIT; IF Rope.Fetch[first, 0] # '- THEN { prefix ¬ PFS.PathFromRope[first]; EXIT }; FOR i: INT IN [1..Rope.Length[first]-1] DO SELECT Rope.Fetch[first, i] FROM 'c, 'C => checkPrev ¬ TRUE; 't, 'T => translateFirst ¬ TRUE; ENDCASE => { cmd.out.PutF1["Unknown switch %g; quitting\n", [character[Rope.Fetch[first, i]]] ]; RETURN; }; ENDLOOP; first ¬ GetCmdToken[argStream].value; ENDLOOP; translation _ GetPath[argStream]; SELECT TRUE FROM prefix=NIL => { cmd.out.PutF1["%g \n", [rope[prefixMapAddDoc]]]; RETURN; }; translation=NIL => { [] _ PFSPrefixMap.Delete[prefix]; }; ENDCASE => { IF translateFirst THEN translation ¬ PFSPrefixMap.Translate[translation]; IF checkPrev THEN { prev: PATH ¬ PFSPrefixMap.Lookup[prefix]; IF prev # NIL AND NOT PFSNames.Equal[prev, translation] THEN cmd.out.PutF["Redefining previous translation (%g)\n", [rope[PFS.RopeFromPath[prev]]] ]; }; [] ¬ PFSPrefixMap.Insert[prefix, translation]; }; EXITS failed=> result _ $Failure; }; PrefixMapPrint: Commander.CommandProc ~ TRUSTED { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> ENABLE QuotedStringError => {msg _ "Mismatched quotes"; GO TO failed}; argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; prefix: PATH _ GetPath[argStream]; SELECT TRUE FROM prefix=NIL => { list: PFSPrefixMap.EntryList ~ PFSPrefixMap.GetMap[]; FOR l: PFSPrefixMap.EntryList _ list, l.rest UNTIL l=NIL DO cmd.out.PutF[ "\t%g\t\t%g\n", [rope[PFS.RopeFromPath[l.first.prefix]]], [rope[PFS.RopeFromPath[l.first.translation]]] ]; ENDLOOP; }; ENDCASE => { translation: PATH ~ PFSPrefixMap.Lookup[prefix]; cmd.out.PutF[ "\t%g\t\t%g\n", [rope[PFS.RopeFromPath[prefix]]], [rope[PFS.RopeFromPath[translation]]] ]; }; EXITS failed=> result _ $Failure; }; PrefixMapTranslate: Commander.CommandProc ~ TRUSTED { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> ENABLE QuotedStringError => {msg _ "Mismatched quotes"; GO TO failed}; argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; path: PATH _ GetPath[argStream]; SELECT TRUE FROM path=NIL => { cmd.out.PutF[ "%g\n", [rope[prefixMapTranslateDoc]] ]; }; ENDCASE => { translation: PATH ~ PFSPrefixMap.Translate[path]; cmd.out.PutF[ "\t%g\t\t%g\n", [rope[PFS.RopeFromPath[path]]], [rope[PFS.RopeFromPath[translation]]] ]; }; EXITS failed=> result _ $Failure; }; NameCommandProc: Commander.CommandProc ~ TRUSTED { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> ENABLE QuotedStringError => {msg _ "Mismatched quotes"; GO TO failed}; argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; val: REF ANY _ LOOPHOLE[ Convert.CardFromRope[GetCmdToken[argStream].value ! RuntimeError.UNCAUGHT => {msg _ "conversion error"; GO TO failed}] ]; name: PATH _ PFSNames.NarrowPath[val ! RuntimeError.UNCAUGHT => { msg _ "narrow failed"; GO TO failed}]; nameRope: ROPE _ PFS.RopeFromPath[name]; cmd.out.PutF[ " %g: %g\n", [cardinal[LOOPHOLE[val]]], [rope[nameRope]] ]; EXITS failed=> result _ $Failure; }; <> DeleteCommandProc: Commander.CommandProc = { argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; out: STREAM = cmd.out; exactLevelMatch: BOOL _ FALSE; componentsRequired: INT _ 0; deleteIt: PFS.NameProc = { ok: PFS.NameConfirmProc ~ { IO.PutRope[out, " deleting "]; IO.PutRope[out, PFS.RopeFromPath[fullName]]; RETURN[TRUE]; }; Process.CheckForAbort[]; continue _ TRUE; IF exactLevelMatch AND componentsRequired # name.ComponentCount[] THEN RETURN; PFS.Delete[name~name, confirmProc~ok ! PFS.Error => IF error.group # bug THEN { IO.PutRope[cmd.err, PFSErrorMsg1[error]]; CONTINUE } ]; IO.PutRope[out, "\n"]; }; DO pattern: ROPE _ GetCmdToken[argStream ! QuotedStringError => {msg _ "Mismatched quotes"; GO TO Die}].value; IF pattern = NIL THEN EXIT; IF Rope.Match["-*", pattern] THEN { <> sense: BOOL _ TRUE; 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 PFS.Error => IF error.group # $bug THEN {msg _ PFSErrorMsg[error]; GO TO Die}; path: PATH _ PFS.PathFromRope[pattern]; path _ PFS.AbsoluteName[path]; IF path.ComponentCount[] = 0 THEN LOOP; IF exactLevelMatch THEN componentsRequired _ path.ComponentCount[]; IF path.ShortName[].version = [none] THEN path _ path.SetVersionNumber[[lowest]]; IF Rope.Find[pattern, "*"] = -1 THEN [] _ deleteIt[path] ELSE PFS.EnumerateForNames[path, deleteIt]; }; Process.CheckForAbort[]; ENDLOOP; EXITS Die => result _ $Failure; }; 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]]; }; <> CopyAndRename: Commander.CommandProc = { destinationDirectory: PATH _ NIL; leftArrowExists: BOOL _ FALSE; useRetrieve: BOOL _ cmd.procData.clientData = $Retrieve; doACopy: BOOL _ cmd.procData.clientData = $Copy; doStore: BOOL _ cmd.procData.clientData = $Store; compsBeforeStar: INT _ 0; forceCopy: BOOL _ TRUE; retainStructure: BOOL _ FALSE; updateOnly: BOOL _ FALSE; exactLevelMatch: BOOL _ FALSE; componentsRequired: INT _ 0; HandleAFile: PROC [to, from: PATH] = { Process.CheckForAbort[]; cmd.out.PutF[" %g _ %g", [rope[PFS.RopeFromPath[to]]], [rope[PFS.RopeFromPath[from]]]]; {ENABLE PFS.Error => IF error.group # bug THEN {msg _ PFSErrorMsg1[error]; GO TO skipIt}; IF updateOnly THEN { sourceID: PFS.UniqueID _ PFS.nullUniqueID; destID: PFS.UniqueID _ PFS.nullUniqueID; sourceID _ PFS.FileInfo[from].uniqueID; 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; }; }; SELECT TRUE FROM doACopy => { IF forceCopy THEN PFS.Copy[from: from, to: to, confirmProc: NIL] ELSE [] _ PFS.Attach[attachedFile: from, attachment: to]; }; useRetrieve => { toStrm: STREAM _ PFS.StreamOpen[to, $create, PFS.FileInfo[from].uniqueID]; Rcp: PFS.RetrieveConfirmProc ~ { RETURN[toStrm] }; PFS.Retrieve[name: from, proc: Rcp]; toStrm.Close[]; }; ENDCASE => PFS.Rename[from: from, to: to]; 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 }; 'c, 'C => forceCopy _ sense; '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; }; <<>> <> ListCommandProc: Commander.CommandProc = { <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> <> EachFileName: PFS.NameProc = { <<[name: PATH] RETURNS [continue: BOOL]>> attachedTo: PATH _ NIL; created: BasicTime.GMT _ BasicTime.nullGMT; bytes: INT _ -1; fileType: PFS.FileType _ PFS.tUnspecified; uniqueID: PFS.UniqueID; item: FileItem _ NIL; continue _ TRUE; Process.CheckForAbort[]; IF xactLevelMatch AND componentsRequired # PFSNames.ComponentCount[name] THEN RETURN; IF NOT prefixOnly THEN { <> [attachedTo: attachedTo, bytes: bytes, uniqueID: uniqueID, fileType: fileType] _ PFS.FileInfo[name: name]; }; IF bytes >=0 AND lostFilesOnly THEN RETURN; IF attachedTo#NIL AND unattachedOnly THEN RETURN; item _ NEW[FileItemRep _ [name, attachedTo, uniqueID.egmt.time, bytes, PFS.tUnspecified]]; filesSeen _ filesSeen + 1; IF bytes > 0 THEN bytesTotal _ bytesTotal + bytes; SELECT TRUE FROM prefixOnly => { oldLag: PATH _ lagPrefix; SetLagPrefix[name]; IF oldLag # lagPrefix THEN { item.fullUName _ lagPrefix; PriorityQueue.Insert[pq, item]; }; }; complexSorting => PriorityQueue.Insert[pq, item]; ENDCASE => PrintOneFile[item]; }; EachFileInfo: PFS.InfoProc = { item: FileItem _ NIL; continue _ TRUE; Process.CheckForAbort[]; IF xactLevelMatch AND componentsRequired # PFSNames.ComponentCount[fullFName] THEN RETURN; IF bytes >=0 AND lostFilesOnly THEN RETURN; IF attachedTo#NIL AND unattachedOnly THEN RETURN; item _ NEW[FileItemRep _ [fullFName, attachedTo, uniqueID.egmt.time, bytes, fileType]]; filesSeen _ filesSeen + 1; IF bytes > 0 THEN bytesTotal _ bytesTotal + bytes; SELECT TRUE FROM prefixOnly => { oldLag: PATH _ lagPrefix; SetLagPrefix[fullFName]; IF oldLag # lagPrefix THEN { item.fullUName _ lagPrefix; PriorityQueue.Insert[pq, item]; }; }; complexSorting => PriorityQueue.Insert[pq, item]; ENDCASE => PrintOneFile[item]; }; PrintOneFile: PROC [item: FileItem] = { <> oldLag: PATH _ lagPrefix; printName: ROPE _ PFS.RopeFromPath[item.fullUName]; Process.CheckForAbort[]; IF NOT fullPrint AND NOT prefixOnly THEN { <> SetLagPrefix[item.fullUName]; IF oldLag # lagPrefix THEN { IO.PutRope[out, PFS.RopeFromPath[lagPrefix]]; IO.PutChar[out, IF oneLine THEN ' ELSE '\n]; }; printName _ PFS.RopeFromPath[PFSNames.SubName[item.fullUName, lagPrefixLen]]; IF NOT oneLine THEN IO.PutRope[out, " "]; }; SELECT TRUE FROM prefixOnly => IO.PutRope[out, printName]; briefPrint => { IO.PutRope[out, printName]; }; ENDCASE => { form: ROPE = IF narrowPrint THEN "%g\n%12g " ELSE "%-24g %6g "; IO.PutF[out, form, [rope[printName]], [integer[item.bytes]] ]; IF item.created = BasicTime.nullGMT THEN IO.PutRope[out, "??"] ELSE DateToStream[out, [explicit, item.created] ]; IF typePrint THEN { r: ROPE ~ SELECT item.fileType FROM PFS.tUnspecified => " tUnspec", PFS.tDirectory => " tDir", PFS.tText => " tText", ENDCASE => IO.PutFR1[" t%g", [integer[item.fileType]]]; IO.PutRope[out, r]; }; IF attachPrint AND item.attachedTo#NIL THEN IO.PutF[out, "%g=> %g", [rope[IF oneLine THEN " " ELSE "\n "]], [rope[PFS.RopeFromPath[item.attachedTo]]]]; }; IO.PutChar[out, IF oneLine THEN ' ELSE '\n]; }; TryPattern: PROC [pattern: PATH, allVersions: BOOL_FALSE] = { Do: PROC [] ~ { ENABLE PFS.Error => IF error.group # $bug THEN { IO.PutRope[cmd.err, " -- "]; IO.PutRope[cmd.err, error.explanation]; IO.PutRope[cmd.err, "\n"]; GO TO err}; patternsTried _ patternsTried + 1; IF allVersions THEN { pattern _ PFSNames.SetVersionNumber[pattern, [all]]; highestPrint _ FALSE; }; IF highestPrint THEN pattern _ PFSNames.SetVersionNumber[pattern, [highest]]; IF xactLevelMatch THEN { pattern _ PFS.AbsoluteName[pattern]; componentsRequired _ PFSNames.ComponentCount[pattern]; }; complexSorting _ sortData # NIL; SELECT TRUE FROM prefixOnly => pq _ PriorityQueue.Create[SortPred, NIL]; complexSorting => pq _ PriorityQueue.Create[SortPred, sortData]; ENDCASE => pq _ NIL; SetLagPrefix[NIL]; IF prefixOnly OR xactLevelMatch THEN PFS.EnumerateForNames[pattern, EachFileName] <> ELSE PFS.EnumerateForInfo[pattern, EachFileInfo]; <> SetLagPrefix[NIL]; IF pq # NIL THEN { lagName: PATH _ NIL; THROUGH [0..PriorityQueue.Size[pq]) DO item: FileItem = NARROW[PriorityQueue.Remove[pq]]; IF prefixOnly THEN { IF PFSNames.Equal[item.fullUName, lagName] THEN LOOP; lagName _ item.fullUName; }; PrintOneFile[item]; ENDLOOP; }; EXITS err => {IO.PutRope[cmd.err, "\n"]; RETURN}; }; wDir: ROPE ~ NARROW[ProcessProps.GetProp[$WorkingDirectory]]; newProp: List.AList ~ List.PutAssoc[$WDir, PFS.PathFromRope[wDir], NIL]; ProcessProps.AddPropList[newProp, Do]; }; SetLagPrefix: PROC [fileName: PATH] = { <<... sets the lagging prefix from the given file name, which is presumed to be syntactically correct, although it need not be complete. A file name without a prefix will set the lagPrefix to NIL. We also enforce lagPrefixLen = Rope.Length[lagPrefix] at exit, assuming that no other routine sets lagPrefix.>> newPrefix: PATH _ IF fileName# NIL THEN PFSNames.Parent[fileName] ELSE NIL; IF lagPrefix # NIL THEN { <> IF PFSNames.Equal[lagPrefix, newPrefix] THEN RETURN; }; <> lagPrefix _ newPrefix; lagPrefixLen _ PFSNames.ComponentCount[newPrefix]; }; AddSortOption: PROC [option: ATOM] = { new: LORA _ LIST[option]; IF sortDataTail = NIL THEN sortData _ new ELSE sortDataTail.rest _ new; sortDataTail _ new; }; RemSortOption: PROC [option: ATOM] = { lag: LORA _ sortData; IF lag = NIL THEN RETURN; IF lag.first = option THEN { sortData _ sortData.rest; RETURN}; FOR each: LORA _ lag.rest, each.rest WHILE each # NIL DO IF each.first = option THEN {lag.rest _ each.rest; EXIT}; lag _ each; ENDLOOP; }; gHost, gDir: ROPE _ NIL; out: STREAM = cmd.out; lagPrefix: PATH _ NIL; lagPrefixLen: INT _ 0; patternsTried, filesSeen, bytesTotal: INT _ 0; briefPrint, complexSorting, fullPrint, attachPrint, keepPrint, lostFilesOnly, narrowPrint, oneLine, prefixOnly, typePrint, unattachedOnly, xactLevelMatch: BOOL _ FALSE; highestPrint: BOOL _ cmd.procData.clientData = $Highest; componentsRequired: INT _ 0; sortData: LORA _ NIL; sortDataTail: LORA _ NIL; pq: PriorityQueue.Ref _ NIL; argStream: IO.STREAM ~ IO.RIS[cmd.commandLine]; ProcessSwitches: PROC [arg: ROPE] = { sense: BOOL _ TRUE; direction: {up, down} _ down; FOR index: INT IN [1..Rope.Length[arg]) DO SELECT Rope.Fetch[arg, index] FROM '~ => {sense _ NOT sense; LOOP}; '> => direction _ down; '< => direction _ up; 'a, 'A => attachPrint _ sense; 'b, 'B => briefPrint _ sense; 'd, 'D => { RemSortOption[$MoreRecent]; RemSortOption[$LessRecent]; IF sense THEN AddSortOption[IF direction = up THEN $LessRecent ELSE $MoreRecent]; }; 'f, 'F => fullPrint _ sense; 'h, 'H => highestPrint _ sense; 'k, 'K => keepPrint _ sense; 'n, 'N => narrowPrint _ sense; 'o, 'O => oneLine _ sense; 'p, 'P => prefixOnly _ sense; 'r, 'R => { <<-- remoteCheck _ sense;>> }; 's, 'S => { RemSortOption[$Larger]; RemSortOption[$Smaller]; IF sense THEN AddSortOption[IF direction = up THEN $Smaller ELSE $Larger]; }; 't, 'T => typePrint _ sense; 'u, 'U => unattachedOnly _ sense; 'x, 'X => xactLevelMatch _ sense; 'z, 'Z => lostFilesOnly _ sense; ENDCASE => { result _ $Failure; msg _ Rope.Cat["Unknown switch: ", arg.Substr[0, index], " ", arg.Substr[index]]; RETURN; }; sense _ TRUE; ENDLOOP; }; DO arg: Token = GetCmdToken[argStream ! QuotedStringError => {msg _ "Mismatched quotes"; GO TO failed}]; IF arg.value = NIL THEN EXIT; IF Rope.Length[arg.value] = 0 THEN LOOP; IF Rope.Fetch[arg.literal, 0] = '- THEN { <> ProcessSwitches[arg.literal]; IF result = $Failure THEN RETURN; LOOP; }; <> TryPattern[PFS.PathFromRope[arg.value ! PFS.Error => IF error.group # $bug THEN { IO.PutRope[cmd.err, " -- "]; IO.PutRope[cmd.err, error.explanation]; IO.PutRope[cmd.err, "\n"]; GO TO failed}]]; ENDLOOP; IF patternsTried = 0 THEN TryPattern[PFS.PathFromRope["*" ! PFS.Error => IF error.group # $bug THEN { IO.PutRope[cmd.err, " -- "]; IO.PutRope[cmd.err, error.explanation]; IO.PutRope[cmd.err, "\n"]; GO TO failed}]]; IF oneLine THEN IO.PutChar[out, '\n]; IF filesSeen > 0 THEN { out: IO.STREAM ~ IO.ROS[]; IO.PutF[out, "-- %g files", [integer[filesSeen]] ]; IF bytesTotal > 0 THEN IO.PutF[out, ", %g total bytes", [integer[bytesTotal]] ]; IO.PutChar[out, '\n]; msg _ IO.RopeFromROS[out]; }; EXITS failed => {result _ $Failure}; }; FileItem: TYPE = REF FileItemRep; FileItemRep: TYPE = RECORD [fullUName, attachedTo: PATH, created: GMT, bytes: INT, fileType: PFS.FileType]; SortPred: PriorityQueue.SortPred = { <<[x: Item, y: Item, data: REF] RETURNS [BOOL]>> xx: FileItem = NARROW[x]; yy: FileItem = NARROW[y]; options: LORA = NARROW[data]; FOR each: LORA _ options, each.rest WHILE each # NIL DO SELECT each.first FROM $MoreRecent => { IF xx.created = yy.created THEN LOOP; RETURN [BasicTime.Period[xx.created, yy.created] < 0]; }; $LessRecent => { IF xx.created = yy.created THEN LOOP; RETURN [BasicTime.Period[xx.created, yy.created] > 0]; }; $Larger => { IF xx.bytes = yy.bytes THEN LOOP; RETURN [xx.bytes > yy.bytes]; }; $Smaller => { IF xx.bytes = yy.bytes THEN LOOP; RETURN [xx.bytes < yy.bytes]; }; ENDCASE; ENDLOOP; RETURN [PFSNames.Compare[xx.fullUName, yy.fullUName, FALSE] = less]; }; <> <<>> -- Stolen from DFUtilitiesImpl DateFormat: TYPE = {explicit, omitted, greaterThan, notEqual}; Date: TYPE = RECORD [ format: DateFormat _ $omitted, gmt: GMT _ BasicTime.nullGMT <<'gmt' is valid only if dateFormat = $explicit. (We don't use a variant record because it complicates the client's life too much (i.e., the compiler is unnecessarily picky about assignments involving variant records that aren't REF-containing).)>> ]; <> DateToStream: PROC [s: STREAM, date: Date] = { SELECT date.format FROM $explicit => { months: ROPE = "JanFebMarAprMayJunJulAugSepOctNovDec"; up: BasicTime.Unpacked = BasicTime.Unpack[date.gmt ! BasicTime.OutOfRange, BasicTime.TimeParametersNotKnown => GO TO noDate]; ConvertZone: PROC = { dst: BOOL = up.dst = yes; SELECT up.zone FROM 0 => IF ~dst THEN s.PutRope["GMT"]; NAT[5*BasicTime.minutesPerHour] => s.PutRope[IF dst THEN "EDT" ELSE "EST"]; NAT[6*BasicTime.minutesPerHour] => s.PutRope[IF dst THEN "CDT" ELSE "CST"]; NAT[7*BasicTime.minutesPerHour] => s.PutRope[IF dst THEN "MDT" ELSE "MST"]; NAT[8*BasicTime.minutesPerHour] => s.PutRope[IF dst THEN "PDT" ELSE "PST"]; ENDCASE => s.PutF["%g%02d%02d", [character[IF up.zone < 0 THEN '- ELSE '+]], [cardinal[up.zone.ABS/BasicTime.minutesPerHour]], [cardinal[up.zone.ABS MOD BasicTime.minutesPerHour]] ] }; s.PutF["%02d-%g-%02d ", [cardinal[up.day]], [rope[months.Substr[start: up.month.ORD*3, len: 3]]], [cardinal[up.year MOD 100]] ]; s.PutF["%02d:%02d:%02d ", [cardinal[up.hour]], [cardinal[up.minute]], [cardinal[up.second]]]; ConvertZone[]; }; $notEqual => s.PutRope["~="]; $greaterThan => s.PutChar['>]; ENDCASE; EXITS noDate => NULL; }; <> deleteDoc: ROPE = "{switch | pattern}* Deletes files matching pattern. -x exact level match"; listDoc: ROPE = "{switch | pattern}* Lists files matching pattern. -a print attachments -b brief format -d date sort -f full name print -h highest version -k keep print -n narrow print -o one line -p prefixes only -s size sort -t file type print -x exact level match -u unattached files only -z 0-length files only -> sort decreasing -< sort increasing"; nameDoc: ROPE = "PFSName -- "; prefixMapAddDoc: ROPE = "{-c|t} Adds a prefix map translation; omit to remove; -c means check if redefining and print out prev; -t means translate translation first."; prefixMapPrintDoc: ROPE = " Prints a prefix map translation; omit to print them all."; prefixMapTranslateDoc: ROPE = " Translates through the prefix map and prints the result."; copyDoc: ROPE = "newFile _ oldFile | directory _ {pattern}* Copies a file or files. -c force copy -r retain structure -u update only -x exact level match"; retrieveDoc: ROPE = "newFile _ oldFile | directory _ {pattern}* Uses retrieve to copy a file or files (necessary for large xns files). -r retain structure -u update only -x exact level match"; renameDoc: ROPE = "newFile _ oldFile | directory _ {pattern}* Renames a file or files. -r retain structure -u update only -x exact level match"; Init: PROC = { Commander.Register["Delete", DeleteCommandProc, deleteDoc]; Commander.Register["Del", DeleteCommandProc, deleteDoc]; Commander.Register["LS", ListCommandProc, listDoc]; Commander.Register["LSH", ListCommandProc, listDoc, $Highest]; Commander.Register["List", ListCommandProc, listDoc]; Commander.Register["ListH", ListCommandProc, listDoc, $Highest]; Commander.Register["PFSPATH", NameCommandProc, nameDoc]; Commander.Register["PrefixMapAdd", PrefixMapAdd, prefixMapAddDoc]; Commander.Register["PMA", PrefixMapAdd, prefixMapAddDoc]; Commander.Register["PrefixMapPrint", PrefixMapPrint, prefixMapPrintDoc]; Commander.Register["PMP", PrefixMapPrint, prefixMapPrintDoc]; Commander.Register["PrefixMapTranslate", PrefixMapTranslate, prefixMapTranslateDoc]; Commander.Register["PMT", PrefixMapTranslate, prefixMapTranslateDoc]; Commander.Register["Copy", CopyAndRename, copyDoc, $Copy]; Commander.Register["Rename", CopyAndRename, renameDoc]; Commander.Register["Retrieve", CopyAndRename, retrieveDoc, $Retrieve]; }; Init[]; END.