DIRECTORY Basics, BasicTime, BcdDefs, CommandTool, Convert, DFUtilities, FileSets, FileSetsPrivate, FS, FSPseudoServers, FSRemoteFile, IO, Process, RedBlackTree, Rope, STP, STPConnectionCache, Tempus, TextFind, TextReplace, VersionMap, ViewerClasses, ViewerTools; FileSetsImpl: CEDAR PROGRAM IMPORTS BasicTime, CommandTool, Convert, DFUtilities, FS, FSPseudoServers, FSRemoteFile, IO, Process, RedBlackTree, Rope, STP, STPConnectionCache, Tempus, TextFind, TextReplace, VersionMap EXPORTS FileSets, FileSetsPrivate ={OPEN FileSets, FileSetsPrivate; Error: PUBLIC ERROR [message: ROPE] = CODE; Warning: PUBLIC SIGNAL [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.id.name, remoteCheck: FALSE !FS.Error => {ok _ FALSE; CONTINUE}]; IF ok THEN fn.created _ created _ ct}; }; GetAccounting: PUBLIC PROC [fn: FileNote] RETURNS [read, write: GMT, author: ROPE] = { h: STPConnectionCache.Handle; full, hostName, rest: ROPE; cp: FS.ComponentPositions; IF fn.readTried THEN RETURN [fn.read, fn.write, fn.author]; fn.readTried _ TRUE; [full, cp] _ FS.ExpandName[fn.fsName]; hostName _ full.Substr[start: cp.server.start, len: cp.server.length]; IF hostName.Length[] = 0 THEN {--try for a remote attachment-- attachedTo: ROPE; [attachedTo: attachedTo] _ FS.FileInfo[full]; IF attachedTo # NIL THEN { [full, cp] _ FS.ExpandName[attachedTo]; hostName _ full.Substr[start: cp.server.start, len: cp.server.length]; }; }; rest _ full.Substr[start: cp.dir.start-1]; IF rest.Fetch[0] # '< THEN ERROR; IF hostName.Length[] # 0 THEN { servers: LOR _ FSPseudoServers.TranslateForRead[hostName]; found: BOOL _ FALSE; FOR servers _ servers, servers.rest WHILE servers # NIL AND NOT found DO server: ROPE _ servers.first; h _ STPConnectionCache.Get[server, "FileSetsImpl.GetAccounting" !STP.Error => IF code IN STP.ConnectionErrors THEN LOOP]; {ENABLE UNWIND => h.Release[stpTimeout]; PerMatch: PROC [file: ROPE] RETURNS [continue: STP.Continue] = { fi: STP.FileInfo _ h.stpHandle.GetFileInfo[]; fn.read _ FSRemoteFile.FTPTimeToGMT[fi.read]; fn.write _ FSRemoteFile.FTPTimeToGMT[fi.write]; fn.author _ fi.author; found _ TRUE; RETURN [yes]}; h.stpHandle.Enumerate[rest, PerMatch ! STP.Error => IF code = noSuchFile THEN CONTINUE]; }; h.Release[stpTimeout]; ENDLOOP; found _ found; }; RETURN [fn.read, fn.write, fn.author]; }; stpTimeout: INT--seconds-- _ 15; NewFileSet: PUBLIC PROC [ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { fs _ NEW [FileSetRep _ [ elts: RedBlackTree.Create[Id, IF ids.askFS OR ids.create THEN CompareFullFileNotes ELSE CompareFileNotesByName], ids: ids, summary: NIL]]; }; Id: PROC [data: REF ANY] RETURNS [key: REF ANY] --RedBlackTree.GetKey-- = {key _ data}; CompareFileNotesByName: PROC [k, data: REF ANY] RETURNS [c: Basics.Comparison] --RedBlackTree.Compare-- = { fn1: FileNote _ NARROW[k]; fn2: FileNote _ NARROW[data]; c _ fn1.id.name.Compare[fn2.id.name, FALSE]}; CompareFullFileNotes: PROC [k, data: REF ANY] RETURNS [c: Basics.Comparison] --RedBlackTree.Compare-- = { fn1: FileNote _ NARROW[k]; fn2: FileNote _ NARROW[data]; IF (c _ fn1.id.name.Compare[fn2.id.name, FALSE]) = equal THEN { c _ SELECT BasicTime.Period[from: fn1.id.created, to: fn2.id.created] FROM <0 => less, =0 => equal, >0 => greater, ENDCASE => ERROR; }; }; EnumSet: PUBLIC PROC [fs: FileSet, to: FileConsumer] = { FOR fn: FileNote _ NARROW[fs.elts.LookupSmallest[]], NARROW[fs.elts.LookupNextLarger[fn]] WHILE fn # NIL DO Process.CheckForAbort[]; to[fn]; ENDLOOP; fs _ fs}; First: PUBLIC PROC [fs: FileSet] RETURNS [fn: FileNote] = {fn _ NARROW[fs.elts.LookupSmallest[]]}; Next: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [next: FileNote] = {next _ NARROW[fs.elts.LookupNextLarger[fn]]}; 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] = { node: RedBlackTree.Node _ fs.elts.Delete[fn]; found _ IF node # NIL THEN NARROW[node.data] ELSE NIL; }; Insert: PUBLIC PROC [fs: FileSet, fn: FileNote] RETURNS [news: BOOL] = { prevFN: FileNote _ Lookup[fs, fn]; SELECT (news _ prevFN = NIL) FROM TRUE => fs.elts.Insert[fn, fn]; FALSE => NULL; ENDCASE => ERROR; }; Size: PUBLIC PROC [fs: FileSet] RETURNS [numberOfFiles: INT] = {numberOfFiles _ fs.elts.Size[]}; MapNames: PUBLIC PROC [fs: FileSet, map: RopeMap, mapIDName, mapFSName: BOOL _ TRUE] RETURNS [mfs: FileSet] = { TranslateFile: PROC [fn: FileNote] = { newFN: FileNote _ NEW [FileNoteRep _ fn^]; IF mapIDName THEN newFN.id.name _ MapName[newFN.id.name, mfs.ids]; IF mapFSName THEN newFN.fsName _ MapName[newFN.fsName, fullID]; IF NOT Insert[mfs, newFN] THEN ERROR; }; MapName: PROC [pn: ROPE, ids: IdentificationScheme] RETURNS [rn: ROPE] = { full: ROPE; cp: FS.ComponentPositions; rn _ map.Apply[pn]; [full, cp] _ FS.ExpandName[rn]; rn _ TrimNameByIDS[full, cp, ids]; }; mfs _ NewFileSet[fs.ids]; EnumSet[fs, TranslateFile]; mfs.summary _ Convert.RopeFromInt[Size[mfs]].Cat[":(MapNames ", fs.summary, ")"]; }; fullID: IdentificationScheme = [server: TRUE, directory: TRUE, version: TRUE, create: TRUE, askFS: FALSE]; ReIdentify: PUBLIC PROC [fs: FileSet, ids: IdentificationScheme _ []] RETURNS [mfs: FileSet] = { Mapit: PROC [fn: FileNote] = { newFN: FileNote = CreateNote[fn.id.name, fn.id.created, ids]; IF newFN # NIL THEN [] _ Insert[mfs, newFN]; }; mfs _ NewFileSet[ids]; EnumSet[fs, Mapit]; mfs.summary _ Convert.RopeFromInt[Size[mfs]].Cat[":(ReIdentify ", fs.summary, ")"]; }; MapNameAndLookup: PUBLIC PROC [fs: FileSet, map: RopeMap, ids: IdentificationScheme _ []] RETURNS [mfs: FileSet] = { TranslateFile: PROC [fn: FileNote] = { newName: ROPE = map.Apply[fn.id.name]; newFN: FileNote = CreateNote[newName, noGMT, ids]; IF newFN # NIL THEN [] _ Insert[mfs, newFN]; }; mfs _ NewFileSet[ids]; EnumSet[fs, TranslateFile]; mfs.summary _ Convert.RopeFromInt[Size[mfs]].Cat[":(MapNameAndLookup ", fs.summary, ")"]; }; pseudoServerToRead: PUBLIC RopeMap _ NEW [TextReplace.RopeMapRep _ [ MapPseudoServerToRead]]; MapPseudoServerToRead: PROC [data: REF ANY, pn: ROPE] RETURNS [rn: ROPE] = { full: ROPE; cp: FS.ComponentPositions; IF pn.Length[] = 0 THEN RETURN [pn]; [full, cp] _ FS.ExpandName[pn]; rn _ FS.ConstructFName[[ server: FSPseudoServers.TranslateForRead[full.Substr[cp.server.start, cp.server.length]].first, dir: full.Substr[cp.dir.start, cp.dir.length], subDirs: full.Substr[cp.subDirs.start, cp.subDirs.length], base: full.Substr[cp.base.start, cp.base.length], ext: full.Substr[cp.ext.start, cp.ext.length], ver: full.Substr[cp.ver.start, cp.ver.length] ]]; }; Direction: TYPE = {normal, reverse}; HasRelative: PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction] RETURNS [has: BOOL] = { NoteRelative: PROC [rfn: FileNote] = {has _ TRUE; Enuf}; has _ FALSE; EnumerateRelatives[fn, fs, fr, direction, NoteRelative !Enuf => CONTINUE]; has _ has; }; Enuf: ERROR = CODE; EnumerateRelatives: PROC [fn: FileNote, fs: FileSet, fr: FileRelation, direction: Direction, NoteRelative: PROC [FileNote]] = { fir: FileIDRange = SELECT direction FROM normal => fr.LeftToRight[fr.data, fn.id], reverse => fr.RightToLeft[fr.data, fn.id], ENDCASE => ERROR; Relates: PROC [fn2: FileNote] RETURNS [b: BOOL] = { b _ SELECT direction FROM normal => fr.Test[fr.data, fn.id, fn2.id], reverse => fr.Test[fr.data, fn2.id, fn.id], ENDCASE => ERROR; }; test: FileNote = NEW [FileNoteRep _ [ id: LowEnd[fir] ]]; highID: FileID = HighEnd[fir]; found: FileNote _ NARROW[fs.elts.Lookup[test]]; IF found # NIL AND Relates[found] THEN NoteRelative[found]; FOR found _ NARROW[fs.elts.LookupNextLarger[test]], NARROW[fs.elts.LookupNextLarger[found]] WHILE found # NIL AND LessOrEqual[found.id, highID] DO IF Relates[found] THEN NoteRelative[found]; ENDLOOP; found _ found; }; LowEnd: PROC [fir: FileIDRange] RETURNS [fid: FileID] = { fid _ [ name: fir.lowName, created: IF fir.earlyCreated = noGMT THEN BasicTime.earliestGMT ELSE fir.earlyCreated ]; }; HighEnd: PROC [fir: FileIDRange] RETURNS [fid: FileID] = { fid _ [ name: fir.highName, created: IF fir.lateCreated = noGMT THEN BasicTime.latestGMT ELSE fir.lateCreated ]; }; LessOrEqual: PROC [fid1, fid2: FileID] RETURNS [leq: BOOL] = { leq _ SELECT fid1.name.Compare[fid2.name, FALSE] FROM less => TRUE, equal => BasicTime.Period[from: fid1.created, to: fid2.created] <= 0, greater => FALSE, ENDCASE => ERROR; }; OpWork: PROC [fsl: FileSetList, head: ROPE, combine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet]] RETURNS [fs: FileSet] = { summary: ROPE _ head; fs _ NIL; IF fsl = NIL THEN ERROR Error["NIL FileSetList given --- that's a no-no"]; FOR fsl _ fsl, fsl.rest WHILE fsl # NIL DO IF fs # NIL AND fs.ids # fsl.first.ids THEN ERROR; fs _ combine[fs, fsl.first]; summary _ summary.Cat[" ", Convert.RopeFromInt[Size[fsl.first]]]; ENDLOOP; IF fs = NIL THEN ERROR; fs.summary _ Convert.RopeFromInt[Size[fs]].Cat[":", summary, ")"]; }; Union: PUBLIC PROC [fsl: FileSetList, fr: FileRelation _ NIL] RETURNS [fs: FileSet] = { UnionCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[sum, fn]}; AddIfNotRelated: PROC [fn: FileNote] --FileConsumer-- = { related: BOOL = HasRelative[fn, sum, fr, reverse]; IF NOT related THEN [] _ Insert[sum, fn]; }; sum _ sofar; IF sum = NIL THEN sum _ NewFileSet[this.ids]; EnumSet[this, IF fr = NIL THEN Add ELSE AddIfNotRelated]; }; fs _ OpWork[fsl, "(union", UnionCombine]; }; Difference: PUBLIC PROC [fsl: FileSetList, fr: FileRelation _ NIL] RETURNS [fs: FileSet] = { DiffCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = { IF sofar = NIL THEN { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[sum, fn]}; sum _ NewFileSet[this.ids]; EnumSet[this, Add]; } ELSE { Subtract: PROC [fn: FileNote] --FileConsumer-- = {[] _ Delete[sum, fn]}; SubtractRelatives: PROC [fn: FileNote] --FileConsumer-- = { EnumerateRelatives[fn, sum, fr, reverse, Subtract]; }; sum _ sofar; EnumSet[this, IF fr = NIL THEN Subtract ELSE SubtractRelatives]; }; }; fs _ OpWork[fsl, "(diff", DiffCombine]; }; SymmetricDifference: PUBLIC PROC [fsl: FileSetList, fr: FileRelation _ NIL] RETURNS [fs: FileSet] = { SDiffCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = { IF sofar = NIL THEN { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[sum, fn]}; sum _ NewFileSet[this.ids]; EnumSet[this, Add]; } ELSE { SimpleSDiffit: PROC [fn: FileNote] --FileConsumer-- = { SELECT Lookup[sum, fn] FROM =NIL => [] _ Insert[sum, fn]; #NIL => [] _ Delete[sum, fn]; ENDCASE => ERROR; }; GeneralSDiffit: PROC [fn: FileNote] --FileConsumer-- = { related: BOOL _ FALSE; SeeLeftee: PROC [fn: FileNote] --FileConsumer-- = { [] _ Delete[sum, fn]; related _ TRUE; }; EnumerateRelatives[fn, sum, fr, reverse, SeeLeftee]; IF NOT related THEN [] _ Insert[sum, fn]; }; sum _ sofar; EnumSet[this, IF fr = NIL THEN SimpleSDiffit ELSE GeneralSDiffit]; }; }; fs _ OpWork[fsl, "(sdiff", SDiffCombine]; }; Intersection: PUBLIC PROC [fsl: FileSetList, fr: FileRelation _ NIL] RETURNS [fs: FileSet] = { IntersectionCombine: PROC [sofar, this: FileSet] RETURNS [sum: FileSet] = { IF sofar = NIL THEN { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[sum, fn]}; sum _ NewFileSet[this.ids]; EnumSet[this, Add]; } ELSE { SimpleIntersect: PROC [fn: FileNote] --FileConsumer-- = { IF Lookup[this, fn] = NIL THEN [] _ Delete[sum, fn]; }; GeneralIntersect: PROC [fn: FileNote] --FileConsumer-- = { IF NOT HasRelative[fn, this, fr, normal] THEN [] _ Delete[sum, fn]; }; sum _ sofar; EnumSet[sum, IF fr = NIL THEN SimpleIntersect ELSE GeneralIntersect]; }; }; fs _ OpWork[fsl, "(intersect", IntersectionCombine]; }; equality: PUBLIC FileRelation _ NEW [FileRelationPrivate _ [ TestEquality, PointRange, PointRange]]; TestEquality: PROC [data: REF ANY, left, right: FileID] RETURNS [b: BOOL] = { b _ left.name.Equal[right.name, FALSE] AND left.created = right.created; }; PointRange: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = { fir _ [ lowName: fid.name, highName: fid.name, earlyCreated: fid.created, lateCreated: fid.created ]; }; ReverseFileRelation: PUBLIC PROC [fr: FileRelation] RETURNS [frr: FileRelation] = { frr _ NEW [FileRelationPrivate _ [ TestReverse, ReverseLeftToRight, ReverseRightToLeft, fr]]; }; TestReverse: PROC [data: REF ANY, left, right: FileID] RETURNS [b: BOOL] = { fr: FileRelation = NARROW[data]; b _ fr.Test[fr.data, right, left]; }; ReverseLeftToRight: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = { fr: FileRelation = NARROW[data]; fir _ fr.RightToLeft[fr.data, fid]; }; ReverseRightToLeft: PROC [data: REF ANY, fid: FileID] RETURNS [fir: FileIDRange] = { fr: FileRelation = NARROW[data]; fir _ fr.LeftToRight[fr.data, fid]; }; FilterFileSet: PUBLIC PROC [subject: FileSet, filter: Filter] RETURNS [filtered: FileSet] = { DoFilter: PROC [fn: FileNote] = {IF filter.Eval[fn, filter.data] THEN [] _ Insert[filtered, fn]}; filtered _ NewFileSet[subject.ids]; EnumSet[subject, DoFilter]; filtered.summary _ Convert.RopeFromInt[Size[filtered]].Cat[":(filter ", subject.summary, ")"]; }; And: PUBLIC PROC [fl: FilterList] RETURNS [f: Filter] = { f _ NEW [FilterRep _ [AndFilters, fl]]; }; AndFilters: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { fl: FilterList _ NARROW[data]; FOR fl _ fl, fl.rest WHILE fl # NIL DO IF NOT fl.first.Eval[fn, fl.first.data] THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE]; }; Or: PUBLIC PROC [fl: FilterList] RETURNS [f: Filter] = { f _ NEW [FilterRep _ [OrFilters, fl]]; }; OrFilters: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { fl: FilterList _ NARROW[data]; FOR fl _ fl, fl.rest WHILE fl # NIL DO IF fl.first.Eval[fn, fl.first.data] THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE]; }; Not: PUBLIC PROC [f: Filter] RETURNS [nf: Filter] = { nf _ NEW [FilterRep _ [NegateFilter, f]]}; NegateFilter: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { f: Filter _ NARROW[data]; RETURN [NOT f.Eval[fn, f.data]]; }; IfCreated: PUBLIC PROC [reln: TimeReln, when: GMT] RETURNS [f: Filter] = { f _ NEW [FilterRep _ [TestTime, NEW [TimeFilterRep _ [created, reln, when]]]]; }; IfRead: PUBLIC PROC [reln: TimeReln, when: GMT] RETURNS [f: Filter] = { f _ NEW [FilterRep _ [TestTime, NEW [TimeFilterRep _ [read, reln, when]]]]; }; TimeFilter: TYPE = REF TimeFilterRep; TimeFilterRep: TYPE = RECORD [ which: WhichTime, reln: TimeReln, when: GMT]; WhichTime: TYPE = {created, read}; TestTime: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { tf: TimeFilter _ NARROW[data]; time: GMT _ SELECT tf.which FROM read => GetAccounting[fn].read, created => GetCreated[fn], ENDCASE => ERROR; IF time = noGMT THEN RETURN [FALSE]; RETURN [BasicTime.Period[from: tf.when, to: time] * (SELECT tf.reln FROM before => -1, after => 1, ENDCASE => ERROR) > 0]; }; NameMatches: PUBLIC PROC [pattern: ROPE, literal, word, ignoreCase, addBounds: BOOL _ FALSE] RETURNS [f: Filter] = { f _ NEW [FilterRep _ [TestName, TextFind.CreateFromRope[pattern: pattern, literal: literal, word: word, ignoreCase: ignoreCase, addBounds: addBounds] ]]; }; TestName: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { finder: TextFind.Finder _ NARROW[data]; RETURN [TextFind.SearchRope[finder, fn.fsName].found]; }; isOnline: PUBLIC Filter _ NEW [FilterRep _ [TestOnline, NIL]]; TestOnline: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { exists: BOOL _ TRUE; [] _ FS.FileInfo[name: fn.fsName, wantedCreatedTime: GetCreated[fn] !FS.Error => {exists _ FALSE; CONTINUE}]; RETURN [exists]; }; DFContentsFilter: TYPE = REF DFContentsFilterRec; DFContentsFilterRec: TYPE = RECORD [ filter: Filter, dfFilter: DFUtilities.Filter, allowableExceptions: NAT, ids: IdentificationScheme ]; ContentsPass: PUBLIC PROC [filter: Filter, dfFilter: DFUtilities.Filter, allowableExceptions: NAT _ 0, ids: IdentificationScheme] RETURNS [f: Filter--applicable only to DF files--] = { f _ NEW [FilterRep _ [TestDFContents, NEW [DFContentsFilterRec _ [filter, dfFilter, allowableExceptions]] ]]; }; TestDFContents: PROC [fn: FileNote, data: REF ANY] RETURNS [BOOL] = { dfcf: DFContentsFilter _ NARROW[data]; exceptions: NAT _ 0; TestContent: PROC [fn: FileNote] = { IF NOT dfcf.filter.Eval[fn, dfcf.filter.data] THEN exceptions _ exceptions + 1; }; created: GMT _ GetCreated[fn]; EnumDF[ dfName: fn.fsName, date: IF created # noGMT THEN [explicit, created] ELSE [], to: TestContent, filter: dfcf.dfFilter, which: remote, ids: dfcf.ids]; RETURN [exceptions <= dfcf.allowableExceptions]; }; CreateNote: PUBLIC PROC [fileName: ROPE, created: GMT, ids: IdentificationScheme] RETURNS [fn: FileNote] = { createTried: BOOL _ FALSE; fsName: ROPE _ NIL; realCreated: GMT _ created; SELECT ids.askFS FROM TRUE => { 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]}; fsName _ fileName; realCreated _ created; createTried _ TRUE; }; FALSE => { cp: FS.ComponentPositions; needInfo: BOOL _ FALSE; [fileName, cp] _ FS.ExpandName[fileName]; IF (ids.version AND cp.ver.length = 0) OR (ids.create AND created = noGMT) THEN { ok: BOOL _ 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]}; [fileName, cp] _ FS.ExpandName[fileName]; realCreated _ created; createTried _ TRUE; }; fsName _ fileName; fileName _ TrimNameByIDS[fileName, cp, ids]; IF NOT ids.create THEN created _ noGMT; }; ENDCASE => ERROR; fn _ NEW [FileNoteRep _ [id: [name: fileName, created: created], fsName: fsName, created: realCreated, createTried: createTried]]; }; TrimNameByIDS: PROC [full: ROPE, cp: FS.ComponentPositions, ids: IdentificationScheme] RETURNS [fileName: ROPE] = { fileName _ full; IF NOT ids.version THEN fileName _ fileName.Substr[len: cp.ext.start + cp.ext.length]; IF NOT ids.directory THEN fileName _ fileName.Substr[start: cp.base.start] ELSE IF NOT ids.server THEN fileName _ fileName.Substr[start: cp.dir.start-1]; }; FromFS: PUBLIC PROC [pattern: ROPE, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[fs, fn]}; fs _ NewFileSet[ids]; EnumFSMatches[pattern, Add, ids]; fs.summary _ Convert.RopeFromInt[Size[fs]]; }; EnumFSMatches: PUBLIC PROC [pattern: ROPE, to: FileConsumer, ids: IdentificationScheme _ []] = { PerMatch: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = { fn: FileNote _ CreateNote[fullFName, created, ids]; IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR; IF fn # NIL THEN to[fn]; }; HashEnumerate[pattern, FSEnumerate, PerMatch]; }; FSEnumerate: PROC [pattern: ROPE, Consume: Consumer] = { PerFile: PROC [ fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] = { Consume[fullFName, created, NIL, NIL]; continue _ TRUE}; FS.EnumerateForInfo[pattern: pattern, proc: PerFile]; }; FromVersionMapsByShortName: PUBLIC PROC [vml: VersionMapList, pattern: ROPE, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[fs, fn]}; fs _ NewFileSet[ids]; EnumVersionMapsByShortName[vml, pattern, Add, ids]; fs.summary _ Convert.RopeFromInt[Size[fs]]; }; EnumVersionMapsByShortName: PUBLIC PROC [vml: VersionMapList, pattern: ROPE, to: FileConsumer, ids: IdentificationScheme _ []] = { VMEnumerate: PROC [pattern: ROPE, Consume: Consumer] = {EnumVMsByShort[vml, pattern, Consume]}; PerMatch: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = { fn: FileNote _ CreateNote[fullFName, created, ids]; IF primaryVolume # NIL OR backupVolume # NIL THEN ERROR; IF fn # NIL THEN to[fn]; }; HashEnumerate[pattern, VMEnumerate, PerMatch]; }; EnumVMsByShort: PROC [vml: VersionMapList, pattern: ROPE, Consume: Consumer] = { size: INT = Rope.Length[pattern]; starPos: INT = pattern.Index[0, "*"]; match: BOOL = starPos # size; prefix: ROPE = Rope.Flatten[pattern, 0, starPos]; prefixLen: INT = Rope.Length[prefix]; rangeList: VersionMap.RangeList _ VersionMap.ShortNameToRanges[vml, prefix]; IF size = 0 THEN RETURN; WHILE rangeList # NIL DO range: VersionMap.Range _ rangeList.first; map: VersionMap.Map = range.map; rangeList _ rangeList.rest; Process.CheckForAbort[]; IF match THEN { entries: CARDINAL = VersionMap.Length[map]; IF range.first >= entries THEN LOOP; range.len _ entries - range.first; WHILE range.len # 0 DO fullName: ROPE; stamp: BcdDefs.VersionStamp; thisShort: ROPE; created: BasicTime.GMT; [fullName, stamp, created, range] _ VersionMap.RangeToEntry[range]; thisShort _ ShortName[fullName]; IF Rope.Run[prefix, 0, thisShort, 0, FALSE] # prefixLen THEN EXIT; IF Rope.Match[pattern, thisShort, FALSE] THEN { Consume[fullName, created, NIL, NIL]; }; ENDLOOP; } ELSE { WHILE range.len # 0 DO fullName: ROPE; stamp: BcdDefs.VersionStamp; created: BasicTime.GMT; [fullName, stamp, created, range] _ VersionMap.RangeToEntry[range]; Consume[fullName, created, NIL, NIL]; ENDLOOP; }; ENDLOOP; }; ShortName: PROC [r: ROPE] RETURNS [ROPE] = { first: INT _ 0; last: INT _ Rope.Length[r]; FOR i: INT DECREASING IN [0..last) DO c: CHAR _ r.Fetch[i]; SELECT c FROM '>, '/ => {first _ i+1; EXIT}; '! => last _ i ENDCASE; ENDLOOP; RETURN [r.Substr[first, last - first]] }; FromVersionMapsByCreateDate: PUBLIC PROC [vml: VersionMapList, range: TimeRange, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[fs, fn]}; fs _ NewFileSet[ids]; EnumVersionMapsByCreateDate[vml, range, Add, ids]; fs.summary _ IO.PutFR["%g:(versionMapFilesCreated \"%g\" \"%g\")", [integer[Size[fs]]], [time[range.from]], [time[range.to]]]; }; EnumVersionMapsByCreateDate: PUBLIC PROC [vml: VersionMapList, range: TimeRange, to: FileConsumer, ids: IdentificationScheme _ []] = { FOR vml _ vml, vml.rest WHILE vml # NIL DO map: VersionMap.Map = vml.first; Process.CheckForAbort[]; FOR i: INT IN [0 .. map.Length[]) DO created: GMT = map.FetchCreated[i]; IF InRange[created, range] THEN { fn: FileNote = CreateNote[VersionMap.FetchName[map, i], created, ids]; IF fn # NIL THEN to[fn]; }; ENDLOOP; ENDLOOP; }; InRange: PROC [time: GMT, range: TimeRange] RETURNS [in: BOOL] = INLINE { in _ BasicTime.Period[range.from, time] >= 0 AND BasicTime.Period[time, range.to] >= 0; }; HashEnumerate: PUBLIC PROC [pattern: ROPE, Enumerate: Enumerator, Consume: Consumer] = { fsPattern: ROPE _ pattern; matchPattern: ROPE _ pattern; filteringNeeded: BOOL _ FALSE; nChecks: INT _ 0; checks: LOR _ NIL; finder: TextFind.Finder _ NIL; NoteFile: PROC [fullFName: ROPE, created: GMT, primaryVolume, backupVolume: ROPE] = { IF filteringNeeded THEN { found: BOOL _ TextFind.SearchRope[finder, fullFName].found; rm: TextReplace.RopeMap _ TextReplace.MapNamedSubfieldToMatch[finder, fullFName]; IF NOT found THEN { SIGNAL Warning[IO.PutFR["FileSets Enumeration/directory filter bug: couldn't match %g against %g", IO.refAny[matchPattern], IO.refAny[fullFName]]]; RETURN; }; FOR cl: LOR _ checks, cl.rest WHILE cl # NIL DO IF rm.Apply[cl.first].Find[">"] # -1 THEN RETURN; ENDLOOP; }; Consume[fullFName, created, primaryVolume, backupVolume]; }; FOR i: INT _ fsPattern.Find["#"], fsPattern.Find["#"] WHILE i >= 0 DO filteringNeeded _ TRUE; fsPattern _ fsPattern.Substr[len: i].Cat["*", fsPattern.Substr[start: i+1]]; ENDLOOP; IF filteringNeeded THEN { IF matchPattern.Find["/"] # -1 THEN ERROR Error[IO.PutFR["FileSets can't enumerate a pattern in slashy format with a # in it (%g)", IO.refAny[matchPattern]]]; SELECT matchPattern.Fetch[0] FROM '[, '< => NULL; IN ['A .. 'Z], IN ['a .. 'z], IN ['0 .. '9], '., '!, '*, '# => { cwd: ROPE _ GetCWD[]; matchPattern _ cwd.Cat[matchPattern]; fsPattern _ cwd.Cat[fsPattern]; }; ENDCASE => ERROR Error[IO.PutFR["FileSets thinks file name pattern %g is bogus", IO.refAny[matchPattern]]]; matchPattern _ TextReplace.Apply[quoteMetas, matchPattern]; matchPattern _ matchPattern.Substr[len: Rope.Index[s1: matchPattern, s2: "!"]].Concat["!*"]; FOR i: INT _ matchPattern.Find["#"], matchPattern.Find["#"] WHILE i >= 0 DO checks _ CONS[IO.PutFR["ck%g", IO.int[nChecks _ nChecks + 1]], checks]; matchPattern _ matchPattern.Substr[len: i].Cat["<", checks.first, ":*>", matchPattern.Substr[start: i+1]]; ENDLOOP; finder _ TextFind.CreateFromRope[pattern: matchPattern, ignoreCase: TRUE]; }; Enumerate[fsPattern, NoteFile]; }; quoteMetas: TextReplace.RopeMap _ TextReplace.RopeMapFromPairs[LIST[["<", "'<", TRUE], [">", "'>", TRUE]]]; GetCWD: PROC RETURNS [cwd: ROPE--in brackety format--] = { cwd _ FS.ExpandName["x!3", CommandTool.CurrentWorkingDirectory[]].fullFName; cwd _ cwd.Substr[len: cwd.Length[]-3]; }; FromDF: PUBLIC PROC [dfName: ROPE, date: DFUtilities.Date _ [], filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[fs, fn]}; fs _ NewFileSet[ids]; EnumDF[dfName, date, Add, filter, which, ids]; fs.summary _ Convert.RopeFromInt[Size[fs]]; }; FromDFs: PUBLIC PROC [dfs: FileSet, filter: DFUtilities.Filter, which: DFWhich, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] --FileConsumer-- = {[] _ Insert[fs, fn]}; PerDF: PROC [fn: FileNote] --FileConsumer-- = { created: GMT _ GetCreated[fn]; dfDate: DFUtilities.Date _ IF created # noGMT THEN [explicit, created] ELSE []; EnumDF[fn.fsName, dfDate, Add, filter, which, ids]; }; fs _ NewFileSet[ids]; EnumSet[dfs, PerDF]; fs.summary _ Convert.RopeFromInt[Size[fs]].Cat[":(fromDFS ", dfs.summary, ")"]; }; 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] = { Process.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 => CreateNote[fiName, fi.date.gmt, ids], local => CreateNote[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 !FS.Error => { SIGNAL Warning[IO.PutFR["FS.Error[%g, %g]; assumed empty", IO.atom[error.code], IO.rope[error.explanation]]]; file _ FS.nullOpenFile; CONTINUE }]; IF file = FS.nullOpenFile THEN RETURN; from _ FS.StreamFromOpenFile[file]; DFUtilities.ParseFromStream[in: from, proc: PerItem, filter: filter !DFUtilities.SyntaxError => {SIGNAL Warning[IO.PutFR["Syntax error near [%g] in DF-file %g: %g", IO.int[from.GetIndex[]], IO.rope[dfName], IO.rope[reason]]]; CONTINUE}]; from.Close[]; }; MentionedDFs: PUBLIC PROC [dfName: ROPE, filter: DFUtilities.Filter _ [], ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { Add: PROC [fn: FileNote] = {[] _ Insert[fs, fn]}; fs _ NewFileSet[ids]; EnumMentionedDFs[dfName, filter, Add, ids]; fs.summary _ Convert.RopeFromInt[Size[fs]]; }; EnumMentionedDFs: PUBLIC PROC [dfName: ROPE, filter: DFUtilities.Filter _ [], to: FileConsumer, ids: IdentificationScheme _ []] = { ProcessDFFile: PROC [fullDFName: ROPE, filter: DFUtilities.Filter] = { in: IO.STREAM = FS.StreamOpen[fullDFName]; ProcessItem: PROC [item: REF ANY] RETURNS [stop: BOOL _ FALSE] --DFUtilities.ProcessItemProc-- = { WITH item SELECT FROM di: REF DFUtilities.DirectoryItem => NULL; fi: REF DFUtilities.FileItem => NULL; ii: REF DFUtilities.ImportsItem => { fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids]; to[fn]; }; ii: REF DFUtilities.IncludeItem => { fn: FileNote = CreateNote[ii.path1, ii.date.gmt, ids]; to[fn]; ProcessDFFile[ii.path1, filter]; }; ci: REF DFUtilities.CommentItem => NULL; wi: REF DFUtilities.WhiteSpaceItem => NULL; ENDCASE => ERROR; }; DFUtilities.ParseFromStream[in, ProcessItem, filter]; in.Close[]; }; ProcessDFFile[dfName, filter]; }; FromRope: PUBLIC PROC [rope: ROPE, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { s: STREAM _ IO.RIS[rope]; fs _ FromStream[s, ids]; s.Close[]; }; FromFile: PUBLIC PROC [fileName: ROPE, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { s: STREAM _ FS.StreamOpen[fileName]; fs _ FromStream[s, ids]; s.Close[]; }; CRBreak: PROC [char: CHAR] RETURNS [cc: IO.CharClass] --IO.BreakProc-- = { cc _ SELECT char FROM '\n => break, IN ['\000 .. ' ] => sepr, ENDCASE => other; }; FromStream: PUBLIC PROC [from: STREAM, ids: IdentificationScheme _ []] RETURNS [fs: FileSet] = { fs _ NewFileSet[ids]; FOR i: INT _ from.SkipWhitespace[], from.SkipWhitespace[] WHILE NOT from.EndOf[] DO fileName: ROPE _ from.GetTokenRope[CRBreak].token; fn: FileNote; created: GMT _ noGMT; GetToke: PROC RETURNS [toke: ROPE] = {toke _ IF from.EndOf[] THEN "\n" ELSE from.GetTokenRope[CRBreak].token}; FOR toke: ROPE _ GetToke[], GetToke[] UNTIL toke.Equal["\n"] DO SELECT TRUE FROM toke.Equal["Of", FALSE] => { createdRope: ROPE _ from.GetLineRope[]; [created] _ Tempus.Parse[rope: createdRope, search: FALSE]; EXIT; }; ENDCASE => ERROR; ENDLOOP; fn _ CreateNote[fileName, created, ids]; IF fn # NIL THEN [] _ Insert[fs, fn]; ENDLOOP; fs.summary _ Convert.RopeFromInt[Size[fs]]; }; ToRope: PUBLIC PROC [fs: FileSet] RETURNS [rope: ROPE] = { s: IO.STREAM _ IO.ROS[]; ToStream[fs, s]; rope _ s.RopeFromROS[]; }; ToFile: PUBLIC PROC [fs: FileSet, fileName: ROPE] = { s: IO.STREAM _ FS.StreamOpen[fileName, create]; ToStream[fs, s]; s.Close[]; }; ToStream: PUBLIC PROC [fs: FileSet, to: IO.STREAM] = { PerNote: PROC [fn: FileNote] --FileConsumer-- = { created: GMT _ GetCreated[fn]; to.PutRope[fn.fsName]; IF created # noGMT THEN to.PutF[" Of %g", IO.time[created]]; to.PutChar['\n]; }; EnumSet[fs, PerNote]; }; }. ~FileSetsImpl.Mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Last Edited by: Spreitzer, April 7, 1986 5:18:53 pm PST Basics: For syntax errors and such. For syntax errors and such. Informational signal: Couldn't identify. FileNote Operations: FileSet Operations: Primitive Enumerations: make a long name into a short one assumes a valid long name, of course Κ%œ˜code™Kšœ Οmœ1™Kšœ žœ˜Kšœžœ˜-šžœžœžœ˜Kšœ žœ˜'KšœF˜FK˜—K˜—K˜*Kšžœžœžœ˜!šžœžœ˜Kšœ žœ.˜:Kšœžœžœ˜š žœ!žœ žœžœžœž˜HKšœžœ˜š œAžœ žœžœžœžœžœ˜yKšœžœžœ˜(š  œžœžœžœ žœ˜@Kšœžœ&˜-Kšœ-˜-Kšœ/˜/K˜Kšœžœ˜ Kšžœ˜—šœ&˜&Kšžœ žœžœžœ˜1—K˜—Kšœ˜Kšžœ˜—Kšœ˜K˜—Kšžœ ˜&K˜—K˜Kšœ ž‘ œ˜ —™š  œžœžœ"žœ˜Ršœžœ˜Kš œžœ žœ žœžœ˜pK˜ Kšœ žœ˜—K˜K˜š œžœžœžœžœžœžœ‘œ˜IKšœ ˜ —K˜š  œžœ žœžœžœ‘œ˜kKšœžœ˜Kšœžœ˜Kšœ%žœ˜-—K˜š  œžœ žœžœžœ‘œ˜iKšœžœ˜Kšœžœ˜šžœ'žœ žœ˜?šœžœ<ž˜JK˜ K˜ K˜Kšžœžœ˜—Kšœ˜—K˜——K˜š œžœžœ$˜8š žœžœžœžœžœž˜kK˜Kšœ˜Kšžœ˜—K˜ —K˜š œžœžœžœ˜9Kšœžœ˜(—K˜š œžœžœžœ˜HKšœžœ ˜.—K˜š œžœžœžœ˜KKšœ žœ˜%—K˜š œžœžœžœ˜MKšœ-˜-Kš œžœžœžœžœ žœžœ˜6K˜—K˜š  œžœžœžœžœ˜HKšœ"˜"šžœžœž˜!Kšžœ˜Kšžœžœ˜Kšžœžœ˜—K˜—K˜š  œžœžœžœžœ˜>Kšœ!˜!—K˜š  œžœžœ3žœžœžœ˜oš  œžœ˜&Kšœžœ˜*Kšžœ žœ1˜BKšžœ žœ.˜?Kšžœžœžœžœ˜%K˜—š  œžœžœžœžœ˜JKšœžœ˜ Kšœžœ˜K˜Kšœ žœ˜K˜"K˜—K˜Kšœ˜KšœQ˜QK˜—K˜Kš œ(žœ žœ žœ žœ žœ˜jK˜š  œžœžœ/žœ˜`š œžœ˜Kšœ=˜=Jšžœ žœžœ˜,K˜—K˜Kšœ˜KšœS˜SK˜—K˜š œžœžœ=žœ˜tš  œžœ˜&Kšœ žœ˜&Kšœ2˜2Jšžœ žœžœ˜,K˜—K˜Kšœ˜KšœY˜YK˜—K˜šœžœ žœ˜DKšœ˜—K˜š œžœžœžœžœžœžœ˜LKšœžœ˜ Kšœžœ˜Kšžœžœžœ˜$Kšœ žœ˜šœžœ˜Kšœ_˜_K˜.K˜:K˜1K˜.K˜-K˜—K˜—K˜Kšœ žœ˜$K˜š  œžœEžœžœ˜mKš  œžœžœ˜8Kšœžœ˜ Kšœ@žœ˜JK˜ K˜—K˜Kšœžœžœ˜K˜š œžœE  œžœ˜šœžœ ž˜(K˜)K˜*Kšžœžœ˜—š œžœžœžœ˜3šœžœ ž˜K˜*K˜+Kšžœžœ˜—K˜—šœžœ˜%K˜K˜—K˜Kšœžœ˜/Kšžœ žœžœžœ˜;š žœ žœ"žœ"žœ žœžœž˜’Kšžœžœ˜+Kšžœ˜—Kšœ˜K˜—K˜š œžœžœ˜9˜K˜Kšœ žœžœžœ˜UK˜—K˜—K˜š œžœžœ˜:˜K˜Kšœ žœžœžœ˜QK˜—K˜—K˜š  œžœžœžœ˜>šœžœžœž˜5Kšœžœ˜ KšœE˜EKšœ žœ˜Kšžœžœ˜—K˜—K˜š  œžœžœ žœžœžœ˜‚Kšœ žœ˜Kšœžœ˜ Kšžœžœžœžœ3˜Jšžœžœžœž˜*Kš žœžœžœžœžœ˜2Kšœ˜KšœA˜AKšžœ˜—Kšžœžœžœžœ˜KšœB˜BK˜—K˜š  œžœžœ'žœžœ˜Wš  œžœžœ˜DKš œžœ‘œ˜Cš œžœ‘œ˜9Kšœ žœ%˜2Kšžœžœ žœ˜)K˜—K˜ Kšžœžœžœ˜-Kš œžœžœžœžœ˜9K˜—Kšœ)˜)Kšœ˜—K˜š   œžœžœ'žœžœ˜\š  œžœžœ˜Cšžœ ž˜šžœ˜Kš œžœ‘œ˜CK˜Kšœ˜K˜—šžœ˜Kš œžœ‘œ˜Hš œžœ‘œ˜;Kšœ3˜3K˜—K˜ Kš œžœžœžœ žœ˜@K˜——K˜—Kšœ'˜'Kšœ˜—K˜š  œžœžœ'žœžœ˜eš  œžœžœ˜Dšžœ ž˜šžœ˜Kš œžœ‘œ˜CK˜Kšœ˜K˜—šžœ˜š  œžœ‘œ˜7šžœž˜Kšœžœ˜Kšœžœ˜Kšžœžœ˜—Kšœ˜—š œžœ‘œ˜8Kšœ žœžœ˜š  œžœ‘œ˜3K˜Kšœ žœ˜K˜—Kšœ4˜4Kšžœžœ žœ˜)Kšœ˜—K˜ Kš œžœžœžœžœ˜BK˜——K˜—Kšœ)˜)Kšœ˜—K˜š   œžœžœ'žœžœ˜^š œžœžœ˜Kšžœ ž˜šžœ˜Kš œžœ‘œ˜CK˜Kšœ˜K˜—šžœ˜š œžœ‘œ˜9Kšžœžœžœ˜4Kšœ˜—š œžœ‘œ˜:Kšžœžœ#žœ˜CKšœ˜—K˜ Kš œ žœžœžœžœ˜EK˜——K˜—Kšœ4˜4Kšœ˜—K˜šœ žœžœ˜K˜š   œžœžœžœžœžœ˜AKšœžœžœ˜Kš œžœ>žœžœžœ˜mKšžœ ˜K˜—K˜Kšœžœžœ˜1šœžœžœ˜$Kšœ˜Kšœ˜Kšœžœ˜K˜K˜—K˜š   œžœžœEžœ!žœ ‘œ˜ΈKšœžœžœD˜mK˜—K˜š  œžœžœžœžœžœ˜EKšœžœ˜&Kšœ žœ˜š  œžœ˜$Kšžœžœ(žœ˜OK˜—Kšœ žœ˜šœ˜K˜Kšœžœžœžœ˜:K˜K˜K˜K˜—Kšžœ*˜0K˜——™š   œžœžœ žœ žœžœ˜lKšœ žœžœ˜Kšœžœžœ˜Kšœ žœ ˜šžœ ž˜šžœ˜ Kšœžœžœ˜Kšœžœ˜Kš œ*žœCžœžœžœžœ˜ššžœžœžœ˜Kšžœ˜Kšžœžœ˜—Kšœ˜Kšœ˜Kšœžœ˜K˜—šžœ˜ Kšœžœ˜Kšœ žœžœ˜Kšœžœ˜)š žœžœžœ žœžœ˜QKšœžœžœ˜Kš œ*žœCžœžœžœžœ˜ššžœžœžœ˜Kšžœ˜Kšžœžœ˜—Kšœžœ˜)Kšœ˜Kšœžœ˜K˜—Kšœ˜Kšœ,˜,Kšžœžœ žœ˜'K˜—Kšžœžœ˜—Kšœžœz˜‚K˜—K˜š   œžœžœžœ0žœ žœ˜sK˜Kšžœžœ žœ?˜VKšžœžœžœ1˜JKšžœžœžœ žœ3˜NK˜—K˜š  œžœžœ žœ"žœ˜]Kš œžœ‘œ˜BK˜Kšœ!˜!Kšœ+˜+K˜—K˜š  œžœžœ žœ7˜`š  œžœ žœ žœžœ˜UK˜3Kš žœžœžœžœžœžœ˜8Kšžœžœžœ˜K˜—Kšœ.˜.K˜—K˜š  œžœ žœ œ˜8š œžœžœžœ žœžœžœ žœ˜}Kšœžœžœ˜&Kšœ žœ˜—Kšžœ3˜5K˜—K˜š  œžœžœ žœ"žœ˜†Kš œžœ‘œ˜BK˜Kšœ3˜3Kšœ+˜+K˜—K˜š œžœžœ žœ7˜‚Kš  œžœ žœ œ6˜_š  œžœ žœ žœžœ˜UK˜3Kš žœžœžœžœžœžœ˜8Kšžœžœžœ˜K˜—Kšœ.˜.K˜—K˜š œžœ žœ œ˜PKšœžœ˜!Kšœ žœ˜%Kšœžœ˜Kšœžœ%˜1Kšœ žœ˜%KšœL˜LKšžœ žœžœ˜šžœ žœž˜Kšœ*˜*Kšœ ˜ Kšœ˜K˜šžœ˜šžœ˜Kšœ žœ˜+Kšžœžœžœ˜$Kšœ"˜"šžœž˜Kšœ žœ˜Kšœ˜Kšœ žœ˜Kšœžœ˜KšœC˜CKšœ ˜ Kšžœ#žœžœžœ˜Bšžœ žœžœ˜/Kšœžœžœ˜%K˜—Kšžœ˜—K˜—šžœ˜šžœž˜Kšœ žœ˜Kšœ˜Kšœžœ˜KšœC˜CKšœžœžœ˜%Kšž˜—K˜——Kšžœ˜—K˜—K˜š   œžœžœžœžœ˜,Kšœ!™!Kšœ$™$Kšœžœ˜Kšœžœ˜š žœžœž œžœ ž˜%Kšœžœ˜šžœž˜ Kšœžœ˜K˜Kšžœ˜ —Kšžœ˜—Kšžœ ˜&K˜—K˜š œžœžœIžœ˜ŠKš œžœ‘œ˜BK˜Kšœ2˜2Kšœ žœo˜~K˜—K˜š œžœžœ^˜†šžœžœžœž˜*K˜ K˜šžœžœžœž˜$Kšœ žœ˜#šžœžœ˜!KšœF˜FKšžœžœžœ˜K˜—Kšžœ˜—Kšžœ˜—K˜—K˜š  œžœžœžœžœžœ˜IKšœ-žœ'˜WK˜—K˜š  œžœžœ žœ/˜XKšœ žœ ˜Kšœžœ ˜Kšœžœžœ˜Kšœ žœ˜Kšœžœžœ˜Kšœžœ˜š  œžœ žœ žœžœ˜Ušžœžœ˜Kšœžœ0˜;KšœQ˜Qšžœžœžœ˜Kšžœ žœRžœžœ˜“Kšžœ˜K˜—š žœžœžœžœž˜/Kšžœ#žœžœ˜1Kšžœ˜—K˜—Kšœ9˜9K˜—šžœžœ,žœž˜EKšœžœ˜K˜LKšžœ˜—šžœžœ˜Kš žœžœžœžœRžœ˜žšžœž˜!Kšœ žœ˜šžœ žœ žœ ˜@Kšœžœ ˜Kšœ%˜%Kšœ˜K˜—Kšžœžœžœ8žœ˜k—Kšœ;˜;Kšœ\˜\šžœžœ2žœž˜KKšœ žœžœžœ&˜GKšœj˜jKšžœ˜—KšœDžœ˜JKšœ˜—Kšœ˜K˜—K˜Kšœ?žœ žœžœ˜kK˜š  œžœžœž‘œ˜:KšœžœD˜LK˜&K˜—K˜š  œžœžœ žœkžœ˜₯Kš œžœ‘œ˜BK˜Kšœ.˜.Kšœ+˜+K˜—K˜š œžœžœ\žœ˜‰Kš œžœ‘œ˜Bš œžœ‘œ˜/Kšœ žœ˜Kšœžœžœžœ˜OKšœ3˜3K˜—K˜Kšœ˜KšœO˜OK˜—K˜š œžœžœ žœ€˜‘Kšœžœ˜Kšœ žœžœ˜š œžœžœžœžœžœžœ˜>K˜šžœžœž˜KšœžœG˜Nšœžœ˜!Kš œžœžœžœžœžœžœ˜OKšœžœ˜ Kšœžœ˜K˜ Kšœžœ˜2šžœžœž˜Kšœ/˜/KšœI˜IKšžœžœžœžœ˜$—K˜—šœžœ˜$˜!Kšœ˜Kšœ˜Kšœ žœžœ žœ˜@Jšœ˜Jšœžœžœ žœ ˜6J˜—Kšœ5˜5K˜—KšœžœN˜UKšœžœžœ˜(Kšœžœžœ˜+Kšžœžœ˜—K˜—Kšœžœ žœ˜$Kšœžœ˜ šœžœ˜Kšœ ˜ šœžœ ž˜*K˜Kšœ4˜4Kšžœž˜—šœžœ ˜Kšžœ žœ*žœžœ˜mKšœžœ˜Kšž˜Kšœ˜——Kšžœžœžœžœ˜&Kšœžœ˜#Kš œažœ žœ3žœžœžœžœ˜νK˜ K˜—K˜š   œžœžœ žœCžœ˜ƒKš œžœ(˜1Kšœ˜Kšœ+˜+Kšœ+˜+K˜—K˜š œžœžœ žœX˜ƒš  œžœžœ!˜FKšœžœžœžœ˜*š  œžœžœžœžœžœžœ‘œ˜bšžœžœž˜Kšœžœžœ˜*Kšœžœžœ˜%šœžœ˜$K˜6K˜K˜—šœžœ˜$K˜6K˜Kšœ ˜ K˜—Kšœžœžœ˜(Kšœžœžœ˜+Kšžœžœ˜—K˜—K˜5K˜ K˜—Kšœ˜K˜—K˜š  œžœžœžœ"žœ˜\Kšœžœžœžœ˜Kšœ˜K˜ K˜—K˜š  œžœžœ žœ"žœ˜`Kšœžœžœ˜$Kšœ˜K˜ K˜—K˜š  œžœžœžœžœ ‘œ˜Jšœžœž˜Kšœ ˜ Kšžœ˜Kšžœ ˜—K˜—K˜š   œžœžœžœ"žœ˜`K˜š žœžœ0žœžœž˜SKšœ žœ$˜2K˜ Kšœ žœ ˜š œžœžœžœ˜$Kšœžœžœžœ#˜I—šžœžœžœž˜?šžœžœž˜šœžœ˜Kšœ žœ˜'Kšœ4žœ˜;Kšžœ˜K˜—Kšžœžœ˜—Kšžœ˜—Kšœ(˜(Kšžœžœžœ˜%Kšžœ˜—Kšœ+˜+K˜—K˜š  œžœžœžœžœ˜:Kš œžœžœžœžœ˜K˜K˜K˜—K˜š œžœžœžœ˜5Kšœžœžœžœ˜/K˜K˜ K˜—K˜š  œžœžœžœžœ˜6š œžœ‘œ˜1Kšœ žœ˜K˜Kšžœžœžœ˜