DIRECTORY Atom, Basics, BasicTime, Commander, CommandExtras, CommandTool, Convert, DFUtilities, FileCmds, FS, IO, OrderedSymbolTableRef, ProcessExtras, Rope, Tempus, TiogaOps, ViewerClasses, ViewerTools, WalnutExtras; FileCmdsImpl: CEDAR PROGRAM IMPORTS Atom, Commander, CommandExtras, CommandTool, Convert, DFUtilities, FileCmds, FS, IO, OrderedSymbolTableRef, ProcessExtras, Rope, Tempus, TiogaOps, WalnutExtras EXPORTS FileCmds ={OPEN FileCmds; Command: TYPE = REF CommandRep; CommandRep: TYPE = RECORD [ cmdData: REF ANY, Start: PROC [instance: Instance] _ NIL, HandleSwitch: PROC [instance: Instance, switch: ROPE] RETURNS [handled: BOOL] _ NIL, HandleArg: PROC [instance: Instance] RETURNS [handled: BOOL] _ NIL, AfterParse: PROC [instance: Instance], PerFile: PROC [instance: Instance, fn: FileNote], Finish: PROC [instance: Instance] RETURNS [result: REF ANY, msg: ROPE] _ NIL, usage: ROPE ]; Instance: TYPE = REF InstanceRep; InstanceRep: TYPE = RECORD [ cmd: Commander.Handle, of: Command, cls: STREAM, argCount, misses, subFailures: INT _ 0, setAsAny: REF ANY _ NIL, set: FileSet _ NIL, ids: IdentificationScheme _ longWithVersion, touchy: BOOL _ TRUE, createdNeeded: BOOL _ FALSE, data: REF ANY _ NIL]; Error: PUBLIC ERROR [message: ROPE] = CODE; Miss: PUBLIC SIGNAL [name: ROPE, created: GMT] = CODE; GetCreated: PUBLIC PROC [fn: FileNote] RETURNS [created: GMT] = { IF (created _ fn.created) # noGMT THEN RETURN; IF NOT fn.createTried THEN { ok: BOOL _ TRUE; ct: GMT _ noGMT; fn.createTried _ TRUE; [created: ct] _ FS.FileInfo[name: fn.name, remoteCheck: FALSE !FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN fn.created _ created _ ct}; }; Size: PUBLIC PROC [fs: FileSet] RETURNS [numberOfFiles: INT] = {numberOfFiles _ fs.elts.Size[]}; Lookup: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [found: FileNote] = {found _ NARROW[fs.elts.Lookup[fn]]}; Delete: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [found: FileNote] = {found _ NARROW[fs.elts.Delete[fn]]}; Generic: PROC [cmd: Commander.Handle] RETURNS [result: REF ANY _ NIL, msg: ROPE _ NIL] --Commander.CommandProc-- = { command: Command _ NARROW[cmd.procData.clientData]; cls: STREAM _ IO.RIS[cmd.commandLine]; instance: Instance _ NEW [InstanceRep _ [cmd, command, cls]]; DoIt: PROC [fn: FileNote] --FileConsumer-- = { created: GMT _ GetCreated[fn]; IF instance.createdNeeded AND created = noGMT THEN { cmd.err.PutF["Couldn't get a create time for %g\n", IO.rope[fn.name]]; instance.subFailures _ instance.subFailures + 1; } ELSE { command.PerFile[instance, fn]; }; }; IF command.Start # NIL THEN command.Start[instance]; FOR i: INT _ cls.SkipWhitespace[], cls.SkipWhitespace[] WHILE NOT cls.EndOf[] DO IF cls.PeekChar = '- THEN { switch: ROPE _ cls.GetTokenRope[MyBreak].token; IF switch.Equal["-longWithVersion", FALSE] THEN instance.ids _ longWithVersion ELSE IF switch.Equal["-shortAndCreate", FALSE] THEN instance.ids _ shortAndCreate ELSE IF switch.Equal["-askFS", FALSE] THEN instance.ids _ askFS ELSE IF switch.Equal["-touchy", FALSE] THEN instance.touchy _ TRUE ELSE IF switch.Equal["-tough", FALSE] THEN instance.touchy _ FALSE ELSE IF command.HandleSwitch = NIL OR NOT command.HandleSwitch[instance, switch] THEN GOTO CmdSyntaxErr; } ELSE { ENABLE IO.Error => IF ec = SyntaxError THEN { result _ $Failure; msg _ IO.PutFR["Syntax error while parsing %g'th argument", IO.int[instance.argCount + 1]]; GOTO GiveUp --because the turkey language won't let us simply RETURN from catch phrases }; SELECT TRUE FROM command.HandleArg = NIL => { SELECT instance.argCount FROM 0 => instance.setAsAny _ instance.cls.GetRefAny[]; ENDCASE => GOTO CmdSyntaxErr; }; command.HandleArg # NIL => { IF NOT command.HandleArg[instance] THEN GOTO CmdSyntaxErr; }; ENDCASE => ERROR; instance.argCount _ instance.argCount + 1; }; REPEAT CmdSyntaxErr => RETURN [$Failure, command.usage]; ENDLOOP; IF instance.setAsAny = NIL THEN RETURN [$Failure, command.usage]; {ENABLE { Miss => { cmd.err.PutF["Failed to identify %g", IO.rope[name]]; IF created # noGMT THEN cmd.err.PutF[" of %g", IO.time[created]]; cmd.err.PutRope["\n"]; instance.misses _ instance.misses + 1; RESUME; }; Error => { result _ $Failure; msg _ message; GOTO GiveUp; }; }; IF command.AfterParse # NIL THEN command.AfterParse[instance]; instance.set _ Eval[instance.setAsAny, instance.ids]; IF instance.misses = 0 OR NOT instance.touchy THEN EnumSet[instance.set, DoIt]; }; IF instance.subFailures > 0 OR (instance.touchy AND instance.misses > 0) THEN RETURN [$Failure, IO.PutFR["%g files missed and %g command(s) failed", IO.int[instance.misses], IO.int[instance.subFailures]]]; IF command.Finish # NIL THEN [result, msg] _ command.Finish[instance]; cmd.out.PutF["total %g\n", IO.rope[instance.set.summary]]; EXITS GiveUp => RETURN; }; CmdPerFile: TYPE = REF CmdPerFileRep; CmdPerFileRep: TYPE = RECORD [ pattern: ROPE _ NIL]; StartCmdPerFile: PROC [instance: Instance] = { cpf: CmdPerFile _ NEW [CmdPerFileRep _ []]; instance.data _ cpf}; HandleCmdPerFileArg: PROC [instance: Instance] RETURNS [handled: BOOL] = { cpf: CmdPerFile _ NARROW[instance.data]; SELECT instance.argCount FROM 0 => cpf.pattern _ instance.cls.GetRopeLiteral[]; 1 => instance.setAsAny _ instance.cls.GetRefAny[]; ENDCASE => RETURN [FALSE]; handled _ TRUE}; AfterCmdPerFileParse: PROC [instance: Instance] = { cpf: CmdPerFile _ NARROW[instance.data]; instance.createdNeeded _ cpf.pattern.Find[""] > 0; }; PerCmdPerFileFile: PROC [instance: Instance, fn: FileNote] = { cpf: CmdPerFile _ NARROW[instance.data]; createdR: ROPE _ IF instance.createdNeeded THEN Convert.RopeFromTime[fn.created] ELSE "??"; commandLine: ROPE _ Replace[Replace[Replace[Replace[cpf.pattern, "", fn.name], "", createdR], "", fn.primaryVolume], "", fn.backupVolume]; thisResult: REF ANY; thisResult _ CommandTool.DoCommand[commandLine: commandLine, parent: instance.cmd]; IF thisResult = $Failure THEN instance.subFailures _ instance.subFailures + 1; }; cmdPerFile: Command _ NEW [CommandRep _ [ cmdData: NIL, Start: StartCmdPerFile, HandleArg: HandleCmdPerFileArg, AfterParse: AfterCmdPerFileParse, PerFile: PerCmdPerFileFile, usage: "Usage: CmdPerFile {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough}* || {\"command pattern\" fileSetExpression}"]]; CmdAllFiles: TYPE = REF CmdAllFilesRep; CmdAllFilesRep: TYPE = RECORD [ prefix, perFile, infix, postfix, commandLine: ROPE _ NIL, first: BOOL _ TRUE]; StartCmdAllFiles: PROC [instance: Instance] = { caf: CmdAllFiles _ NEW [CmdAllFilesRep _ []]; instance.data _ caf}; HandleCmdAllFilesArg: PROC [instance: Instance] RETURNS [handled: BOOL] = { caf: CmdAllFiles _ NARROW[instance.data]; SELECT instance.argCount FROM 0 => caf.prefix _ instance.cls.GetRopeLiteral[]; 1 => caf.perFile _ instance.cls.GetRopeLiteral[]; 2 => caf.infix _ instance.cls.GetRopeLiteral[]; 3 => caf.postfix _ instance.cls.GetRopeLiteral[]; 4 => instance.setAsAny _ instance.cls.GetRefAny[]; ENDCASE => RETURN [FALSE]; handled _ TRUE}; AfterCmdAllFilesParse: PROC [instance: Instance] = { caf: CmdAllFiles _ NARROW[instance.data]; instance.createdNeeded _ caf.perFile.Find[""] > 0; }; PerCmdAllFilesFile: PROC [instance: Instance, fn: FileNote] = { caf: CmdAllFiles _ NARROW[instance.data]; createdR: ROPE _ IF instance.createdNeeded THEN Convert.RopeFromTime[fn.created] ELSE "??"; perThis: ROPE _ Replace[Replace[Replace[Replace[caf.perFile, "", fn.name], "", createdR], "", fn.primaryVolume], "", fn.backupVolume]; caf.commandLine _ caf.commandLine.Cat[IF caf.first THEN caf.prefix ELSE caf.infix, perThis]; caf.first _ FALSE; }; FinishCmdAllFiles: PROC [instance: Instance] RETURNS [result: REF ANY, msg: ROPE] = { caf: CmdAllFiles _ NARROW[instance.data]; caf.commandLine _ caf.commandLine.Cat[caf.postfix]; result _ CommandTool.DoCommand[commandLine: caf.commandLine, parent: instance.cmd]; }; cmdAllFiles: Command _ NEW [CommandRep _ [ Start: StartCmdAllFiles, HandleArg: HandleCmdAllFilesArg, AfterParse: AfterCmdAllFilesParse, PerFile: PerCmdAllFilesFile, Finish: FinishCmdAllFiles, usage: "Usage: CmdAllFiles {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough}* || {\"command prefix\" \"pattern per file\" \"pattern between files\" \"command postfix\" fileSetExpression}"]]; ListFileSet: TYPE = REF ListFileSetRep; ListFileSetRep: TYPE = RECORD [ long: BOOL _ FALSE]; StartListFileSet: PROC [instance: Instance] = { lfs: ListFileSet _ NEW [ListFileSetRep _ []]; instance.data _ lfs}; HandleListFileSetSwitch: PROC [instance: Instance, switch: ROPE] RETURNS [handled: BOOL] = { lfs: ListFileSet _ NARROW[instance.data]; IF switch.Equal["-l", FALSE] THEN lfs.long _ TRUE ELSE IF switch.Equal["-s", FALSE] THEN lfs.long _ FALSE ELSE RETURN [FALSE]; handled _ TRUE}; AfterListFileSetParse: PROC [instance: Instance] = { lfs: ListFileSet _ NARROW[instance.data]; instance.createdNeeded _ lfs.long}; PerListFileSetFile: PROC [instance: Instance, fn: FileNote] = { lfs: ListFileSet _ NARROW[instance.data]; instance.cmd.out.PutRope[fn.name]; IF lfs.long THEN instance.cmd.out.PutF[" %g", IO.time[fn.created]]; instance.cmd.out.PutRope["\n"]}; listFileSet: Command _ NEW [CommandRep _ [ Start: StartListFileSet, HandleSwitch: HandleListFileSetSwitch, AfterParse: AfterListFileSetParse, PerFile: PerListFileSetFile, usage: "Usage: ListFileSet {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough|-l|-s}* || {fileSetExpression}"]]; AfterDeleteParse: PROC [instance: Instance] = { instance.createdNeeded _ SELECT instance.ids FROM longWithVersion, askFS => FALSE, shortAndCreate => TRUE, ENDCASE => ERROR}; PerDeleteFile: PROC [instance: Instance, fn: FileNote] = { ok: BOOL _ TRUE; instance.cmd.out.PutRope[fn.name]; SELECT instance.ids FROM longWithVersion, askFS => NULL; shortAndCreate => instance.cmd.out.PutF[" of %g", IO.time[fn.created]]; ENDCASE => ERROR; instance.cmd.out.PutRope[" ... "]; FS.Delete[ name: fn.name, wantedCreatedTime: SELECT instance.ids FROM longWithVersion, askFS => noGMT, shortAndCreate => fn.created, ENDCASE => ERROR !FS.Error => { instance.cmd.out.PutF[" Error[%g, %g]\n", IO.atom[error.code], IO.rope[error.explanation]]; ok _ FALSE; instance.subFailures _ instance.subFailures + 1; CONTINUE; }]; IF ok THEN instance.cmd.out.PutRope[" ok\n"]; }; deleteFileSet: Command _ NEW [CommandRep _ [ AfterParse: AfterDeleteParse, PerFile: PerDeleteFile, usage: "Usage: DeleteFileSet {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough}* || {fileSetExpression}"]]; AfterArchiveRequestParse: PROC [instance: Instance] = { instance.createdNeeded _ FALSE}; PerArchiveRequestFile: PROC [instance: Instance, fn: FileNote] = { instance.cmd.out.PutF["Archive: %g\n", IO.rope[fn.name]]}; archiveFileSet: Command _ NEW [CommandRep _ [ AfterParse: AfterArchiveRequestParse, PerFile: PerArchiveRequestFile, usage: "Usage: ArchiveFileSet {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough}* || {fileSetExpression}"]]; AfterRetrieveRequestParse: PROC [instance: Instance] = { instance.createdNeeded _ TRUE}; PerRetrieveRequestFile: PROC [instance: Instance, fn: FileNote] = { instance.cmd.out.PutF["Retrieve: %g of %g from %g or %g\n", IO.rope[fn.name], IO.time[fn.created], IO.rope[fn.primaryVolume], IO.rope[fn.backupVolume]]}; retrieveFileSet: Command _ NEW [CommandRep _ [ AfterParse: AfterRetrieveRequestParse, PerFile: PerRetrieveRequestFile, usage: "Usage: RetrieveFileSet {-longWithVersion|-shortAndCreate|-askFS|-touchy|-tough}* || {fileSetExpression}"]]; Replace: PROC [start, match, replace: ROPE] RETURNS [finish: ROPE] = { ml: INT _ match.Length[]; rl: INT _ replace.Length[]; len: INT _ (finish _ start).Length[]; loc: INT _ finish.Index[0, match]; WHILE loc < len DO finish _ finish.Substr[len: loc].Cat[replace, finish.Substr[start: loc+ml]]; len _ finish.Length[]; loc _ finish.Index[loc + rl, match]; ENDLOOP; }; Eval: PUBLIC PROC [ra: REF ANY, ids: IdentificationScheme] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = { prevFN: FileNote _ fs.Lookup[fn]; SELECT TRUE FROM prevFN = NIL => { fs.elts.Insert[fn]; }; prevFN # NIL => IF GetCreated[fn] # GetCreated[prevFN] THEN ERROR Error[IO.PutFR["%g found with two create dates", IO.rope[fn.name]]]; ENDCASE => ERROR; }; Subtract: PROC [fn: FileNote] --FileConsumer-- = { [] _ fs.Delete[fn]; }; NoteFile: PROC [fullFName, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOLEAN] --FS.InfoProc-- = { fn: FileNote _ Map[fullFName, created, ids]; IF fn # NIL THEN Add[fn]; continue _ TRUE; }; fs _ NEW [FileSetRep _ [ elts: OrderedSymbolTableRef.CreateTable[SELECT ids FROM longWithVersion => CompareLongs, shortAndCreate => CompareShorts, askFS => CompareLongs, ENDCASE => ERROR], summary: NIL]]; WITH ra SELECT FROM l: LORA => { WITH l.first SELECT FROM a: ATOM => SELECT a FROM $df, $source, $derived, $sourceAndDerived, $public, $private, $publicAndPrivate, $defining, $imported, $definingAndImported, $remote, $local => { filter: DFUtilities.Filter _ [filterC: defining]; dfw: DFWhich _ remote; dfNames: LIST OF ROPE _ NIL; FOR dl: LORA _ l, dl.rest WHILE dl # NIL DO WITH dl.first SELECT FROM a: ATOM => SELECT a FROM $df => NULL; $source => filter.filterA _ source; $derived => filter.filterA _ derived; $sourceAndDerived => filter.filterA _ all; $public => filter.filterB _ public; $private => filter.filterB _ private; $publicAndPrivate => filter.filterB _ all; $defining => filter.filterC _ defining; $imported => filter.filterC _ imported; $definingAndImported => filter.filterC _ all; $remote => dfw _ remote; $local => dfw _ local; ENDCASE => ERROR Error[IO.PutFR["Non-understood DF atom %g", IO.atom[a]]]; sl: LORA => { numOnlys, i: INT _ 0; IF sl.first # $using THEN ERROR Error[IO.PutFR["DF using list %g must begin with atom $using", IO.refAny[sl]]]; IF filter.list # NIL THEN ERROR Error[IO.PutFR["DF list %g contains more than one using list", IO.refAny[l]]]; FOR nl: LORA _ sl.rest, nl.rest WHILE nl # NIL DO numOnlys _ numOnlys + 1; ENDLOOP; filter.list _ NEW [DFUtilities.UsingList[numOnlys]]; FOR nl: LORA _ sl.rest, nl.rest WHILE nl # NIL DO filter.list[i] _ [FALSE, NARROW[nl.first]]; i _ i + 1; ENDLOOP; IF (filter.list.nEntries _ i) # numOnlys THEN ERROR; }; r: ROPE => { Consume: PROC [fullFName: ROPE] RETURNS [continue: BOOLEAN] --FS.NameProc-- = { dfNames _ CONS[fullFName, dfNames]; continue _ TRUE; }; pattern: ROPE _ IF r.Find["!"] < 0 THEN r.Cat["!H"] ELSE r; FS.EnumerateForNames[pattern, Consume]; }; ENDCASE => ERROR Error[IO.PutFR["Non-understood DF thing %g", IO.refAny[dl.first]]]; ENDLOOP; FOR dfNames _ dfNames, dfNames.rest WHILE dfNames # NIL DO EnumDF[dfName: dfNames.first, to: Add, filter: filter, which: dfw, ids: ids]; ENDLOOP; fs.summary _ Convert.RopeFromInt[fs.Size[]]}; $archive, $online => { dateRope: ROPE _ NARROW[l.rest.first]; date: GMT _ Tempus.Parse[dateRope].time; EnumArchiveMsg[date, Add, SELECT a FROM $archive => archive, $online => online, ENDCASE => ERROR, ids]; fs.summary _ Convert.RopeFromInt[fs.Size[]]}; $union => { fs.summary _ "(union"; FOR rest: LORA _ l.rest, rest.rest WHILE rest # NIL DO sub: FileSet _ Eval[rest.first, ids]; fs.summary _ fs.summary.Cat[" ", Convert.RopeFromInt[sub.Size[]]]; EnumSet[sub, Add]; ENDLOOP; fs.summary _ Convert.RopeFromInt[fs.Size[]].Cat[":", fs.summary, ")"]; }; $diff => { first: BOOL _ TRUE; fs.summary _ "(diff"; FOR rest: LORA _ l.rest, rest.rest WHILE rest # NIL DO sub: FileSet _ Eval[rest.first, ids]; fs.summary _ fs.summary.Cat[" ", Convert.RopeFromInt[sub.Size[]]]; EnumSet[sub, IF first THEN Add ELSE Subtract]; first _ FALSE; ENDLOOP; fs.summary _ Convert.RopeFromInt[fs.Size[]].Cat[":", fs.summary, ")"]; }; $sdiff => {--symmetric difference first: BOOL _ TRUE; fs.summary _ "(sdiff"; FOR rest: LORA _ l.rest, rest.rest WHILE rest # NIL DO SubtractIntersection: PROC [fn: FileNote] --FileConsumer-- = { IF fs.Lookup[fn] = NIL THEN RETURN; [] _ fs.Delete[fn]; [] _ sub.Delete[fn]; }; sub: FileSet _ Eval[rest.first, ids]; fs.summary _ fs.summary.Cat[" ", Convert.RopeFromInt[sub.Size[]]]; EnumSet[sub, SubtractIntersection]; EnumSet[sub, Add]; first _ FALSE; ENDLOOP; fs.summary _ Convert.RopeFromInt[fs.Size[]].Cat[":", fs.summary, ")"]; }; $intersection => { first: BOOL _ TRUE; fs.summary _ "(intersection"; FOR rest: LORA _ l.rest, rest.rest WHILE rest # NIL DO sub: FileSet _ Eval[rest.first, ids]; Filter: PROC [fn: FileNote] = { IF sub.Lookup[fn] = NIL THEN [] _ fs.Delete[fn]}; fs.summary _ fs.summary.Cat[" ", Convert.RopeFromInt[sub.Size[]]]; IF first THEN first _ FALSE ELSE EnumSet[fs, Filter]; ENDLOOP; fs.summary _ Convert.RopeFromInt[fs.Size[]].Cat[":", fs.summary, ")"]; }; ENDCASE => ERROR Error[IO.PutFR["Can't process %g", IO.refAny[ra]]]; ENDCASE => ERROR Error[IO.PutFR["Can't process %g", IO.refAny[ra]]]; }; r: ROPE => { FS.EnumerateForInfo[pattern: r, proc: NoteFile]; fs.summary _ Convert.RopeFromInt[fs.Size[]]}; a: ATOM => { FS.EnumerateForInfo[pattern: Atom.GetPName[a], proc: NoteFile]; fs.summary _ Convert.RopeFromInt[fs.Size[]]}; ENDCASE => ERROR Error[IO.PutFR["Can't process %g", IO.refAny[ra]]]; }; EnumSet: PUBLIC PROC [fs: FileSet, to: FileConsumer] = { PerNote: PROC [ra: REF ANY] RETURNS [stop: BOOL] = { fn: FileNote _ NARROW[ra]; ProcessExtras.CheckForAbort[]; to[fn]; stop _ FALSE}; fs.elts.EnumerateIncreasing[PerNote]; fs _ fs}; FindByDate: PROC [date: GMT] RETURNS [v: Viewer] = { list: LIST OF Viewer _ WalnutExtras.EnumWalnutViewers[keepSeparate: TRUE].msgList; FOR list _ list, list.rest WHILE list # NIL DO { root: TiogaOps.Ref _ TiogaOps.ViewerDoc[list.first]; curNode: TiogaOps.Ref _ root; FOR curNode: TiogaOps.Ref _ TiogaOps.StepForward[root], TiogaOps.StepForward[curNode] WHILE curNode # NIL DO from: STREAM _ IO.RIS[TiogaOps.GetRope[curNode].Cat["\n"]]; FOR i: INT _ from.SkipWhitespace[], from.SkipWhitespace[] WHILE NOT from.EndOf[] DO keyword: ROPE _ from.GetTokenRope[MyBreak].token; ProcessExtras.CheckForAbort[]; IF keyword.Equal["Date:", FALSE] THEN { restOfLine: ROPE _ from.GetLineRope[]; thisDate: GMT _ Tempus.Parse[restOfLine].time; IF thisDate = date THEN RETURN [list.first] ELSE GOTO NotThisOne; }; IF keyword.Fetch[keyword.Length[]-1] # ': THEN GOTO NotThisOne; IF keyword.Equal["Archived:"] OR keyword.Equal["Archive:"] OR keyword.Equal["Retrieved:"] OR keyword.Equal["Retrieve:"] THEN GOTO NotThisOne; [] _ from.GetLineRope[]; ENDLOOP; ENDLOOP; ERROR Error[IO.PutFR["No date in Walnut message viewer named %g", IO.refAny[list.first.name]]]; EXITS NotThisOne => NULL; } ENDLOOP; ERROR Error[IO.PutFR["no message viewer on an archive message dated %g", IO.time[date]]]; }; Map: PROC [fileName: ROPE, created: GMT, ids: IdentificationScheme] RETURNS [fn: FileNote] = { createTried: BOOL _ FALSE; SELECT ids FROM askFS => { ok: BOOL _ TRUE; createTried _ TRUE; [fullFName: fileName, created: created] _ FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN { SIGNAL Miss[fileName, created]; RETURN [NIL]}; }; longWithVersion => { cp: FS.ComponentPositions; [fileName, cp] _ FS.ExpandName[fileName]; IF cp.ver.length # 0 THEN NULL ELSE { ok: BOOL _ TRUE; full: ROPE _ NIL; createTried _ TRUE; [fullFName: fileName, created: created] _ FS.FileInfo[name: fileName, wantedCreatedTime: created, remoteCheck: FALSE !FS.Error => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN { SIGNAL Miss[fileName, created]; RETURN [NIL]}; }; }; shortAndCreate => { full: ROPE _ NIL; cp: FS.ComponentPositions; [full, cp] _ FS.ExpandName[fileName]; fileName _ full.Substr[start: cp.base.start, len: cp.ext.start + cp.ext.length - cp.base.start]; IF created # noGMT THEN NULL ELSE { ok: BOOL _ TRUE; createTried _ TRUE; [created: created] _ FS.FileInfo[name: full !FS.Error => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN { SIGNAL Miss[fileName, created]; RETURN [NIL]}; }; }; ENDCASE => ERROR; fn _ NEW [FileNoteRep _ [name: fileName, created: created, createTried: createTried]]; }; EnumArchiveMsg: PUBLIC PROC [date: GMT, to: FileConsumer, which: ArchiveWhich, ids: IdentificationScheme] = { heading: BOOL _ TRUE; v: Viewer _ FindByDate[date]; root: TiogaOps.Ref _ TiogaOps.ViewerDoc[v]; curNode: TiogaOps.Ref _ root; FOR curNode: TiogaOps.Ref _ TiogaOps.StepForward[root], TiogaOps.StepForward[curNode] WHILE curNode # NIL DO from: STREAM _ IO.RIS[TiogaOps.GetRope[curNode].Cat["\n"]]; Skip: PROC [toke: ROPE] = { got: ROPE _ from.GetTokenRope[MyBreak].token; IF NOT got.Equal[toke] THEN ERROR Error[IO.PutFR["Got %g instead of %g in archive message of %g", IO.refAny[got], IO.refAny[toke], IO.time[date]]]; }; InsistEOL: PROC = { c: CHAR _ from.GetChar[]; IF c # '\n THEN ERROR Error[IO.PutFR["Got %g instead of newline in archive message of %g", IO.char[c], IO.time[date]]]; }; FOR i: INT _ from.SkipWhitespace[], from.SkipWhitespace[] WHILE NOT from.EndOf[] DO keyword: ROPE _ from.GetTokenRope[MyBreak].token; ProcessExtras.CheckForAbort[]; IF keyword.Equal["Archived:"] THEN { fileName, pv, bv: ROPE; gmt: GMT; fn: FileNote; heading _ FALSE; fileName _ from.GetTokenRope[MyBreak].token; Skip["of"]; gmt _ from.GetTime[]; Skip["on"]; pv _ from.GetTokenRope[MyBreak].token; Skip["or"]; bv _ from.GetTokenRope[MyBreak].token; InsistEOL[]; IF (fn _ Map[fileName: fileName, created: gmt, ids: ids]) # NIL THEN { fn.primaryVolume _ pv; fn.backupVolume _ bv; to[fn]; }; } ELSE IF keyword.Equal["Retrieved:"] THEN { fromName, toName: ROPE; gmt: GMT; fn: FileNote; heading _ FALSE; fromName _ from.GetTokenRope[MyBreak].token; Skip["of"]; gmt _ from.GetTime[]; Skip["as"]; toName _ from.GetTokenRope[MyBreak].token; InsistEOL[]; IF (fn _ Map[fileName: SELECT which FROM archive => fromName, online => toName, ENDCASE => ERROR, created: gmt, ids: ids]) # NIL THEN to[fn]; } ELSE IF keyword.Equal["Archive:"] THEN { fileName: ROPE; fn: FileNote; heading _ FALSE; fileName _ from.GetTokenRope[MyBreak].token; InsistEOL[]; IF HasWildcards[fileName] THEN { Consume: PROC [fullFName: ROPE] RETURNS [continue: BOOL] --FS.NameProc-- = { IF (fn _ Map[fullFName, noGMT, ids]) # NIL THEN to[fn]; continue _ TRUE}; FS.EnumerateForNames[fileName, Consume]; } ELSE { IF (fn _ Map[fileName, noGMT, ids]) # NIL THEN to[fn]; }; } ELSE IF keyword.Equal["Retrieve:"] THEN { fileName, pv, bv: ROPE; gmt: GMT; fn: FileNote; heading _ FALSE; fileName _ from.GetTokenRope[MyBreak].token; Skip["of"]; gmt _ from.GetTime[]; Skip["from"]; pv _ from.GetTokenRope[MyBreak].token; Skip["or"]; bv _ from.GetTokenRope[MyBreak].token; InsistEOL[]; IF (fn _ Map[fileName, gmt, ids]) # NIL THEN { fn.primaryVolume _ pv; fn.backupVolume _ bv; to[fn]; }; } ELSE { IF NOT heading THEN ERROR Error[IO.PutFR["Got non-archiving keyword %g among archiving keywords in archive message of %g", IO.rope[keyword], IO.time[date]]]; IF keyword.Length[] = 0 THEN ERROR; IF keyword.Fetch[keyword.Length[]-1] # ': THEN ERROR Error[IO.PutFR["Found non-keyword %g in archive message of %g", IO.refAny[keyword], IO.time[date]]]; [] _ from.GetLineRope[]; }; ENDLOOP; from.Close[]; ENDLOOP; }; HasWildcards: PROC [fileName: ROPE] RETURNS [hasem: BOOL] = {hasem _ fileName.Find["*"] >= 0}; MyBreak: PROC [char: CHAR] RETURNS [cc: IO.CharClass] --IO.BreakProc-- = { cc _ IF char IN ['\000 .. ' ] THEN sepr ELSE other; }; RefineTime: PROC [fileName: ROPE, date: DFUtilities.Date] RETURNS [gmt: GMT] = { SELECT date.format FROM explicit => RETURN [date.gmt]; omitted, greaterThan, notEqual => RETURN [GetGMT[fileName]]; ENDCASE => ERROR; }; GetGMT: PROC [fileName: ROPE] RETURNS [created: GMT] = { ok: BOOL _ TRUE; [created: created] _ FS.FileInfo[name: fileName, remoteCheck: FALSE !FS.Error => {ok _ FALSE; CONTINUE}]; IF NOT ok THEN created _ noGMT; }; EnumDF: PUBLIC PROC [dfName: ROPE, date: DFUtilities.Date _ [], to: FileConsumer, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme] = { dir: ROPE _ "?"; readOnly: BOOL _ FALSE; PerItem: PROC [item: REF ANY] RETURNS [stop: BOOL _ FALSE] = { ProcessExtras.CheckForAbort[]; WITH item SELECT FROM di: REF DFUtilities.DirectoryItem => {dir _ di.path1; readOnly _ di.readOnly}; fi: REF DFUtilities.FileItem => { useDir: ROPE _ SELECT which FROM remote => dir, local => NIL, ENDCASE => ERROR; fiName: ROPE; fiCP: FS.ComponentPositions; fn: FileNote; [fiName, fiCP, ] _ FS.ExpandName[fi.name, useDir]; IF (fn _ SELECT which FROM remote => Map[fiName, fi.date.gmt, ids], local => Map[DFUtilities.RemoveVersionNumber[fiName], noGMT, ids], ENDCASE => ERROR) # NIL THEN to[fn]; }; ii: REF DFUtilities.ImportsItem => { subFilter: DFUtilities.Filter _ [ comments: filter.comments, filterA: filter.filterA, filterB: IF ii.form = $exports THEN $public ELSE filter.filterB, filterC: $all, list: IF ii.form = $list THEN ii.list ELSE filter.list ]; EnumDF[ii.path1, ii.date, to, subFilter, which, ids]; }; ii: REF DFUtilities.IncludeItem => EnumDF[ii.path1, ii.date, to, filter, which, ids]; ci: REF DFUtilities.CommentItem => NULL; wi: REF DFUtilities.WhiteSpaceItem => NULL; ENDCASE => ERROR; }; file: FS.OpenFile _ FS.nullOpenFile; from: STREAM; file _ FS.Open[ name: dfName, wantedCreatedTime: SELECT date.format FROM explicit => date.gmt, omitted, greaterThan, notEqual => BasicTime.nullGMT, ENDCASE => ERROR]; IF file = FS.nullOpenFile THEN RETURN; from _ FS.StreamFromOpenFile[file]; DFUtilities.ParseFromStream[in: from, proc: PerItem, filter: filter !DFUtilities.SyntaxError => {ERROR Error[IO.PutFR["Syntax error near [%g] in DF-file %g: %g", IO.int[from.GetIndex[]], IO.rope[dfName], IO.rope[reason]]]}]; from.Close[]; }; CompareLongs: PROC [r1, r2: REF ANY] RETURNS [c: Basics.Comparison] -- OrderedSymbolTableRef.CompareProc-- = { fn1: FileNote _ NARROW[r1]; fn2: FileNote _ NARROW[r2]; c _ fn1.name.Compare[fn2.name, FALSE]}; CompareShorts: PROC [r1, r2: REF ANY] RETURNS [c: Basics.Comparison] -- OrderedSymbolTableRef.CompareProc-- = { fn1: FileNote _ NARROW[r1]; fn2: FileNote _ NARROW[r2]; IF (c _ fn1.name.Compare[fn2.name, FALSE]) = equal THEN { c _ SELECT BasicTime.Period[from: fn1.created, to: fn2.created] FROM <0 => less, =0 => equal, >0 => greater, ENDCASE => ERROR; }; }; Register: PROC [key: ROPE, command: Command, doc: ROPE] = { Commander.Register[key: key, proc: Generic, doc: doc, clientData: command]; CommandExtras.MakeUninterpreted[Commander.Lookup[CommandTool.CurrentWorkingDirectory[].Cat[key]]]; }; Start: PROC = { Register["CmdPerFile", cmdPerFile, "execute a command for every file in ..."]; Register["CmdAllFiles", cmdAllFiles, "execute a command that takes every file in ..."]; Register["ListFileSet", listFileSet, "list files in a set"]; Register["lfs", listFileSet, "list files in a set"]; Register["DeleteFileSet", deleteFileSet, "delete files in a set"]; Register["dfs", deleteFileSet, "delete files in a set"]; Register["ArchiveFileSet", archiveFileSet, "print request to archive files in a set"]; Register["afs", archiveFileSet, "print request to files in a set"]; Register["RetrieveFileSet", retrieveFileSet, "print request to retrieves files in a set from archive"]; Register["rfs", retrieveFileSet, "print request to retrieves files in a set from archive"]; }; Start[]; }. FileCmdsImpl.Mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Last Edited by: Spreitzer, December 6, 1984 8:44:46 pm PST set instance.createdNeeded. For syntax errors and such. Informational signal: Couldn't identify. now we're done with parse, eval & execute ΚK˜Icode™Kšœ Οmœ1™Kšœ!˜!—K˜š œžœžœžœ˜KKšœ žœ˜%—K˜š œžœžœžœ˜KKšœ žœ˜%—K˜š œžœžœ žœžœžœžœžœΟcœ˜tKšœžœ˜3Kšœžœžœžœ˜&Kšœžœ%˜=š œžœ‘œ˜.Kšœ žœ˜šžœžœžœ˜4Kšœ4žœ˜FKšœ0˜0K˜—šžœ˜Kšœ˜K˜—K˜—Kšžœžœžœ˜4š žœžœ.žœžœ ž˜Pšžœžœ˜Kšœžœ#˜/Kšžœ"žœžœ˜NKšžœžœ!žœžœ˜QKšžœžœžœžœ˜?Kš žœžœžœžœž˜BKš žœžœžœžœž˜BKšžœžœžœžœžœ(žœžœ˜hK˜—šžœ˜šžœžœ žœžœ˜-K˜Kšœžœ4žœ˜[Kšžœ‘K˜WKšœ˜—šžœžœž˜šœžœ˜šžœž˜K˜2Kšžœžœ˜K˜——šœžœ˜Kšžœžœžœžœ˜:K˜—Kšžœžœ˜—Kšœ*˜*K˜—šž˜Kšœžœ˜1—Kšžœ˜—Kšžœžœžœžœ˜Ašœ)™)šœžœ˜ ˜ Kšœ&žœ ˜5Kšžœžœžœ˜AK˜K˜&Kšžœ˜K˜—˜ K˜K˜Kšžœ˜ K˜—K˜—Kšžœžœžœ˜>K˜5Kšžœžœžœžœ˜OK˜—Kšžœžœžœžœžœ žœ3žœžœ˜ΝKšžœžœžœ*˜FKšœžœ˜:šž˜Kšœ žœ˜—K˜—K˜Kšœ žœžœ˜%šœžœžœ˜Kšœ žœžœ˜—K˜š œžœ˜.Kšœžœ˜+K˜—K˜š œžœžœ žœ˜JKšœžœ˜(šžœž˜Kšœ1˜1Kšœ2˜2Kšžœžœžœ˜—Kšœ žœ˜—K˜š œžœ˜3Kšœžœ˜(Kšœ;˜;K˜—K˜š œžœ'˜>Kšœžœ˜(Kš œ žœžœžœ"žœ˜[Kšœ žœ©˜ΊKšœ žœžœ˜KšœS˜SKšžœžœ1˜NK˜—K˜šœžœ˜)Kšœ žœ˜ Kšœ˜Kšœ˜Kšœ!˜!Kšœ˜Kšœ‚˜‚—K˜Kšœ žœžœ˜'šœžœžœ˜Kšœ.žœžœ˜9Kšœžœžœ˜—K˜š œžœ˜/Kšœžœ˜-K˜—K˜š œžœžœ žœ˜KKšœžœ˜)šžœž˜Kšœ0˜0Kšœ1˜1Kšœ/˜/Kšœ1˜1Kšœ2˜2Kšžœžœžœ˜—Kšœ žœ˜—K˜š œžœ˜4Kšœžœ˜)Kšœ;˜;K˜—K˜š œžœ'˜?Kšœžœ˜)Kš œ žœžœžœ"žœ˜[Kšœ žœ©˜ΆKšœ&žœ žœ žœ˜\Kšœ žœ˜K˜—K˜š  œžœžœ žœžœžœ˜UKšœžœ˜)K˜3KšœS˜SK˜—K˜šœžœ˜*Kšœ˜Kšœ ˜ Kšœ"˜"Kšœ˜Kšœ˜KšœΕ˜Ε—K˜Kšœ žœžœ˜'šœžœžœ˜Kšœžœžœ˜—K˜š œžœ˜/Kšœžœ˜-K˜—K˜š  œžœžœžœ žœ˜\Kšœžœ˜)Kšžœžœžœ ž˜1Kš žœžœžœžœ ž˜7Kšžœžœžœ˜Kšœ žœ˜—K˜š œžœ˜4Kšœžœ˜)Kšœ#˜#—K˜š œžœ'˜?Kšœžœ˜)K˜"Kšžœ žœžœ˜CK˜ —K˜šœžœ˜*Kšœ˜Kšœ&˜&Kšœ"˜"Kšœ˜Kšœu˜u—K˜š œžœ˜/šœžœž˜1Kšœžœ˜ Kšœžœ˜Kšžœžœ˜——K˜š  œžœ'˜:Kšœžœžœ˜K˜"šžœž˜Kšœžœ˜Kšœ2žœ˜GKšžœžœ˜—K˜"šžœ˜ Kšœ˜šœžœž˜+K˜ K˜Kšžœž˜—šœžœ ˜Kšœ*žœžœ˜[Kšœžœ˜ K˜0Kšžœ˜ Kšœ˜——Kšžœžœ#˜-K˜—K˜šœžœ˜,Kšœ˜Kšœ˜Kšœq˜q—K˜š œžœ˜7Kšœžœ˜ —K˜š œžœ'˜BKšœ'žœ˜:—K˜šœžœ˜-Kšœ%˜%Kšœ˜Kšœr˜r—K˜š œžœ˜8Kšœžœ˜—K˜š œžœ'˜CKš œ<žœžœžœžœ˜™—K˜šœžœ˜.Kšœ&˜&Kšœ ˜ Kšœs˜s—K˜š  œžœžœžœ žœ˜FKšœžœ˜Kšœžœ˜Kšœžœ˜%Kšœžœ˜"šžœ ž˜K˜LKšœ˜Kšœ$˜$Kšžœ˜—K˜—K˜š  œžœžœžœžœžœ˜Tš œžœ‘œ˜-Kšœ!˜!šžœžœž˜šœ žœ˜K˜K˜—Kš œ žœžœ%žœžœžœ)žœ˜†Kšžœžœ˜—K˜—š œžœ‘œ˜2Kšœ˜K˜—š œžœžœ žœ žœžœžœ žœ‘œ˜†K˜,Kšžœžœžœ ˜Kšœ žœ˜K˜—šœžœ˜Kš œ(žœžœZžœžœ˜£Kšœ žœ˜—šžœžœž˜šœžœ˜ šžœ žœž˜šœžœžœž˜šœ‘˜‘Kšœ1˜1K˜Kš œ žœžœžœžœ˜š žœžœžœžœž˜+šžœ žœž˜šœžœžœž˜Kšœžœ˜ Kšœ#˜#Kšœ%˜%Kšœ*˜*Kšœ#˜#Kšœ%˜%Kšœ*˜*Kšœ'˜'Kšœ'˜'Kšœ-˜-Kšœ˜Kšœ˜Kšžœžœžœ$žœ ˜J—šœžœ˜ Kšœ žœ˜Kš žœžœžœžœ7žœ˜oKš žœžœžœžœžœ7žœ ˜nš žœžœžœžœž˜1K˜Kšžœ˜—Kšœžœ#˜4š žœžœžœžœž˜1Kšœžœžœ ˜+K˜ Kšžœ˜—Kšžœ'žœžœ˜4K˜—šœžœ˜ š  œžœ žœžœ žœ‘œ˜OKšœ žœ˜#Kšœ žœ˜K˜—Kš œ žœžœžœ žœ˜;Kšžœ%˜'K˜—Kšžœžœžœ%žœ˜T—Kšžœ˜—šžœ!žœ žœž˜:KšœM˜MKšžœ˜—Kšœ-˜-—˜Kšœ žœžœ˜&Kšœžœ˜(Kš œžœžœ)žœžœ˜gKšœ-˜-—˜ K˜š žœžœžœžœž˜6K˜%KšœB˜BK˜Kšžœ˜—KšœF˜FK˜—˜ Kšœžœžœ˜K˜š žœžœžœžœž˜6K˜%KšœB˜BKšœ žœžœžœ ˜.Kšœžœ˜Kšžœ˜—KšœF˜FK˜—šœ ‘˜!Kšœžœžœ˜K˜š žœžœžœžœž˜6š œžœ‘œ˜>Kšžœžœžœžœ˜#Kšœ˜Kšœ˜K˜—K˜%KšœB˜BKšœ#˜#K˜Kšœžœ˜Kšžœ˜—KšœF˜FK˜—˜Kšœžœžœ˜K˜š žœžœžœžœž˜6K˜%š œžœ˜Kšžœžœžœ˜1—KšœB˜BKšžœžœ žœžœ˜5Kšžœ˜—KšœF˜FK˜—Kšžœžœžœžœ˜D—Kšžœžœžœžœ˜D—K˜—šœžœ˜ Kšžœ.˜0Kšœ-˜-—šœžœ˜ Kšžœ=˜?Kšœ-˜-—Kšžœžœžœžœ˜D—K˜—K˜š œžœžœ$˜8š  œžœžœžœžœžœ˜4Kšœžœ˜K˜Kšœ˜Kšœžœ˜—K˜%K˜ —K˜š  œžœžœžœ˜4Kšœžœžœ7žœ ˜Ršžœžœžœžœ˜0Kšœ4˜4Kšœ˜šžœSžœ žœž˜lKšœžœžœžœ&˜;š žœžœ0žœžœž˜SKšœ žœ$˜1K˜šžœžœžœ˜'Kšœ žœ˜&Kšœ žœ!˜.Kš žœžœžœžœžœ ˜AK˜—Kšžœ(žœžœ ˜?Kš žœžœžœžœžœžœ ˜Kšœ˜Kšžœ˜—Kšžœ˜—Kšžœžœ4žœ˜_šž˜Kšœžœ˜—Kšœžœ˜ —Kšžœžœ;žœ˜YK˜—K˜š  œžœ žœ žœžœ˜^Kšœ žœžœ˜šžœž˜˜ Kšœžœžœ˜Kšœžœ˜Kš œ*žœCžœžœžœžœ˜ššžœžœžœ˜Kšžœ˜Kšžœžœ˜—K˜—˜Kšœžœ˜Kšœžœ˜)Kšžœžœž˜šžœ˜Kšœžœžœ˜Kšœžœžœ˜Kšœžœ˜Kš œ*žœCžœžœžœžœ˜ššžœžœžœ˜Kšžœ˜Kšžœžœ˜—K˜—K˜—˜Kšœžœžœ˜Kšœžœ˜Kšœ žœ˜%Kšœ`˜`Kšžœžœž˜šžœ˜Kšœžœžœ˜Kšœžœ˜Kš œžœžœžœžœ˜Qšžœžœžœ˜Kšžœ˜Kšžœžœ˜—K˜—K˜—Kšžœžœ˜—KšœžœN˜VK˜—K˜š œžœžœžœG˜mKšœ žœžœ˜K˜Kšœ+˜+Kšœ˜šžœSžœ žœž˜lKšœžœžœžœ&˜;š œžœžœ˜Kšœžœ$˜-Kšžœžœžœžœžœ8žœžœžœ˜“K˜—š  œžœ˜Kšœžœ˜Kš žœ žœžœžœ=žœ žœ˜wK˜—š žœžœ0žœžœž˜SKšœ žœ$˜1K˜šžœžœ˜$Kšœžœ˜Kšœžœ˜ K˜ Kšœ žœ˜Kšœ,˜,K˜ Kšœ˜K˜ Kšœ&˜&K˜ Kšœ&˜&K˜ šžœ:žœžœ˜FK˜K˜Kšœ˜K˜—Kšœž˜—šžœžœ˜%Kšœžœ˜Kšœžœ˜ K˜ Kšœ žœ˜Kšœ,˜,K˜ Kšœ˜K˜ Kšœ*˜*K˜ Kšžœžœžœ(žœžœžœžœ˜Kšœž˜—šžœžœ˜#Kšœ žœ˜K˜ Kšœ žœ˜Kšœ,˜,Kšœ ˜ šžœžœ˜ š  œžœ žœžœ žœ‘œ˜LKšžœ%žœžœ˜7Kšœ žœ˜—Kšžœ&˜(K˜—šžœ˜Kšžœ$žœžœ˜6K˜—Kšœž˜—šžœžœ˜$Kšœžœ˜Kšœžœ˜ K˜ Kšœ žœ˜Kšœ,˜,K˜ Kšœ˜K˜ Kšœ&˜&K˜ Kšœ&˜&Kšœ ˜ šžœ"žœžœ˜.K˜K˜Kšœ˜K˜—Kšœž˜—šœ˜Kšžœžœ žœžœžœYžœžœ˜Kšžœžœžœ˜#Kš žœ(žœžœžœ8žœžœ˜™Kšœ˜Kšœ˜—Kšžœ˜—Kšœ ˜ Kšžœ˜—K˜—K˜š   œžœ žœžœ žœ˜;Kšœ"˜"—K˜š  œžœžœžœžœ ‘œ˜JKš œžœžœžœžœ˜3K˜—K˜š   œžœ žœžœžœ˜Pšžœ ž˜Kšœ žœ ˜Kšœ"žœ˜K˜šžœžœž˜KšœžœG˜Nšœžœ˜!Kš œžœžœžœžœžœžœ˜OKšœžœ˜ Kšœžœ˜K˜ Kšœžœ˜2šžœžœž˜Kšœ(˜(KšœB˜BKšžœžœžœžœ˜$—K˜—šœžœ˜$˜!Kšœ˜Kšœ˜Kšœ žœžœ žœ˜@Jšœ˜Jšœžœžœ žœ ˜6J˜—Kšœ5˜5K˜—KšœžœN˜UKšœžœžœ˜(Kšœžœžœ˜+Kšžœžœ˜—K˜—Kšœžœ žœ˜$Kšœžœ˜ šœžœ˜Kšœ ˜ šœžœ ž˜*K˜Kšœ4˜4Kšžœžœ˜——Kšžœžœžœžœ˜&Kšœžœ˜#Kš œažœžœ3žœžœžœ˜ΰK˜ K˜—K˜š   œžœ žœžœžœ‘&œ˜nKšœžœ˜Kšœžœ˜Kšœžœ˜'—K˜š   œžœ žœžœžœ‘&œ˜oKšœžœ˜Kšœžœ˜šžœ!žœ žœ˜9šœžœ6ž˜DK˜ K˜ K˜Kšžœžœ˜—Kšœ˜—K˜—K˜š œžœžœžœ˜;K˜KK˜bK˜—K˜š œžœ˜K˜NK˜WK˜