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 { 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 { 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 { 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 { 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 = { 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 = { EachFileName: PFS.NameProc = { 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] = { 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 => { }; '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 = { 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 ]; 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. Μ PFSCommandsImpl.mesa Copyright Σ 1989, 1990, 1991, 1992 by Xerox Corporation. All rights reserved. Carl Hauser, August 24, 1989 4:43:16 pm PDT Michael Plass, January 3, 1990 4:16:36 pm PST Doug Wyatt, February 2, 1990 5:03:58 pm PST Last tweaked by Mike Spreitzer on February 7, 1990 12:12:30 pm PST Chauser, September 28, 1990 11:16 am PDT Willie-s, April 24, 1992 12:32 pm PDT [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] Delete Files This is a switch, not a file name Copying and Renaming 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. If we get here, then for each of the filenames and patterns, copy the file to the destination directory. [name: PATH] RETURNS [continue: BOOL] List Command [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] [name: PATH] RETURNS [continue: BOOL] We may need the info item: REF [fullUName, attachedTo: PATH, created: GMT, bytes: INT, keep: CARDINAL, PFS.FileType] Factor out the directories In these cases it is better to enumerate the names, then get the info In these cases it is better to enumerate for info, to reduce server traffic ... 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. do we have a new prefix? We have a new lagging prefix, so scan backwards for the LAST directory -- remoteCheck _ sense; This argument sets switches for the remaining patterns Now the argument is assumed to be a file pattern. [x: Item, y: Item, data: REF] RETURNS [BOOL] Utilities '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).) Note: Dates returned by ParseFromStream are guaranteed to have `gmt' = nullGMT if `format' ~= $explicit. The other procedures of this interface ignore the 'gmt' field if 'format' ~= $explicit. Initialization Κ ϊ•NewlineDelimiter ™codešœ™K™NK™+K™-K™+K™BK™(K™%K˜šΟk ˜ Kšœ œœ}˜‘Kšœ œ,˜;Kšœœ˜#Kšœ˜Kšœœ˜Kšœœ³˜ΌKšœ œmœX˜ΧKšœ œ?˜QKšœœ5˜HKšœœ˜Kšœ œ˜*Kšœœ2œ ˜JKšœ œœ˜——K˜šΠlnœœ˜Kšœ œœR˜†Kšœ˜K˜Kšœœ œ˜Kš œœœœœœ˜Kšœœœ˜Kšœœ œ˜šœœœœ˜K˜—šΟnœœœ˜ K˜—š Ÿ œœœœœ˜;Kšœ œœ ˜!Kšœ œ œ œ œ œœ˜WKšœ ˜K˜K˜—Kšœœœœ˜,šŸ œœ œœœœœ˜MKšœœ&œœ˜h•StartOfExpansion6[pattern: ROPE, object: ROPE, case: BOOL _ TRUE]šœ!œ˜)Kšœœ˜ K–[self: STREAM, char: CHAR]šœ ˜"Kš œœœœœ˜Qšœœ˜Kšœœ˜!Kšœœ˜#—Kšœ˜—K˜K˜—š Ÿœœ œœœœ˜4Kšœœ)˜4K˜K˜—KšŸ œœ˜/š œœ œœœœ™:Kšœœœ™GKšœ2œœ ˜FKšœœœ˜(Kš œ œœœœ˜/Kšœœ˜Kšœœ ˜+š˜Kšœ œœœ˜Kšœœ œœ˜Mšœœœ˜*šœ˜ Kšœœ˜Kšœ œœ˜ šœ˜ K˜SKšœ˜K˜——Kšœ˜—K˜%Kšœ˜—Kšœ!˜!šœœ˜šœœ˜K˜0Kšœ˜K˜—šœ œ˜Kšœ!˜!Kšœ˜——šœ˜ Kšœœ3˜Išœ œ˜Kšœœ˜)š œœœœ#˜˜MKšœœ œœ˜*K˜—šœœ˜Kšœœ˜)šœ˜Kšœ˜K˜—šœ˜ Kš œœœ œœ˜@Kšœ<˜>šœ!˜#Kšœœ˜Kšœ.˜2—šœ œ˜šœœœ˜#Kšœ˜Kšœ˜Kšœ˜Kšœœ*˜7—Kšœ˜Kšœ˜—Kšœ œœœœœ œœœ"˜˜K˜——Kšœœ œœ˜-K˜—š Ÿ œœ œœœ˜=šŸœœ˜šœœ œœ˜0Kšœ˜Kšœ%˜'K˜Kšœœ˜ —K˜"šœ œ˜Kšœ4˜4Kšœœ˜Kšœ˜—šœ˜Kšœ8˜8—šœœ˜Jšœ œ˜$Jšœ6˜6Kšœ˜—Kšœœ˜ šœœ˜Kšœ2œ˜7Kšœ@˜@Kšœ œ˜—Kšœ œ˜šœ œ˜šœœ)˜1K™E—šœœ)˜1K™K——Kšœ œ˜šœœœ˜Kšœ œœ˜šœ˜&Kšœœ˜2šœ œ˜Kšœ)œœ˜5Kšœ˜K˜—Kšœ˜Kšœ˜—K˜—š˜Kšœœœ˜+—K˜—Kšœœœ*˜=Kšœ+œœ˜HKšœ&˜&K˜—šŸ œœ œ˜'KšœΏœp™²Kš œ œœ œœœœ˜Kšœ œœ˜Kšœ™Kšœ&œœ˜4K˜—Kšœ8œ ™FKšœ˜Kšœ2˜2Kšœ˜—šŸ œœ œ˜&Kšœœœ ˜Kšœœœœ˜GKšœ˜K˜—šŸ œœ œ˜&Kšœœ ˜Kšœœœœ˜šœœ˜Kšœ˜Kšœ˜—š œœœœ˜8Kšœœœ˜9K˜ Kšœ˜—K˜—Kšœ œœ˜Kšœœ ˜Kšœ œœ˜Kšœœ˜Kšœ&œ˜.Kšœ›œœ˜¨Kšœœ&˜8Kšœœ˜Kšœ œœ˜Kšœœœ˜Kšœœ˜Kš œ œœœœ˜/šŸœœœ˜%Kšœœœ˜K˜šœœœ˜*šœ˜"Kšœœœ˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜šœ ˜ Kšœ˜Kšœ˜šœ˜ Kšœœœ œ˜C—K˜—Kšœ˜Kšœ˜K˜K˜K˜Kšœ˜šœ ˜ Kšœ™Kšœ˜—šœ ˜ Kšœ˜Kšœ˜šœ˜ Kšœœœ œ ˜<—Kšœ˜—K˜K˜!Kšœ!˜!Kšœ ˜ šœ˜ Kšœ˜KšœQ˜QKšœ˜Kšœ˜——Kšœœ˜ Kšœ˜—K˜K˜š˜KšœVœœ ˜eKšœ œœœ˜Kšœœœ˜(šœ!œ˜)Kšœ6™6Kšœ˜Kšœœœ˜!Kšœ˜Kšœ˜—Kšœ1™1šœ œ'œœ˜QKšœ˜Kšœ%˜'K˜Kšœœ ˜—Kšœ˜—š œœ œ!œœ˜eKšœ˜Kšœ%˜'K˜Kšœœ ˜—Kšœ œœ˜%šœœ˜Kš œœœœœ˜Kšœ1˜3Kšœœœ7˜PKšœ˜Kšœœ˜K˜—š˜Kšœ˜—K˜—K˜Kšœ œœ ˜!š œ œœœ œ œ œ ˜kK˜—šŸœ˜$Kšœœœœ™,Kšœœ˜Kšœœ˜Kšœ œœ˜š œœœœ˜7šœ ˜˜Kšœœœ˜%Kšœ0˜6K˜—˜Kšœœœ˜%Kšœ0˜6K˜—˜ Kšœœœ˜!Kšœ˜K˜—˜ Kšœœœ˜!Kšœ˜K˜—Kšœ˜—Kšœ˜—Kšœ/œ ˜DK˜K˜———™ ™K˜K˜Kšœ œ.˜>šœœœ˜Kšœ˜Kšœœ˜Kšœυ™υKšœ˜K™ΒK˜—šŸ œœœ˜.šœ ˜šœ˜Kšœœ*˜6˜2Kšœ<œœ ˜J—šŸ œœ˜Kšœœ˜šœ ˜Kšœœœ˜#Kšœ*œœœ˜KKšœ*œœœ˜KKšœ*œœœ˜KKšœ*œœœ˜Kšœ˜ šœ˜Kšœ œ œœ˜,Kšœœ˜1Kšœœœ˜4K˜———K˜—šœ˜Kšœ˜Kšœ$œ˜5Kšœœ˜K˜—šœ˜KšœC˜C—Kšœ˜K˜—Kšœ˜Kšœ˜Kšœ˜—š˜Kšœ œ˜—K˜K˜—˜K˜———™˜šœ œP˜_K˜—šœ œυ˜‚K˜—šœ œ,˜9K˜—šœœ·˜ΜK˜—šœœP˜gK˜—šœœL˜gK˜—šœ œ˜˜₯K˜—šœ œ·˜ΘK˜—šœ œ‰˜˜K˜—K˜šŸœœ˜K˜Kšœ;˜;šœ8˜8K˜—Kšœ3˜3Kšœ>˜>Kšœ5˜5Kšœ@˜@K˜K˜8K˜K˜BK˜9K˜HK˜=KšœT˜TKšœE˜EK˜Kšœ:˜:Kšœ7˜7KšœF˜FK˜K˜—K˜—K˜—Kšœ˜—…—^ψŠΎ