DIRECTORY BasicTime USING [GMT, nullGMT], Commander USING [CommandProc, Handle, Register], CommandTool USING [ArgumentVector, GetProp, Failed, Parse], Convert USING [Error, IntFromRope], DFUtilities USING [DateToStream], FileNames USING [ConvertToSlashFormat, CurrentWorkingDirectory, DirectoryContaining, GetShortName, HomeDirectory, IsADirectory, IsAPattern, ResolveRelativePath, StripVersionNumber], FS USING [Close, Copy, Delete, EnumerateForNames, Error, ErrorDesc, ExpandName, FileInfo, Open, NameProc, nullOpenFile, OpenFile, Rename, SetKeep, StreamOpen], FSBackdoor USING [Flush, EntryPtr, Enumerate, EnumerateCacheForNames, MakeFName, NameProc, ScavengeDirectoryAndCache, SetFreeboard, TextFromTextRep, VolumePages], FSPseudoServers USING [DeletePseudoServer, GetPseudoServers, InsertPseudoServer, Lookup, PseudoServerFromRope, PseudoServerList], IO USING [Close, EndOf, EndOfStream, Error, GetBlock, PutBlock, PutChar, PutF, PutFR, PutFR1, PutRope, PutText, Reset, STREAM, TextFromTOS, TOS], List USING [PutAssoc], Process USING [CheckForAbort], ProcessProps USING [GetPropList], ReadEvalPrint USING [Handle], Rope USING [Cat, Concat, Equal, Fetch, Find, Flatten, Index, Length, Match, Replace, ROPE, Run, SkipTo, Substr, Text], ViewerClasses USING [Viewer], ViewerOps USING [PaintViewer]; FSFileCommandsImpl: CEDAR PROGRAM IMPORTS Commander, CommandTool, Convert, DFUtilities, FileNames, FS, FSBackdoor, FSPseudoServers, IO, List, Process, ProcessProps, Rope, ViewerOps = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; FSErrorMsg: PROC [error: FS.ErrorDesc] RETURNS [ROPE] = { SELECT error.group FROM lock => RETURN[" -- locked!\n"]; ENDCASE => IF error.code = $unknownFile THEN RETURN [" -- not found!\n"] ELSE RETURN[Rope.Cat[" -- FS.Error: ", error.explanation, "\n"]]; }; FSErrorMsg1: PROC [error: FS.ErrorDesc] RETURNS [ROPE] = { SELECT error.group FROM lock => RETURN[" -- locked!"]; ENDCASE => IF error.code = $unknownFile THEN RETURN [" -- not found!"] ELSE RETURN[Rope.Concat["\n -- FS.Error: ", error.explanation]]; }; createKeep: CARDINAL _ 2; DeleteFiles: Commander.CommandProc = { out: STREAM = cmd.out; exactLevelMatch: BOOL _ FALSE; anglesRequired: INT _ 0; deleteIt: FS.NameProc = { Process.CheckForAbort[]; continue _ TRUE; IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN; IO.PutRope[out, " deleting "]; IO.PutRope[out, fullFName]; FS.Delete[fullFName ! FS.Error => IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE} ]; IO.PutRope[out, "\n"]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; FOR i: NAT IN [1..argv.argc) DO pattern: ROPE _ argv[i]; 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 FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GO TO Die}; pattern _ FileNames.ResolveRelativePath[pattern]; pattern _ FS.ExpandName[pattern].fullFName; IF pattern.Length[] = 0 THEN LOOP; IF exactLevelMatch THEN anglesRequired _ CountAngles[pattern]; IF Rope.Find[pattern, "!"] = -1 THEN pattern _ Rope.Concat[pattern, "!L"]; IF Rope.Find[pattern, "*"] = -1 THEN [] _ deleteIt[pattern] ELSE FS.EnumerateForNames[pattern, deleteIt]; }; Process.CheckForAbort[]; ENDLOOP; EXITS Die => result _ $Failure; }; TypeFile: Commander.CommandProc = { block: REF TEXT _ NEW[TEXT[256]]; nBytesRead: NAT _ 0; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc < 2 THEN GOTO Usage; FOR i: NAT IN [1..argv.argc) DO fileStream: STREAM _ FS.StreamOpen[FileNames.ResolveRelativePath[argv[i]] ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GO TO Die}]; WHILE NOT IO.EndOf[fileStream] DO nBytesRead _ IO.GetBlock[fileStream, block, 0, 256 ! IO.EndOfStream => EXIT; IO.Error => EXIT; ]; IO.PutBlock[cmd.out, block, 0, nBytesRead ! IO.EndOfStream => EXIT; IO.Error => EXIT; ]; Process.CheckForAbort[]; ENDLOOP; IF fileStream # NIL THEN IO.Close[fileStream]; ENDLOOP; EXITS Die => result _ $Failure; Usage => RETURN[$Failure, "Usage: Type list-of-patterns\n"]; }; PrintFSCacheInfo: Commander.CommandProc = { volName: ROPE _ NIL; size, free, freeboard: INT; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc > 2 THEN {msg _ "Usage: PrintFSCacheInfo {volumeName}"; GO TO Die}; IF argv.argc = 2 THEN volName _ argv[1]; [size, free, freeboard] _ FSBackdoor.VolumePages[volName ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GOTO Die}]; msg _ IO.PutFR[" size = %g, free = %g, freeboard = %g\n", [integer[size]], [integer[free]], [integer[freeboard]] ]; EXITS Die => result _ $Failure; }; SetKeep: Commander.CommandProc = { pattern: ROPE; keep: INT; bangIndex: PROC [r: ROPE] RETURNS [INT] = { FOR i: INT DECREASING IN [0 .. Rope.Length[r]) DO IF Rope.Fetch[r, i] = '! THEN RETURN [i]; ENDLOOP; RETURN [Rope.Length[r]]; }; setIt: FS.NameProc = { fullFName _ Rope.Substr[fullFName, 0, bangIndex[fullFName]]; cmd.out.PutF[" processing %g", [rope[fullFName]]]; FS.SetKeep[fullFName, keep]; cmd.out.PutChar['\n]; RETURN[NOT AbortRequested[cmd]]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc # 3 THEN GO TO Usage; keep _ Convert.IntFromRope[argv[1] ! Convert.Error => GO TO Usage]; pattern _ Rope.Replace[base: argv[2], start: bangIndex[argv[2]], with: "!H"]; FS.EnumerateForNames[pattern, setIt ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GO TO Die}]; EXITS Die => result _ $Failure; Usage => RETURN[$Failure, "Usage: SetKeep keep pattern"]; }; SetFSFreeboard: Commander.CommandProc = { freeBoard: INT; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc # 2 THEN GOTO Usage; freeBoard _ Convert.IntFromRope[argv[1] ! Convert.Error => GOTO Usage]; FSBackdoor.SetFreeboard[freeBoard ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GOTO Die;}]; EXITS Usage => RETURN[$Failure, "Usage: SetFreeboard nPages"]; Die => result _ $Failure; }; PrintWorkingDirectory: Commander.CommandProc = { RETURN[NIL, FileNames.CurrentWorkingDirectory[]]; }; ChangeWorkingDirectory: Commander.CommandProc = { wDir: ROPE; root: BOOL _ FALSE; push: BOOL _ FALSE; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg _ errorMsg; GO TO oops}]; list: LIST OF ROPE _ NARROW[CommandTool.GetProp[cmd, $WorkingDirectoryStack]]; SELECT cmd.procData.clientData FROM $CDR => root _ TRUE; $PDR => root _ push _ TRUE; $PD => push _ TRUE; ENDCASE; IF push THEN list _ CONS[FileNames.CurrentWorkingDirectory[], list]; SELECT argv.argc FROM 1 => wDir _ IF root THEN "///" ELSE FileNames.HomeDirectory[]; 2 => { wDir _ argv[1]; IF root THEN wDir _ FS.ExpandName[wDir, "///" ! FS.Error => {msg _ FSErrorMsg[error]; GO TO oops}; ].fullFName; }; ENDCASE => {msg _ "Usage: ChangeWorkingDirectory directoryName"; GO TO oops}; [result, msg] _ SetWD[cmd, wDir]; IF result # $Failure AND push THEN cmd.propertyList _ List.PutAssoc[$WorkingDirectoryStack, list, cmd.propertyList]; EXITS oops => result _ $Failure; }; PopWD: Commander.CommandProc = { list: LIST OF ROPE _ NARROW[CommandTool.GetProp[cmd, $WorkingDirectoryStack]]; IF list = NIL THEN RETURN[NIL, FileNames.CurrentWorkingDirectory[]]; [result, msg] _ SetWD[cmd, list.first]; list _ list.rest; cmd.propertyList _ List.PutAssoc[key: $WorkingDirectoryStack, val: list, aList: cmd.propertyList]; }; FindExcessVersionsCommand: Commander.CommandProc = { Laggards: TYPE = RECORD [ s: SEQUENCE size: NAT OF RECORD [ name: ROPE, bang: INT ] ]; laggards: REF Laggards _ NIL; out: STREAM = cmd.out; keep: (0..LAST[NAT]] _ 1; lagIdx: INT _ 0; -- current interesting laggard is laggards[lagIdx] inner: FS.NameProc = { bang: INT _ Rope.Length[fullFName]; pos: INT _ bang; continue _ TRUE; WHILE pos > 0 DO IF Rope.Fetch[fullFName, pos _ pos - 1] = '! THEN {bang _ pos; EXIT}; ENDLOOP; IF laggards[lagIdx].bang = bang AND Rope.Run[laggards[lagIdx].name, 0, fullFName, 0, FALSE] >= bang THEN { SELECT TRUE FROM NOT kill => { IO.PutRope[cmd.out, laggards[lagIdx].name]; IO.PutRope[cmd.out, " "]; }; justCache => { IO.PutRope[out, "\nflushing "]; IO.PutRope[out, laggards[lagIdx].name]; FSBackdoor.Flush[laggards[lagIdx].name ! FS.Error => IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}]; }; ENDCASE => { IO.PutRope[cmd.out, "\ndeleting "]; IO.PutRope[cmd.out, laggards[lagIdx].name]; FS.Delete[laggards[lagIdx].name ! FS.Error => IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}]; }; }; laggards[lagIdx].name _ fullFName; laggards[lagIdx].bang _ bang; lagIdx _ (lagIdx+1) MOD laggards.size }; kill: BOOL _ FALSE; justCache: BOOL _ TRUE; DoPattern: PROC [pattern: ROPE] = { laggards _ NEW[Laggards[keep]]; FOR i: INT IN [0..laggards.size) DO laggards[i] _ [name: "", bang: -1] ENDLOOP; lagIdx _ 0; IF justCache THEN FSBackdoor.EnumerateCacheForNames[pattern: pattern, proc: inner ! FS.Error => IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; CONTINUE}] ELSE FS.EnumerateForNames[pattern: pattern, proc: inner ! FS.Error => IF error.group # bug THEN { IO.PutRope[cmd.out, FSErrorMsg1[error]]; CONTINUE; }]; IO.PutRope[cmd.out, "\n"]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO bogus}]; argBase: INT _ 1; didPattern: BOOL _ FALSE; WITH cmd.procData.clientData SELECT FROM opt: ATOM => SELECT opt FROM $FindReal => {kill _ FALSE; justCache _ FALSE}; $KillReal => {kill _ TRUE; justCache _ FALSE}; $FindCached => {kill _ FALSE; justCache _ TRUE}; $KillCached => {kill _ TRUE; justCache _ TRUE}; ENDCASE => ERROR; ENDCASE; WHILE (argv # NIL) AND (argBase < argv.argc) DO arg: ROPE = argv[argBase]; IF Rope.Length[arg] > 0 AND Rope.Fetch[arg, 0] = '- THEN { FOR j: INT IN [1..Rope.Length[arg]) DO SELECT Rope.Fetch[arg, j] FROM 'k, 'K -- keep -- => { k: INT; IF NOT ((argBase _ argBase+1) < argv.argc) THEN { msg _ "Missing number of versions to keep"; GO TO bogus}; k _ Convert.IntFromRope[argv[argBase] ! Convert.Error => { msg _ "Can't parse number of versions to keep"; GO TO bogus}]; IF NOT (k IN [1..100]) THEN { msg _ "Can't keep less than 1 nor more than 100 versions"; GO TO bogus}; keep _ k; }; ENDCASE => NULL; ENDLOOP; -- j } ELSE {DoPattern[arg]; didPattern _ TRUE}; argBase _ argBase+1; ENDLOOP; IF NOT didPattern THEN DoPattern["*"]; EXITS bogus => result _ $Failure; }; FSFlushCache: Commander.CommandProc = { out: STREAM = cmd.out; pattern: ROPE; flushIt: FSBackdoor.NameProc = { out.PutF[" flushing %g", [rope[fullGName]]]; FSBackdoor.Flush[fullGName ! FS.Error => { IF error.group = $lock THEN { out.PutRope[" -- Locked!"]; CONTINUE; } ELSE out.PutRope[" -- Error!\n"]; }]; out.PutRope["\n"]; RETURN[NOT AbortRequested[cmd]]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; FOR i: NAT IN [1..argv.argc) DO pattern _ FileNames.ResolveRelativePath[argv[i]]; {ENABLE FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GO TO Die}; IF Rope.Find[pattern, "*"] = -1 THEN [] _ flushIt[pattern] ELSE FSBackdoor.EnumerateCacheForNames[flushIt, NIL, pattern]; }; IF AbortRequested[cmd] THEN EXIT; ENDLOOP; EXITS Die => result _ $Failure; }; FSListBTree: Commander.CommandProc = { out: STREAM _ cmd.out; pattern: Rope.Text _ NIL; printLocal: BOOL _ FALSE; printAttached: BOOL _ FALSE; printCached: BOOL _ TRUE; anySwitch: BOOL _ FALSE; localText: REF TEXT _ NEW[TEXT[64]]; localOut: STREAM _ IO.TOS[localText]; accept: PROC RETURNS [stop: BOOL] = { IO.PutText[out, IO.TextFromTOS[localOut]]; RETURN[stop: AbortRequested[cmd]]; }; print: UNSAFE PROC [entry: FSBackdoor.EntryPtr] RETURNS [accept, stop: BOOL _ FALSE] = UNCHECKED { name: ROPE = FSBackdoor.MakeFName[ FSBackdoor.TextFromTextRep[@entry[entry.nameBody]], entry.version]; IO.Reset[localOut]; WITH e: entry^ SELECT FROM local => IF printLocal THEN { IO.PutF[localOut, "L %g\n", [rope[name]]]; accept _ TRUE; }; attached => IF printAttached THEN { toName: ROPE = FSBackdoor.TextFromTextRep[@entry[e.attachedTo]]; IO.PutF[localOut, "A %-30g (%g)\n", [rope[name]], [rope[toName]]]; accept _ TRUE; }; cached => IF printCached THEN { IO.PutF[localOut, "C %-40g ", [rope[name]]]; DFUtilities.DateToStream[localOut, [explicit, e.used] ]; IO.PutRope[localOut, "\n"]; accept _ TRUE; }; ENDCASE => ERROR; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc = 1 THEN FSBackdoor.Enumerate[ volName: NIL, nameBodyPattern: "[*", localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: print, acceptProc: accept] ELSE FOR i: NAT IN [1..argv.argc) DO IF argv[i].Fetch[0] = '- THEN { IF NOT anySwitch THEN { printLocal _ FALSE; printAttached _ FALSE; printCached _ FALSE; anySwitch _ TRUE; }; FOR j: INT IN [1..argv[i].Length[]) DO SELECT argv[i].Fetch[j] FROM 'c => printCached _ TRUE; 'l => printLocal _ TRUE; 'a => printAttached _ TRUE; ENDCASE; ENDLOOP; Process.CheckForAbort[]; LOOP; }; pattern _ Rope.Flatten[FileNames.ResolveRelativePath[argv[i]]]; FSBackdoor.Enumerate[ volName: NIL, nameBodyPattern: pattern, localOnly: FALSE, allVersions: TRUE, version: [0], matchProc: print, acceptProc: accept]; IF AbortRequested[cmd] THEN EXIT; ENDLOOP; EXITS Die => result _ $Failure; }; FSLoad: Commander.CommandProc = { out: STREAM _ cmd.out; fetch: FS.NameProc = { file: FS.OpenFile _ FS.nullOpenFile; out.PutRope[" loading "]; out.PutRope[fullFName]; { file _ FS.Open[fullFName, read ! FS.Error => IF error.group # bug THEN {IO.PutRope[out, FSErrorMsg1[error]]; GO TO err} ]; FS.Close[file]; EXITS err => {}}; out.PutRope["\n"]; RETURN[NOT AbortRequested[cmd]]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; FOR i: NAT IN [1..argv.argc) DO pattern: ROPE _ FileNames.ResolveRelativePath[argv[i]]; IF pattern.Find["!"] = -1 THEN pattern _ Rope.Concat[pattern, "!H"]; FS.EnumerateForNames[pattern, fetch ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg1[error]; GO TO Die}]; IF AbortRequested[cmd] THEN EXIT; ENDLOOP; EXITS Die => result _ $Failure; }; FSScavenge: Commander.CommandProc = { volName: ROPE _ NIL; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd, TRUE ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; IF argv.argc > 1 THEN volName _ argv[1]; FSBackdoor.ScavengeDirectoryAndCache[volName ! FS.Error => {msg _ FSErrorMsg[error]; GO TO Die}]; EXITS Die => result _ $Failure; }; CopyAndRename: Commander.CommandProc = { forceCopy: BOOL _ FALSE; destinationDirectory: ROPE _ NIL; leftArrowExists: BOOL; doACopy: BOOL _ cmd.procData.clientData = $Copy; doStore: BOOL _ cmd.procData.clientData = $Store; dirBeforeStar: ROPE; dirBeforeStarLength: INT; retainStructure: BOOL _ FALSE; updateOnly: BOOL _ FALSE; exactLevelMatch: BOOL _ FALSE; anglesRequired: INT _ 0; HandleAFile: PROC [to, from: ROPE] = { Process.CheckForAbort[]; cmd.out.PutF[" %g _ %g", [rope[to]], [rope[from]]]; {ENABLE FS.Error => IF error.group # bug THEN {msg _ FSErrorMsg1[error]; GO TO skipIt}; IF updateOnly THEN { sourceTime: BasicTime.GMT _ BasicTime.nullGMT; destTime: BasicTime.GMT _ BasicTime.nullGMT; sourceTime _ FS.FileInfo[from].created; destTime _ FS.FileInfo[to ! FS.Error => IF error.group # bug THEN CONTINUE].created; IF sourceTime = destTime AND sourceTime # BasicTime.nullGMT THEN { cmd.out.PutRope["\n -- not copied, create dates match"]; GO TO skipIt; }; }; IF doACopy THEN [] _ FS.Copy[from: from, to: to, keep: createKeep, attach: NOT forceCopy] ELSE FS.Rename[from: from, to: to]; EXITS skipIt => {}; }; cmd.out.PutRope["\n"]; }; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO Die}]; nArgs: NAT _ argv.argc; { i: NAT _ 1; length: INT; Bump: PROC [scratch: NAT] = { FOR j: NAT IN (scratch..nArgs) DO argv[j - 1] _ argv[j]; ENDLOOP; nArgs _ nArgs - 1; }; WHILE i < nArgs DO length _ argv[i].Length[]; SELECT TRUE FROM length = 0 => Bump[i]; argv[i].Fetch[0] = '- => { FOR j: INT IN [1..length) DO SELECT argv[i].Fetch[j] FROM 'c, 'C => forceCopy _ TRUE; 'r, 'R => retainStructure _ TRUE; 'u, 'U => updateOnly _ TRUE; 'x, 'X => exactLevelMatch _ TRUE; ENDCASE; ENDLOOP; Bump[i]; }; ENDCASE => i _ i + 1; ENDLOOP; }; -- end of switch processing leftArrowExists _ nArgs >= 3 AND Rope.Equal[argv[2], "_"]; FOR i: NAT IN [1..nArgs) DO argv[i] _ FileNames.ConvertToSlashFormat[FileNames.ResolveRelativePath[argv[i]]]; ENDLOOP; IF leftArrowExists THEN { IF FileNames.IsADirectory[argv[1]] THEN destinationDirectory _ argv[1] ELSE { IF nArgs # 4 OR FileNames.IsAPattern[argv[1]] OR FileNames.IsAPattern[argv[3]] THEN RETURN[$Failure, "Bad syntax for copying a file"]; HandleAFile[from: argv[3], to: argv[1]]; RETURN[IF msg # NIL THEN $Failure ELSE NIL, msg]; }; } ELSE destinationDirectory _ FileNames.ConvertToSlashFormat[FileNames.CurrentWorkingDirectory[]]; FOR i: NAT IN [(IF leftArrowExists THEN 3 ELSE 1) .. nArgs) DO IF FileNames.IsADirectory[argv[i]] THEN { msg _ Rope.Concat["Cannot copy a directory: ", argv[i]]; GO TO Die; }; IF FileNames.IsAPattern[argv[i]] THEN { pattern: ROPE _ argv[i]; handleIt: FS.NameProc = { to: ROPE; short: ROPE _ NIL; continue _ TRUE; IF SkipFunny[fullFName] THEN RETURN; IF exactLevelMatch AND anglesRequired # CountAngles[fullFName] THEN RETURN; fullFName _ FileNames.ConvertToSlashFormat[fullFName]; IF retainStructure THEN to _ FileNames.StripVersionNumber[fullFName.Substr[dirBeforeStarLength]] ELSE to _ FileNames.GetShortName[fullFName]; to _ Rope.Concat[destinationDirectory, to]; HandleAFile[from: fullFName, to: to]; RETURN[msg = NIL]; }; IF pattern.Find["!"] = -1 THEN pattern _ Rope.Concat[pattern, "!H"]; IF pattern.Fetch[0] # '/ THEN pattern _ Rope.Concat[FileNames.CurrentWorkingDirectory[], pattern]; IF exactLevelMatch THEN anglesRequired _ CountAngles[pattern]; dirBeforeStar _ FileNames.DirectoryContaining[ path: pattern, pos: Rope.Index[s1: pattern, s2: "*"]]; dirBeforeStarLength _ dirBeforeStar.Length[]; FS.EnumerateForNames[pattern, handleIt ! FS.Error => IF error.group # $bug THEN {msg _ FSErrorMsg[error]; GO TO Die}]; } ELSE { to: ROPE _ Rope.Concat[destinationDirectory, FileNames.GetShortName[argv[i]]]; HandleAFile[from: argv[i], to: to]; IF msg # NIL THEN GO TO Die; }; Process.CheckForAbort[]; ENDLOOP; EXITS Die => result _ $Failure; }; PseudoServerAddCommand: Commander.CommandProc = { args: CommandTool.ArgumentVector _ CommandTool.Parse[cmd ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; list: FSPseudoServers.PseudoServerList _ NIL; server: ROPE _ NIL; IF args.argc <= 1 THEN { msg _ "Usage: PseudoServerAdd server write read ..."; GO TO failed}; IF args.argc = 2 THEN { server _ args[1]; IF FSPseudoServers.DeletePseudoServer[server] # NIL THEN msg _ IO.PutFR1["'%g' deleted from pseudo server list.", [rope[server]]] ELSE msg _ IO.PutFR1["'%g' not in pseudo server list.", [rope[server]]]; RETURN}; [msg, list] _ FSPseudoServers.PseudoServerFromRope[cmd.commandLine]; IF msg # NIL THEN GO TO failed; IF list = NIL THEN {msg _ "empty??"; GO TO failed}; server _ list.first.server; IF FSPseudoServers.Lookup[server] # NIL THEN msg _ IO.PutFR1["'%g' updated in pseudo server list.", [rope[server]]] ELSE msg _ IO.PutFR1["'%g' added to pseudo server list.", [rope[server]]]; FSPseudoServers.InsertPseudoServer[list]; EXITS failed => {result _ $Failure}; }; PseudoServerPrintCommand: Commander.CommandProc = { serverList: FSPseudoServers.PseudoServerList _ FSPseudoServers.GetPseudoServers[]; out: STREAM = cmd.out; PutName: PROC [name: ROPE] = { IO.PutRope[out, " "]; IF Rope.Length[name] = 0 THEN IO.PutRope[out, "$"] ELSE IO.PutRope[out, name]; }; FOR each: FSPseudoServers.PseudoServerList _ serverList, each.rest WHILE each # NIL DO IF each.first.server = NIL THEN LOOP; IO.PutRope[out, " "]; PutName[each.first.server]; IF each.first.avoidCheck THEN PutName[" -r"]; PutName[each.first.write]; FOR readers: LIST OF ROPE _ each.first.read, readers.rest WHILE readers # NIL DO PutName[readers.first]; ENDLOOP; IO.PutRope[out, "\n"]; ENDLOOP; }; SetWD: PROC [cmd: Commander.Handle, wDir: ROPE] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] = { wDir _ FileNames.ResolveRelativePath[FileNames.ConvertToSlashFormat[wDir]]; IF wDir.Length[] = 0 THEN RETURN[$Failure, "empty working directory"]; IF wDir.Fetch[0] # '/ THEN wDir _ Rope.Concat[FileNames.CurrentWorkingDirectory[], wDir]; IF wDir.Fetch[wDir.Length[] - 1] # '/ THEN wDir _ Rope.Concat[wDir, "/"]; IF wDir.Length[] < 3 THEN RETURN[NIL]; msg _ wDir; [] _ List.PutAssoc[key: $WorkingDirectory, val: wDir, aList: ProcessProps.GetPropList[]]; WITH CommandTool.GetProp[cmd, $ReadEvalPrintHandle] SELECT FROM rep: ReadEvalPrint.Handle => { name: ROPE _ Rope.Concat["CommandTool: WD = ", wDir]; root: ViewerClasses.Viewer _ rep.viewer; IF root # NIL THEN { root.name _ name; ViewerOps.PaintViewer[viewer: root, hint: caption, clearClient: FALSE]; FOR v: ViewerClasses.Viewer _ root.link, v.link WHILE v # NIL AND v # root DO v.name _ name; ViewerOps.PaintViewer[viewer: v, hint: caption, clearClient: FALSE]; ENDLOOP; }; }; ENDCASE; }; SkipFunny: PROC [fullFName: ROPE] RETURNS [BOOL] = { lastPoint, bangIndex: INT _ 0; FOR i: INT DECREASING IN [0 .. fullFName.Length[]) DO SELECT fullFName.Fetch[i] FROM '! => bangIndex _ i; '> => {lastPoint _ i; EXIT}; ENDCASE; ENDLOOP; RETURN[ bangIndex = lastPoint + 1 ]; -- skip [..]<..>!1 }; CountAngles: PROC [pattern: ROPE] RETURNS [count: INT _ 0] = { len: INT = Rope.Length[pattern]; pos: INT _ 0; WHILE pos < len DO pos _ Rope.SkipTo[pattern, pos+1, ">/"]; count _ count + 1; ENDLOOP; IF Rope.Match["/*", pattern] THEN count _ count - 1; }; AbortRequested: PROC [cmd: Commander.Handle] RETURNS [BOOL] = { Process.CheckForAbort[]; RETURN [FALSE]; }; Init: PROCEDURE = { docAdd: ROPE = "add pseudo-server translation\n\t-r: assume replicated files (avoid remote check)"; docPrint: ROPE = "print pseudo-server translations"; Commander.Register[ "///Commands/Copy", CopyAndRename, "Copy newFile _ oldFile, or Copy directory _ {pattern}*\n -c: force copy, -r: retain structure, -u: update only, -x: eXact level match", $Copy]; Commander.Register[ "///Commands/Del", DeleteFiles, "Delete {switch | pattern}*\n -x: eXact level match"]; Commander.Register[ "///Commands/Delete", DeleteFiles, "Delete {switch | pattern}*\n -x: eXact level match"]; Commander.Register[ "///Commands/Rename", CopyAndRename, "Rename newFile _ oldFile, or Rename directory _ {pattern}*\n -r: retain structure, -u: update only, -x: eXact level match"]; Commander.Register[ "///Commands/Type", TypeFile, "Type fileName (in executive window)"]; Commander.Register[ "///Commands/PrintFSCacheInfo", PrintFSCacheInfo, "PrintFSCacheInfo {volumeName}, Print FS cache info"]; Commander.Register[ "///Commands/SetKeep", SetKeep, "SetKeep keep {pattern}*"]; Commander.Register[ "///Commands/SetFreeboard", SetFSFreeboard, "SetFreeboard nPages -- set FS cache limit"]; Commander.Register[ "///Commands/PrintWorkingDirectory", PrintWorkingDirectory, "Print working directory"]; Commander.Register[ "///Commands/PWD", PrintWorkingDirectory, "Print working directory"]; Commander.Register[ "///Commands/ChangeWorkingDirectory", ChangeWorkingDirectory, "Change working directory", $CD]; Commander.Register[ "///Commands/CD", ChangeWorkingDirectory, "Change working directory", $CD]; Commander.Register[ "///Commands/CDR", ChangeWorkingDirectory, "Change working directory (root relative)", $CDR]; Commander.Register[ "///Commands/PushWorkingDirectory", ChangeWorkingDirectory, "Push working directory", $PD]; Commander.Register[ "///Commands/Push", ChangeWorkingDirectory, "Push working directory", $PD]; Commander.Register[ "///Commands/PushR", ChangeWorkingDirectory, "Push working directory (root relative)", $PDR]; Commander.Register[ "///Commands/PopWorkingDirectory", PopWD, "Pop working directory"]; Commander.Register[ "///Commands/Pop", PopWD, "Pop working directory"]; Commander.Register[ "///Commands/FlushCache", FSFlushCache, "FlushCache {pattern}* -- flush FS cache"]; Commander.Register[ "///Commands/ListBTree", FSListBTree, "ListBTree {pattern}* -- list FS local directory and cache"]; Commander.Register[ "///Commands/FSLoad", FSLoad, "Load {pattern}* -- retrieve files"]; Commander.Register[ "///Commands/Scavenge", FSScavenge, "Scavenge {volumeName} -- FS scavenge"]; Commander.Register[ "///Commands/CacheFindExcessVersions", FindExcessVersionsCommand, "lists excess versions of files to the command output.", $FindCached]; Commander.Register[ "///Commands/CacheKillExcessVersions", FindExcessVersionsCommand, "deletes excess versions of files.", $KillCached]; Commander.Register[ "///Commands/FindExcessVersions", FindExcessVersionsCommand, "lists excess versions of files to the command output.", $FindReal]; Commander.Register[ "///Commands/KillExcessVersions", FindExcessVersionsCommand, "deletes excess versions of files.", $KillReal]; Commander.Register[ "///Commands/PSAdd", PseudoServerAddCommand, docAdd]; Commander.Register[ "///Commands/PseudoServerAdd", PseudoServerAddCommand, docAdd]; Commander.Register[ "///Commands/PSPrint", PseudoServerPrintCommand, docPrint]; Commander.Register[ "///Commands/PseudoServerPrint", PseudoServerPrintCommand, docPrint]; }; Init[]; END. ΐFSFileCommandsImpl.mesa Copyright c 1984, 1985, 1986 by Xerox Corporation. All rights reserved. Russ Atkinson (RRA) September 23, 1985 7:20:31 pm PDT McCreight, January 30, 1986 5:37:08 pm PST [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [fullFName: ROPE] RETURNS [continue: BOOL] This is a switch, not a file name [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [fullFName: ROPE] RETURNS [continue: BOOL] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [fullFName: ROPE] RETURNS [continue: BOOL] We have a duplicate file name that presumably differs only in version number just list the name to the command output [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] [fullFName: ROPE] RETURNS [continue: BOOL] [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] There will be three forms of Copy: Copy newFile _ oldFile copy a single file to a new name Copy directory _ list-of-files and patterns copy a group of files to a new directory each with the same shortname as before If the directory _ is omitted, files will be copied to the current working directory Copy -c -- Copy, don't use Attach [cmd: REF CommandObject] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [ in, out, err: STREAM, commandLine, command: ROPE, propertyList: List.AList, procData: CommandProcHandle] This file does not need a copy, since it has the same create date as the destination file. We have been instructed to trust the create date. Process switches First find out whether there is a _ anywhere. If there is, it must be the second arg. If we get here, then for each of the filenames and patterns, copy the file to the destination directory. [fullFName: ROPE] RETURNS [continue: BOOL] Count the number of slashes after the first *. FSPseudoServer Commands [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] Really trying to delete this server [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] Utility routines Do a bit of checking! Now change the commander herald, if there is one This routine counts either angle brackets or slashes (not counting a slash in the first position, which does not correspond to a slash). The idea is that this gives a measure of the number of levels in a path name. The count is too high by 1 (we skipped the first char anyway), since the first two slashes indicate server delimiters rather than directory element delimiters. Initialization Κ%b˜codešœ™Kšœ Οmœ=™HK™5K™*K™šΟk ˜ Kšœ žœžœ ˜Kšœ žœ!˜0Kšœ žœ*˜;Kšœžœ˜#Kšœ žœ˜!Kšœ žœ¦˜΅Kšžœžœ—˜ŸKšœ žœ’˜’Kšœžœl˜Kšžœžœožœžœ˜‘Kšœžœ ˜Kšœžœ˜Kšœ žœ˜!Kšœžœ ˜KšœžœKžœ˜vKšœžœ ˜Kšœ žœ˜——headšœžœž˜!Kšžœ:žœžœ.˜’Kšœž˜K˜Kšžœžœžœ˜Kšžœžœžœžœ˜—K˜š Οn œžœ žœ žœžœ˜9šžœ ž˜Kšœžœ˜!šžœ˜ šžœ˜Kšžœžœ˜!Kšžœžœ6˜A———Kšœ˜K˜—š Ÿ œžœ žœ žœžœ˜:šžœ ž˜Kšœžœ˜šžœ˜ šžœ˜Kšžœžœ˜Kšžœžœ6˜A———Kšœ˜K˜—šŸ œžœ˜K˜—šŸ œ˜&Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœžœ ˜Kšœžœžœ˜Kšœžœ˜šœ žœ ˜Kšœ žœžœ žœ™*Kšœ˜Kšœ žœ˜Kšžœžœ)žœžœ˜KKšžœ˜Kšžœ˜šžœ˜šœžœ ˜ Kšžœžœžœ#žœ˜I—Kšœ˜—Kšžœ˜Kšœ˜—šœ8˜8Kšœ)žœžœ˜5—šžœžœžœž˜Kšœ žœ ˜šžœžœ˜#Kšœ!™!Kšœžœžœ˜šžœžœžœž˜*Kšœžœ˜!šžœž˜ Kšœžœžœ˜ K˜"Kšžœ˜—Kšœžœ˜ Kšžœ˜—Kšžœ˜K˜—š œžœžœ žœžœžœžœ˜TKšœ1˜1Kšœ žœ˜+Kšžœžœžœ˜"Kšžœžœ'˜>Kšžœžœ'˜Kšžœ˜Kšžœ˜Kšžœžœ&˜-—K˜—Kšœ˜Kšžœ˜—šž˜Kšœ˜—K˜K˜—šŸœ˜#Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kš œžœžœžœžœ˜!Kšœ žœ˜šœ:ž˜>Kšœ)žœžœ˜5—Kšžœžœžœ˜!šžœžœžœž˜šœ žœžœ2˜IKš œžœ žœžœžœžœ˜O—šžœžœžœž˜!šœ žœ%˜4KšžœΟrž ˜Kšžœ  ž ˜Kšœ˜—šžœ)˜+Kšžœ ž ˜Kšžœ  ž ˜Kšœ˜—Kšœ˜Kšžœ˜—Kšžœžœžœžœ˜.Kšžœ˜—šž˜Kšœ˜Kšœ žœ-˜<—K˜K˜—šŸœ˜+Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœ žœžœ˜Kšœžœ˜šœ:ž˜>Kšœ)žœžœ˜5—Kšžœžœ0žœžœ˜PKšžœžœ˜(šœ8˜8Kš œžœ žœžœžœ˜N—šœžœ2˜:Kšœ9˜9—šž˜Kšœ˜—K˜K˜—šŸœ˜"Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœ žœ˜Kšœžœ˜ š œ žœžœžœžœ˜+š žœžœž œžœž˜1Kšžœžœžœ˜)Kšžœ˜—Kšžœ˜K˜—šœžœ ˜Kšœ žœžœ žœ™*Kšœ<˜Kšœ)žœžœ˜5—Kšžœžœžœ˜!Kšœ;žœ˜Gšœ!˜!Kš œžœ žœžœžœ˜O—šžœ˜Kšœ žœ)˜8Kšœ˜—K˜K˜—šŸœ˜0Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšžœžœ'˜1K˜K˜—šŸœ˜1Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœžœ˜ Kšœžœžœ˜Kšœžœžœ˜šœ:ž˜>Kšœ)žœžœ˜6—Kš œžœžœžœžœ3˜Nšžœž˜#Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœžœžœ,˜Dšžœ ž˜Kšœ žœžœžœ˜>šœ˜Kšœ˜šžœžœžœ˜-Kšœžœ$žœžœ˜4Kšœ ˜ —K˜—Kšžœ:žœžœ˜M—Kšœ!˜!šžœžœž˜"KšœQ˜Q—Kšžœ˜ K˜K˜—šŸœ˜ Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kš œžœžœžœžœ3˜NKš žœžœžœžœžœ'˜DKšœ'˜'Kšœ˜K˜bK˜K˜—šŸœ˜4Kš œžœ žœžœžœžœ™:šœ™Kšœžœžœ™1Kšœ6™6K™—šœ žœžœ˜š œžœžœžœžœ˜!Kšœžœ˜ Kšœž˜ Kšœ˜—Kšœ˜—Kšœ žœ žœ˜Kšœžœ ˜Kšœ žœžœ˜KšœžœΟc2˜CJ˜šœžœ ˜Kšœ žœžœ žœ™*Kšœžœ˜#Kšœžœ˜Kšœ žœ˜šžœ ž˜Kšžœ+žœžœ˜EKšžœ˜—šžœžœ2žœ žœ˜jK™Lšžœžœž˜šžœ ˜ 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š žœžœžœžœ$žœ˜OKšœ ˜ šžœ ž˜šœ?˜?šœžœ ˜ Kšžœžœžœ#žœ˜J———šž˜šžœ0˜2šœžœ žœžœ˜)Kšžœ&˜(Kšžœ˜ Kšœ˜———Kšžœ˜K˜K˜—šœ8˜8Kšœ)žœžœ ˜7—Kšœ žœ˜Kšœ žœžœ˜šžœžœž˜(šœžœ˜ šžœž˜Kšœžœžœ˜/Kšœžœžœ˜.Kšœžœžœ˜0Kšœžœžœ˜/Kšžœžœ˜——Kšžœ˜K˜—šžœ žœžœž˜/Kšœžœ˜šžœžœžœ˜:šžœžœžœž˜&šžœž˜šœ‘ œ˜Kšœžœ˜šžœžœ%žœ˜1Kšœ,žœžœ˜9—šœ:˜:Kšœ0žœžœ ˜>—šžœžœžœ žœ˜Kšœ;žœžœ˜H—Kšœ ˜ Kšœ˜—Kšžœžœ˜—Kšžœ‘˜ —K˜—Kšžœžœ˜)K˜Kšžœ˜K˜—Kšžœžœ žœ˜&šž˜Kšœ˜—K˜K˜—šŸ œ˜'Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœžœ ˜Kšœ žœ˜šœ ˜ Kšœ-˜-šœžœ ˜*šžœžœ˜Kšœ˜Kšžœ˜ K˜—Kšžœ˜"Kšœ˜—Kšœ˜Kšžœžœ˜ Kšœ˜—šœ8˜8Kšœ)žœžœ˜5—šžœžœžœž˜Kšœ1˜1š œžœžœ žœžœžœžœ˜Tšžœ˜Kšžœ˜Kšžœ,žœ ˜>—K˜—Kšžœžœžœ˜!Kšžœ˜—šž˜Kšœ˜—K˜K˜—šŸ œ˜&Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœžœ ˜Kšœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœ žœžœ˜Kš œ žœžœžœžœ˜$Kšœ žœžœžœ ˜%šœžœžœžœ˜%Kšžœžœ˜*Kšžœ˜"K˜—šœžœž˜Kš œžœžœžœž œ˜Ošœžœ˜"KšœC˜C—Kšžœ˜šžœ žœž˜šœ žœ žœ˜Kšžœ(˜*Kšœ žœ˜K˜—šœ žœžœ˜#Kšœžœ4˜@Kšžœ@˜BKšœ žœ˜K˜—šœ žœ žœ˜Kšžœ+˜-Kšœ8˜8Kšžœ˜Kšœ žœ˜K˜—Kšžœžœ˜—Kšœ˜—šœ8˜8Kšœ)žœžœ˜5—šžœ˜šž˜šœ˜Kšœ žœ$žœžœ5˜}——šž˜šžœžœžœž˜šžœžœ˜šžœžœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœžœ˜Kšœ žœ˜K˜—šžœžœžœž˜&šžœž˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšœ˜Kšžœ˜K˜—Kšœ?˜?šœ˜Kšœ žœ'žœžœ6˜—Kšžœžœžœ˜!Kšžœ˜———šž˜Kšœ˜—K˜K˜—šŸœ˜!Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœžœ ˜šœžœ ˜Kšœ žœžœ žœ™*Kšœžœ žœ˜$Kšœ˜Kšœ˜˜šœžœ˜šœžœ ˜ Kš žœžœžœ#žœžœ˜J—Kšœ˜—Kšžœ ˜Kšžœ ˜—Kšœ˜Kšžœžœ˜ Kšœ˜—šœ8˜8Kšœ)žœžœ˜5—šžœžœžœž˜Kšœ žœ*˜7Kšžœžœ&˜Dšžœ!˜#Kš œžœ žœžœžœžœ˜P—Kšžœžœžœ˜!Kšžœ˜—šž˜Kšœ˜—Kšœ˜K˜—šŸ œ˜%Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœ žœžœ˜šœ:ž˜>Kšœ)žœžœ˜5—Kšžœžœ˜(šœ,˜,Kšœžœ$žœžœ˜4—šž˜Kšœ˜—K˜K™—™"™K™ —™+K™OK™U—Kšœ ‘™"K˜—šŸ œ˜(Kš œžœžœ žœžœžœžœ™Ešœ™Kšœžœžœ™1Kšœ6™6—Kšœ žœžœ˜Kšœžœžœ˜!Kšœžœ˜Kšœ žœ#˜0Kšœ žœ$˜1Kšœžœ˜Kšœžœ˜Kšœžœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœžœ˜K˜šŸ œžœ žœ˜&Kšœ˜Kšœ4˜4šœž˜šžœ ˜ Kšžœžœžœžœ ˜C—šžœ žœ˜Kšœžœ˜.Kšœžœ˜,Kšœ žœ˜'Kš œ žœžœ žœžœžœ ˜Tšžœžœ žœ˜BKšœ™Kšœ9˜9Kšžœžœ˜ K˜—K˜—šžœ˜ Kšžœžœ4žœ ˜NKšžœžœ˜#—šž˜K˜ —K˜—Kšœ˜K˜—K˜šœ8˜8Kšœ)žœžœ˜5—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™VK˜Kšœžœ˜:šžœžœžœ ž˜KšœQ˜QKšžœ˜—K˜šžœ˜šžœ˜šžœ ˜"Kšžœ˜#šžœ˜šžœ žœžœ˜NKšžœžœ,˜7—Kšœ(˜(Kš žœžœžœžœ žœžœ˜1K˜——K˜—šžœ˜KšœD˜D——K˜K™hš žœžœžœžœžœžœž˜>šžœ!žœ˜)Kšœ8˜8Kšžœžœ˜ K˜—šžœ˜ šžœ˜Kšœ žœ ˜šœ žœ ˜Kšœ žœžœ žœ™*Kšœžœ˜ Kšœžœžœ˜Kšœ žœ˜Kšžœžœžœ˜$Kšžœžœ)žœžœ˜KKšœ6˜6šžœ˜KšžœI˜MKšžœ(˜,—Kšœ+˜+Kšœ%˜%Kšžœžœ˜K˜—Kšžœžœ&˜Dšžœž˜KšœD˜D—Kšžœžœ'˜>šœ.˜.Kšœ6˜6—šœ-˜-K™.—šžœ$˜&Kš œžœ žœžœžœžœ˜O—Kšœ˜—šžœ˜KšœžœF˜NKšœ#˜#Kš žœžœžœžœžœ˜K˜——Kšœ˜Kšžœ˜—šž˜Kšœ˜—K˜K˜—™K˜šœ1˜1š œžœ žœžœžœžœ™:Kšœžœžœ™G—K˜šœ8˜8Kšœ)žœžœ ˜8—Kšœ)žœ˜-Kšœžœžœ˜K˜šžœžœ˜Kšœ5˜5Kšžœžœ ˜K˜—šžœžœ˜Kšœ#™#Kšœ˜šžœ.ž˜3Kšžœžœ@˜MKšžœžœ;˜H—Kšžœ˜—K˜KšœD˜DK˜Kš žœžœžœžœžœ˜Kš žœžœžœžœžœ ˜3K˜Kšœ˜šžœ"ž˜'Kšžœžœ>˜KKšžœžœ=˜J—Kšœ)˜)K˜šž˜Kšœ˜—K˜K˜—šœ3˜3š œžœ žœžœžœžœ™:Kšœžœžœ™G—KšœR˜RKšœžœ ˜šŸœžœžœ˜Kšžœ˜šžœ˜Kšžœžœ˜Kšžœžœ˜—K˜—šžœ@žœžœž˜VKšžœžœžœžœ˜%Kšžœ˜Kšœ˜Kšžœžœ˜-Kšœ˜š žœ žœžœžœ!žœ žœž˜PKšœ˜Kšžœ˜—Kšžœ˜Kšžœ˜—K˜K˜——šœ™K™šŸœžœžœžœ žœžœžœžœžœ˜dKšœK˜KK™Kšžœžœžœ&˜Fšžœž˜Kšœ>˜>—Kšžœ$žœ˜IKšžœžœžœžœ˜&Kšœ ˜ KšœY˜YK™0šžœ0žœž˜?˜Kšœžœ+˜5Kšœ(˜(šžœžœžœ˜Kšœ˜Kšœ@žœ˜Gš žœ-žœžœžœ ž˜MK˜Kšœ=žœ˜DKšžœ˜—K˜—K˜—Kšžœ˜—K˜K˜—š Ÿ œžœ žœžœžœ˜4Kšœžœ˜š žœžœž œžœž˜5šžœž˜Kšœ˜Kšœžœ˜Kšžœ˜—Kšžœ˜—Kšžœžœ‘˜7K˜K˜—š Ÿ œžœ žœžœ žœ ˜>KšœΧ™ΧKšœžœ˜ Kšœžœ˜ šžœ ž˜Kšœ(˜(Kšœ˜Kšžœ˜—šžœž˜!KšœŸ™ŸKšœ˜—K˜K˜—šŸœžœžœžœ˜?Kšœ˜Kšžœžœ˜K˜—K™—™K˜šŸœž œ˜KšœžœW˜cKšœ žœ&˜4K˜šœ˜Kšœ"˜"Kšœ‘˜‘—šœ˜Kšœ˜Kšœ7˜7—šœ˜Kšœ"˜"Kšœ7˜7—šœ˜Kšœ$˜$Kšœ~˜~—šœ˜Kšœ˜Kšœ'˜'—šœ˜Kšœ1˜1Kšœ6˜6—šœ˜Kšœ˜Kšœ˜—šœ˜Kšœ+˜+Kšœ-˜-—šœ˜Kšœ;˜;Kšœ˜—šœ˜Kšœ)˜)Kšœ˜—šœ˜Kšœ=˜=Kšœ!˜!—šœ˜Kšœ)˜)Kšœ!˜!—šœ˜Kšœ*˜*Kšœ2˜2—šœ˜Kšœ;˜;Kšœ˜—šœ˜Kšœ+˜+Kšœ˜—šœ˜Kšœ,˜,Kšœ0˜0—šœ˜Kšœ)˜)Kšœ˜—šœ˜Kšœ˜Kšœ˜—šœ˜Kšœ'˜'Kšœ+˜+—šœ˜Kšœ%˜%Kšœ=˜=—šœ˜KšœC˜C—šœ˜Kšœ$˜$Kšœ(˜(—šœ˜KšœA˜AKšœ8˜8Kšœ ˜ —šœ˜KšœA˜AKšœ$˜$Kšœ ˜ —šœ˜Kšœ<˜