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.PutF1["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.PutF1[ "%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.gmt, 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.gmt, 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.PutF1[out, "-- %g files", [integer[filesSeen]] ]; IF bytesTotal > 0 THEN IO.PutF1[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 by Xerox Corporation. All rights reserved. Carl Hauser, August 24, 1989 4:43:16 pm PDT Michael Plass, November 25, 1991 1:19 pm PST Doug Wyatt, April 15, 1992 12:28 pm PDT Last tweaked by Mike Spreitzer on February 7, 1990 12:12:30 pm PST Chauser, September 28, 1990 11:16 am PDT Willie-s, August 14, 1992 3:10 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 –(cedarcode) style™codešœ™Kšœ Οeœ=™HK™+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šœžœ˜)š žœžœžœžœ#ž˜žœ˜Y—K˜—Kšœ.˜.Kšœ˜—šž˜Kšœ˜—K˜K˜—Kš œžœ˜1š œžœ žœžœžœžœ™:Kšœžœžœ™GKšžœ2žœžœ ˜FKš œ žœžœžœžœ˜/Kšœžœ˜"šžœžœž˜šœžœ˜Kšœ5˜5šžœ*žœžœž˜;Kšœ$žœ'žœ'˜xKšžœ˜—K˜—šžœ˜ Kšœ žœ˜0Kšœ$žœžœ ˜iKšœ˜——šž˜Kšœ˜—K˜K˜—Kš œžœ˜5š œžœ žœžœžœžœ™:Kšœžœžœ™GKšžœ2žœžœ ˜FKš œ žœžœžœžœ˜/Kšœžœ˜ šžœžœž˜šœžœ˜ K˜7K˜—šžœ˜ Kšœ žœ ˜1Kšœ$žœžœ ˜gKšœ˜——šž˜Kšœ˜—K˜K˜—Kš œžœ˜2š œžœ žœžœžœžœ™:Kšœžœžœ™GKšžœ2žœžœ ˜FKš œ žœžœžœžœ˜/šœžœžœžœ5˜LKšœ žœžœžœ ˜E—šœžœ˜&Kšœ žœžœžœ ˜A—Kšœ žœžœ˜(Kšœ'žœ˜Kšž˜Kšœ˜—K˜K˜——head™ š œ˜,Kš œ žœžœžœžœ˜/Kšœžœ ˜Kšœžœžœ˜Kšœžœ˜šœ žœ ˜šœ˜Kšžœ˜Kšžœ*˜,Kšžœžœ˜ Kšœ˜—Kšœ˜Kšœ žœ˜Kšžœžœ,žœžœ˜NKš žœ$žœ žœžœžœ(žœ˜‡Kšžœ˜Kšœ˜—šž˜Kšœ žœLžœžœ ˜kKšžœ žœžœžœ˜šžœžœ˜#Kšœ!™!Kšœžœžœ˜šžœžœžœž˜*Kšœžœ˜!šžœž˜ Kšœžœžœ˜ K˜"Kšžœ˜—Kšœžœ˜ Kšžœ˜—Kšžœ˜K˜—š œžœžœ žœžœžœžœ˜VKšœžœžœ˜'Kšœ˜Kšžœžœžœ˜'Kšžœžœ,˜CKšžœ#žœ(˜QKšžœžœžœžœ#˜dK˜—Kšœ˜Kšžœ˜—šž˜Kšœ˜—K˜K˜—š   œžœ žœ žœžœ˜;šžœ˜Kšžœžœ˜!Kšžœžœ7˜B—Kšœ˜K˜—š   œžœ žœ žœžœ˜<šžœ˜Kšžœžœ˜Kšžœžœ7˜B—Kšœ˜K˜—L˜—™š  œ˜(Kšœžœžœ˜!Kšœžœžœ˜Kšœ žœ'˜8Kšœ žœ#˜0Kšœ žœ$˜1Kšœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœ˜K˜š  œžœ žœ˜&Kšœ˜KšœX˜Xšœž˜šžœ ˜ Kšžœžœžœžœ ˜D—šžœ žœ˜Kšœ žœ žœ˜*Kšœžœ žœ˜(Kšœ žœ˜'Kš œ žœžœ žœžœžœ ˜Ušžœžœžœ˜;Kšœ{™{Kšœ9˜9Kšžœžœ˜ K˜—K˜—šžœžœž˜šœ ˜ šžœ ˜ šžœžœ'žœ˜3Kšžœžœ,˜9——K˜—˜Kšœžœžœžœ˜JKš œžœžœ ˜2Kšžœ!˜$Kšœ˜K˜—šžœžœ˜*K˜——šž˜K˜ —K˜—Kšœ˜K˜—K˜Kš œ žœžœžœžœ˜/Kš œžœžœžœžœžœ˜Kšœžœžœžœ˜Kšœžœ˜Kš œžœžœžœžœ˜Kš œ žœžœžœžœ˜šž˜Kšœ$˜$Kšœžœ˜%šžœžœž˜Kšœ žœžœ˜Kšœžœ˜šœ&˜&Kšœžœžœ˜šžœžœžœ ž˜šžœž˜ Kšœžœžœ˜!Kšœ˜Kšœ"˜"Kšœ˜Kšœ"˜"šžœ˜ KšœY˜YKšžœ˜ K˜——Kšœžœ˜ Kšžœ˜—Kšœ˜—šœ žœžœ$˜SKšœžœ˜Kšœ˜—Kšžœ,žœ˜E—Kšžœ˜—Kšœ˜šžœ˜šžœ˜Kšœžœžœ˜,Kšœ˜šžœ˜Kšžœ˜"šžœ˜šžœ žœ!žœ%˜WKšžœžœ,˜7—KšœA˜AKš žœžœžœžœ žœžœ˜1K˜——K˜—šžœ˜Kšœžœ ˜%Kšœ˜Kšœ˜——K˜K™hš žœžœžœžœžœžœž˜?Kšœžœžœ˜,šžœžœ˜Kšœ;˜;Kšžœžœ˜ K˜—šžœ ˜"šžœ˜Kšœ žœ ˜šœ žœ ˜Kšœžœžœ žœ™%Kšœžœ˜ Kšœžœžœ˜Kšœ žœ˜Kš žœžœ,žœžœžœ˜Ušžœ˜Kšžœ#˜'Kšžœ;˜?—Kšœ˜Kšœ,˜,Kšœ ˜ Kšžœžœ˜K˜—Kšžœ&žœ/˜[Kšœ$˜$Kšžœžœ7˜Nšžœžœ˜Kšœžœžœ˜šœ$˜$Kš žœžœ+žœžœžœ&˜rKšœ˜—Kšœ0˜0Kšœ4žœ˜9K˜—šžœ$˜'Kš œžœ žœžœžœžœ˜Q—Kšœ˜—šžœ˜Kšœžœ ˜KšœžœF˜NKšœ,˜,Kšœ ˜ Kš žœžœžœžœžœ˜K˜——Kšœ˜Kšžœ˜—šž˜Kšœ˜—K˜—K™—™ š œ˜*š œžœ žœžœžœžœ™:Kšœžœžœ™G—–. -- [fullFName: ROPE] RETURNS [continue: BOOL]š  œžœ ˜Kšœžœžœ žœ™%Kšœ žœžœ˜Kšœžœ˜+Kšœžœ˜Kšœ žœ žœ˜*Kšœ žœ ˜Kšœžœ˜Kšœ žœ˜Kšœ˜Kšžœžœ4žœžœ˜Ušžœžœ žœ˜K™KšœQžœ˜jK˜—Kšžœ žœžœžœ˜+Kš žœ žœžœžœžœ˜1Kšœžœ<žœ˜YK˜Kšžœ žœ!˜2šžœžœž˜šœ˜Kšœžœ ˜Kšœ˜šžœžœ˜Kšœ˜Kšœ˜K˜—K˜—Kšœ1˜1Kšžœ˜—K˜K˜—š  œžœ ˜Kšœžœ˜Kšœ žœ˜Kšœ˜Kšžœžœ9žœžœ˜ZKšžœ žœžœžœ˜+Kš žœ žœžœžœžœ˜1KšœžœL˜VK˜Kšžœ žœ!˜2šžœžœž˜šœ˜Kšœžœ ˜Kšœ˜šžœžœ˜Kšœ˜Kšœ˜K˜—K˜—Kšœ1˜1Kšžœ˜—K˜K˜—š  œžœ˜'Kš œžœžœ žœ žœžœžœ ™_Kšœžœ ˜Kšœ žœžœ˜3Kšœ˜š žœžœ žœžœ žœ˜*Kšœ™Kšœ˜šžœžœ˜Kšžœžœ˜-Kšžœžœ žœžœ˜-K˜—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—šžœžœ˜Kšœ žœ˜$Kšœ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šžœ2˜4Kšžœžœžœ8˜QKšžœ˜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šžœ˜—…—^ϊŠΗ