DIRECTORY BasicTime, Commander, DefaultRemoteNames, DFOperations, DFUtilities, FS, IO, MessageWindow, Process, ReleaseToolVerify, Rope, SymTab, ViewerIO; UnReleaseToolDriver: CEDAR PROGRAM IMPORTS BasicTime, Commander, DefaultRemoteNames, DFUtilities, FS, IO, MessageWindow, Process, ReleaseToolVerify, Rope, SymTab, ViewerIO = BEGIN CommentItem: TYPE = DFUtilities.CommentItem; Date: TYPE = DFUtilities.Date; DirectoryItem: TYPE = DFUtilities.DirectoryItem; FileItem: TYPE = DFUtilities.FileItem; Filter: TYPE = DFUtilities.Filter; ImportsItem: TYPE = DFUtilities.ImportsItem; IncludeItem: TYPE = DFUtilities.IncludeItem; LORA: TYPE = LIST OF REF ANY; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; WhiteSpaceItem: TYPE = DFUtilities.WhiteSpaceItem; copyAll: BOOL _ FALSE; UnReleaseToolCommand: Commander.CommandProc = { debugging, moving: BOOL _ FALSE; inV,outV: STREAM _ NIL; inStream: STREAM _ NIL; dfTable: SymTab.Ref _ NIL; dfFile: ROPE _ "CurrentCedar.df"; mapPrefix: ROPE _ "Cedar"; releaseDirectory: ROPE _ NIL; releaseHost: ROPE _ NIL; unDir: ROPE _ "[Indigo]"; ris: STREAM = IO.RIS[cmd.commandLine]; sourceMapName,symbolsMapName: ROPE _ NIL; bcdCache: ReleaseToolVerify.BcdCache _ NIL; Cleanup: PROC = { IF bcdCache # NIL THEN {ReleaseToolVerify.FlushBcdCache[bcdCache]; bcdCache _ NIL}; IF inStream # NIL THEN {IO.Close[inStream]; inStream _ NIL}; IF inV # NIL THEN {IO.Close[inV]; inV _ NIL}; IF outV # NIL THEN {IO.Close[outV]; outV _ NIL}; }; TimedMessage: PROC [msg: ROPE] = { IO.PutF[outV, msg, [time[BasicTime.Now[]]]]; }; WITH cmd.procData.clientData SELECT FROM list: LIST OF REF ANY => { WHILE list # NIL DO SELECT list.first FROM $debug => debugging _ TRUE; $move => moving _ TRUE; ENDCASE; list _ list.rest; ENDLOOP; }; ENDCASE; unDir _ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token; dfFile _ DefaultExtension[ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token, ".df"]; mapPrefix _ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token; releaseHost _ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token; releaseDirectory _ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token; inStream _ FS.StreamOpen[dfFile]; [inV,outV] _ ViewerIO.CreateViewerStreams[ "UnReleaseTool.log", NIL, "UnReleaseTool.log", FALSE]; {ENABLE UNWIND => { TimedMessage["\n\n**** Aborting at %g ****\n"]; Cleanup[]; }; IF moving THEN { list: DFTableEntryList _ NIL; TimedMessage["\n\nFile Moving Phase of UnReleaseTool starting at %g\n"]; [dfTable, list] _ BuildDFTable[dfFile, outV, unDir]; TimedMessage["\nDF table built at %g\n"]; MoveFiles[dfTable, list, outV, unDir, debugging]; TimedMessage["\n\nFile Moving Phase of UnReleaseTool ending at %g\n"]; }; }; Cleanup[]; }; showInfo: BOOL _ FALSE; Interact: DFOperations.InteractionProc = { out: STREAM = NARROW[clientData]; IF showInfo THEN WITH interaction SELECT FROM info: REF DFOperations.InfoInteraction => { IO.PutRope[out, info.message]; }; dfInfo: REF DFOperations.DFInfoInteraction => { IO.PutRope[out, dfInfo.message]; }; abort: REF DFOperations.AbortInteraction => { Process.CheckForAbort[]; }; ENDCASE; }; DefaultExtension: PROC [name: ROPE, ext: ROPE] RETURNS [ROPE] = { len: INT = Rope.Length[name]; eLen: INT = Rope.Length[ext]; pos, bang, dot: INT _ len; WHILE pos > 0 DO posM: INT = pos-1; SELECT Rope.Fetch[name, posM] FROM '! => bang _ dot _ posM; '. => dot _ posM; '>, '/, '] => EXIT; ENDCASE; pos _ posM; ENDLOOP; IF bang = len AND (bang-dot # eLen OR Rope.Run[name, dot, ext, 0, FALSE] # eLen) THEN name _ Rope.Concat[name, ext]; RETURN [name]; }; DFTableEntry: TYPE = REF DFTableEntryRep; DFTableEntryList: TYPE = LIST OF DFTableEntry; DFTableEntryRep: TYPE = RECORD [ sourceName: ROPE, tempName: ROPE, destName: ROPE, time: BasicTime.GMT ]; BuildDFTable: PROC [topDF: ROPE, log: STREAM, unDir: ROPE _ NIL] RETURNS [tab: SymTab.Ref _ NIL, list: DFTableEntryList _ NIL] = { now: BasicTime.GMT = BasicTime.Now[]; index: INT _ 0; tail: DFTableEntryList _ NIL; EachDF: PROC [dfName: ROPE, dfDate: Date _ []] = { EachDFPass[dfName, dfDate]; }; EachDFPass: PROC [dfName: ROPE, dfDate: Date _ []] = { inStream: STREAM _ NIL; currentDir: REF DirectoryItem _ NIL; EachItem: DFUtilities.ProcessItemProc = { stop _ FALSE; WITH item SELECT FROM dirItem: REF DirectoryItem => { currentDir _ dirItem; }; inclItem: REF IncludeItem => { EachDF[inclItem.path1, inclItem.date]; }; ENDCASE; }; Cleanup: PROC = { IF inStream # NIL THEN {IO.Close[inStream]; inStream _ NIL}; }; entry: DFTableEntry _ NIL; stripped: ROPE = RemoveVersion[dfName]; realName: ROPE _ NIL; key: ROPE _ NIL; [realName, dfDate] _ MakeExplicit[dfName, dfDate]; IF realName = NIL THEN { IO.PutF[log, "\n**** DF file not found: %g", [rope[dfName]]]; GO TO skipIt }; inStream _ FS.StreamOpen[dfName _ realName]; key _ RemoveVersion[dfName]; WITH SymTab.Fetch[tab, key].val SELECT FROM e: DFTableEntry => { entry _ e; }; ENDCASE => { destName: ROPE _ dfName; tempName: ROPE _ Rope.Concat["Temp.", ShortName[dfName]]; IF unDir # NIL THEN { destName _ Rope.Concat[unDir, RemoveRoot[destName]]; }; entry _ NEW[DFTableEntryRep _ [ sourceName: key, tempName: tempName, destName: destName, time: BasicTime.Update[now, index] ]]; IO.PutF[log, "\nDF table entry for: %g", [rope[dfName]]]; IO.PutF[log, "\n (temp: %g, dest: %g)", [rope[tempName]], [rope[destName]]]; index _ index + 1; IF tail = NIL THEN list _ tail _ LIST[entry] ELSE {tail.rest _ LIST[entry]; tail _ tail.rest}; [] _ SymTab.Store[tab, key, entry]; }; {ENABLE UNWIND => Cleanup[]; filter: Filter _ [filterA: source, filterB: public, filterC: defining]; DFUtilities.ParseFromStream[ inStream, EachItem, filter ! DFUtilities.SyntaxError => { IO.PutF[log, "\n**** Syntax Error: %g", [rope[reason]]]; CONTINUE; }]; }; Cleanup[]; EXITS skipIt => {}; }; tab _ SymTab.Create[151, FALSE]; EachDF[topDF]; }; MakeExplicit: PROC [name: ROPE, date: Date] RETURNS [realName: ROPE _ NIL, realDate: Date] = { realDate.format _ explicit; [fullFName: realName, created: realDate.gmt] _ FS.FileInfo[name, date.gmt ! FS.Error => IF error.group # bug THEN CONTINUE]; IF realName = NIL THEN [fullFName: realName, created: realDate.gmt] _ FS.FileInfo[RemoveVersion[name], date.gmt ! FS.Error => IF error.group # bug THEN CONTINUE]; }; RemoveVersion: PROC [name: ROPE] RETURNS [ROPE] = { pos: INT _ Rope.Length[name]; WHILE (pos _ pos - 1) > 0 DO SELECT Rope.Fetch[name, pos] FROM '! => RETURN [Rope.Flatten[name, 0, pos]]; '>, '], '. => EXIT; ENDCASE; ENDLOOP; RETURN [name]; }; ShortName: PROC [name: ROPE] RETURNS [ROPE] = { pos: INT _ Rope.Length[name]; bang: INT _ Rope.Length[name]; WHILE (pos _ pos - 1) > 0 DO SELECT Rope.Fetch[name, pos] FROM '! => bang _ pos; '>, '] => RETURN [Rope.Flatten[name, pos+1, bang-pos-1]]; ENDCASE; ENDLOOP; RETURN [Rope.Flatten[name, 0, bang]]; }; RemoveRoot: PROC [name: ROPE] RETURNS [ROPE] = { pos: INT _ Rope.SkipTo[name _ RemoveVersion[name], 0, ">"]; IF pos = Rope.Length[name] THEN RETURN [name]; RETURN [Rope.Flatten[name, pos+1]]; }; GetPrefix: PROC [fileName: ROPE] RETURNS [ROPE] = { pos: INT _ Rope.Length[fileName]; WHILE pos > 0 DO pos _ pos - 1; SELECT Rope.Fetch[fileName, pos] FROM '], '> => EXIT; ENDCASE; ENDLOOP; IF pos = 0 THEN RETURN [NIL]; RETURN [Rope.Flatten[fileName, 0, pos+1]]; }; FindDFName: PROC [table: SymTab.Ref, name: ROPE, date: Date] RETURNS [destName: ROPE, destDate: Date, tempName: ROPE _ NIL] = { destName _ RemoveVersion[name]; destDate _ date; WITH SymTab.Fetch[table, destName].val SELECT FROM entry: DFTableEntry => { destName _ entry.destName; destDate.format _ explicit; destDate.gmt _ entry.time; tempName _ entry.tempName; }; ENDCASE; }; FindNonDF: PROC [table: SymTab.Ref, name: ROPE, date: Date] RETURNS [realName: ROPE _ NIL, realDate: Date _ []] = { stripped: ROPE _ RemoveVersion[name]; WITH SymTab.Fetch[table, stripped].val SELECT FROM entry: DFTableEntry => { }; ENDCASE => { [realName, realDate] _ MakeExplicit[name, date]; }; }; MoveFiles: PROC [table: SymTab.Ref, list: DFTableEntryList, log: STREAM, unDir: ROPE, debugging: BOOL] = { totalCount: INT _ 0; FOR each: DFTableEntryList _ list, each.rest WHILE each # NIL DO totalCount _ MoveDFContents[each.first.sourceName, table, log, unDir, totalCount, debugging]; ENDLOOP; FOR each: DFTableEntryList _ list, each.rest WHILE each # NIL DO totalCount _ MoveDFTemp[each.first, log, totalCount, debugging]; ENDLOOP; }; MoveDFContents: PROC [dfName: ROPE, table: SymTab.Ref, log: STREAM, unDir: ROPE, count: INT, debugging: BOOL] RETURNS [INT _ 0] = { entry: DFTableEntry _ NIL; inStream,outStream: STREAM _ NIL; currentDirItem: REF DirectoryItem; forcedDirItem: REF DirectoryItem; currentSourcePrefix: ROPE _ NIL; listHead,listTail: LORA _ NIL; lastDirItem: LORA _ NIL; systemPrefix: ROPE = DefaultRemoteNames.Get[].current; systemPrefixLen: INT = Rope.Length[systemPrefix]; movingThisDir: BOOL _ FALSE; AddToList: PROC [item: REF] = { new: LORA _ LIST[item]; IF listTail = NIL THEN listHead _ new ELSE listTail.rest _ new; listTail _ new; WITH item SELECT FROM dirItem: REF DirectoryItem => { IF lastDirItem # NIL THEN { lastDirItem.first _ item; lastDirItem.rest _ NIL; listTail _ lastDirItem; } ELSE lastDirItem _ new; }; file: REF FileItem => { lastDirItem _ NIL }; imports: REF ImportsItem => { lastDirItem _ NIL }; include: REF IncludeItem => { lastDirItem _ NIL }; ENDCASE; IF lastDirItem = NIL THEN FlushList[]; }; FlushList: PROC = { FOR each: LORA _ listHead, each.rest WHILE each # NIL DO IF each # NIL THEN DFUtilities.WriteItemToStream[outStream, each.first]; ENDLOOP; listTail _ NIL; WHILE listHead # NIL DO lag: LORA _ listHead; listHead _ lag.rest; lag.rest _ NIL; ENDLOOP; }; EachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM dirItem: REF DirectoryItem => { currentSourcePrefix _ dirItem.path1; dirItem.path2 _ NIL; dirItem.path2IsCameFrom _ FALSE; IF unDir # NIL THEN { dirItem.path2 _ Rope.Concat[unDir, RemoveRoot[currentSourcePrefix]]; }; currentDirItem _ dirItem; IF copyAll THEN movingThisDir _ TRUE ELSE movingThisDir _ Rope.Run[currentSourcePrefix, 0, systemPrefix, 0, FALSE] # systemPrefixLen; IF movingThisDir THEN { dirItem.path1 _ dirItem.path2; }; }; file: REF FileItem => { date: Date _ file.date; sourceName: ROPE _ Rope.Concat[currentSourcePrefix, file.name]; noVersion: ROPE _ file.name _ RemoveVersion[file.name]; destExists: BOOL _ FALSE; realName,tempName: ROPE _ NIL; [realName, date] _ FindNonDF[table, sourceName, file.date]; IF forcedDirItem # NIL THEN { AddToList[currentDirItem]; forcedDirItem _ NIL; }; IF realName = NIL THEN { destName: ROPE _ NIL; newPrefix: ROPE _ NIL; [destName, date, tempName] _ FindDFName[table, sourceName, date]; IF NOT Rope.Match["*.df", destName, FALSE] THEN { IO.PutF[log, "\n**** File not found: %g", [rope[sourceName]]]; GO TO skip1 }; IO.PutF[log, "\nFuture copy %g (%g)\n to %g", [rope[sourceName]], [rope[tempName]], [rope[destName]]]; file.date _ date; newPrefix _ GetPrefix[destName]; IF newPrefix # NIL THEN { forcedDirItem _ NEW[DirectoryItem _ [ path1: newPrefix, path2: newPrefix, path2IsCameFrom: FALSE, exported: TRUE, readOnly: FALSE]]; AddToList[forcedDirItem]; }; GO TO skip1; }; file.date _ date; IF movingThisDir THEN { newName: ROPE _ Rope.Concat[unDir, RemoveRoot[realName]]; FileCopy[realName, date, newName, log, debugging]; }; MessageWindow.Append[ IO.PutFR[" %g files checked.", [integer[count _ count + 1]]], TRUE]; EXITS skip1 => {}; }; imports: REF ImportsItem => { destName, tempName: ROPE; destDate: Date; [destName, destDate, tempName] _ FindDFName[table, imports.path1, imports.date]; IF tempName = NIL THEN { [destName, destDate] _ MakeExplicit[imports.path1, imports.date]; IF destName = NIL THEN IO.PutF[log, "\n**** File not found: %g", [rope[imports.path1]]] ELSE {imports.path1 _ destName; imports.date _ destDate}; } ELSE { imports.path2 _ NIL; imports.path1 _ destName; imports.date _ [format: notEqual]; }; }; include: REF IncludeItem => { destName, tempName: ROPE; destDate: Date; [destName, destDate, tempName] _ FindDFName[table, include.path1, include.date]; IF tempName = NIL THEN { [destName, destDate] _ MakeExplicit[include.path1, include.date]; IF destName = NIL THEN IO.PutF[log, "\n**** File not found: %g", [rope[include.path1]]] ELSE {include.path1 _ destName; include.date _ destDate}; } ELSE { include.path2IsCameFrom _ FALSE; include.path2 _ destName; include.path1 _ destName; include.date _ [format: notEqual]; }; }; ENDCASE; AddToList[item] }; WITH SymTab.Fetch[table, dfName].val SELECT FROM e: DFTableEntry => entry _ e; ENDCASE => { IO.PutF[log, "\n**** DF file not in table: %g", [rope[dfName]]]; GO TO quit }; inStream _ FS.StreamOpen[dfName ! FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** DF file not found: %g", [rope[error.explanation]]]; GO TO quit }; ]; outStream _ FS.StreamOpen[entry.tempName, create ! FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** temp DF file not opened: %g", [rope[error.explanation]]]; IO.Close[inStream]; GO TO quit }; ]; IO.PutF[log, "\n\nMoving contents of %g (%g)", [rope[dfName]], [rope[entry.tempName]]]; DFUtilities.ParseFromStream[inStream, EachItem, [comments: TRUE] ! DFUtilities.SyntaxError => { IO.PutF[log, "\n**** Syntax Error: %g", [rope[reason]]]; CONTINUE; }]; FlushList[]; IO.Close[inStream]; FS.SetByteCountAndCreatedTime[FS.OpenFileFromStream[outStream], -1, entry.time]; IO.Close[outStream]; RETURN [count]; EXITS quit => {}; }; MoveDFTemp: PROC [entry: DFTableEntry, log: STREAM, count: INT, debugging: BOOL] RETURNS [INT] = { IO.PutF[log, "\nCopy %g (%g)\n to %g", [rope[entry.sourceName]], [rope[entry.tempName]], [rope[entry.destName]]]; IF debugging THEN IO.PutRope[log, "\n (not copied, debugging)"] ELSE [] _ FS.Copy[entry.tempName, entry.destName ! FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** Copy failed: %g", [rope[error.explanation]]]; CONTINUE; }; ]; MessageWindow.Append[ IO.PutFR[" %g files moved.", [integer[count _ count + 1]]], TRUE]; RETURN [count]; }; FileCopy: PROC [sourceName: ROPE, date: Date, destName: ROPE, log: STREAM, debugging: BOOL] = { tempName: ROPE _ "UnReleaseTool.Temp$"; gmt: BasicTime.GMT; realDestName: ROPE _ NIL; [fullFName: realDestName, created: gmt] _ FS.FileInfo[destName, date.gmt ! FS.Error => IF error.group # bug THEN CONTINUE]; IO.PutF[log, "\nCopy %g\n to %g", [rope[sourceName]], [rope[destName]]]; IF realDestName # NIL THEN { IO.PutRope[log, "\n (not copied, already exists)"]; RETURN; }; IF date.gmt # BasicTime.nullGMT AND ReleaseToolVerify.IsInFileCache[sourceName, date] THEN { tempName _ sourceName; IO.PutRope[log, " (from cache) "]; } ELSE { IF NOT debugging THEN tempName _ FS.Copy[sourceName, tempName ! FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** Copy failed: %g", [rope[error.explanation]]]; GO TO bugOut; }; ]; }; IF debugging THEN { IO.PutRope[log, "\n (not copied, debugging)"]; } ELSE { destName _ FS.Copy[tempName, destName ! FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** Copy failed: %g", [rope[error.explanation]]]; GO TO bugOut; }]; }; EXITS bugOut => {}; }; Commander.Register[ "TestUnRelease", UnReleaseToolCommand, "tests the file moving phase of the UnReleaseTool without moving files. The command line has, in order (all optional): ", LIST[$debug, $move]]; Commander.Register[ "UnRelease", UnReleaseToolCommand, "moves the files in a release to the prerelease directory. We assume that the version maps are valid, and that the files have been verified (although no state is retained between verification and moving). The command line has, in order (all optional): ", LIST[$move]]; END. ²UnReleaseToolDriver.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Russ Atkinson, March 12, 1985 3:30:26 pm PST If TRUE, this switch forces all files to be copied, even though it would save space to just point backwards from the DF file. The command line has the following items (all optional): dfFile mapPrefix releaseHost releaseDirectory [interaction: REF, clientData: REF] RETURNS [abort: BOOL _ FALSE, abortMessageForLog: ROPE _ NIL, response: REF _ NIL] Build a table of source DF names and entries. Each entry gets a distinct desired create time, which is bounded by the start time of the procedure and the start time plus N seconds, where N is the number of DF files. First check to see if we have seen this dfName before. This file has a previous entry (put there on the previous pass) Only need to do this stuff if this is the first entry for this file This is the most restrictive filter we can use to just get the Includes clauses. In this case, the file is a DF file in our table, so we return NIL to indicate that it should NOT be moved! This procedure adds a new item to the list we keep. We splice out directory entries that have no files associated with them. This enables us to change our minds about where files will be stored as we encounter the files. Splice out directory items that have no files. Flush all items in the list out to the stream Take apart the list piece by piece to avoid bogus retention! The contents of this directory must be moved to the new release directory, since the source is not from the current release directory. The file requested is either a DF file (which does NOT get moved yet) or it does not exist, in which case we put a message to the log. !! At this point we need to put out a new directory entry if the destination of the DF file is NOT the same as where the current directory indicates. At this point, realName is the real long name of the sourceFile, and date is the real explicit date of the source file. We only move the file if it is not coming from the current release. This lets us gracefully get rid of very elderly directories. This is an imported file NOT being released. This is an imported file that is being unreleased. This is an included file NOT being released. This is an included file that is being unreleased. No need to copy to a temp file, since we already have this file in our cache. Therefore, we just make the tempName the real source name. It pays to copy this file to a temp file first, since that means we don't have to mess up our current file cache with entries that are better flushed. This would not have to happen if FS did the right thing with remote to remote copies! No copy, just reporting Really do the copy Κ‰˜codešœ™Kšœ Οmœ1™Kšžœžœ˜ K˜—šžœ.˜0Kšœ8˜8—K˜K˜Kšœ ˜ šžœ žœžœ˜Kšœ•™•šœžœ˜%Kšœ˜Kšœ˜Kšœžœ˜Kšœ žœ˜Kšœ žœ˜—Kšœ˜K˜—K˜Kšžœžœ˜ K˜K˜—Kšœϊ™ϊK˜šžœžœ˜Kšœ žœ,˜9Kšœ2˜2K˜—šœ˜Kšžœ=žœ˜E—Kšžœ ˜K˜—šœ žœ˜Kšœžœ˜K˜KšœP˜Pšžœ ž˜šžœ˜Kšœ,™,KšœA˜Ašžœ ž˜Kšžœžœ>˜EKšžœ5˜9—K˜—šžœ˜Kšœ2™2Kšœžœ˜Kšœ˜Kšœ"˜"K˜——K˜—šœ žœ˜Kšœžœ˜K˜KšœP˜Pšžœ ž˜šžœ˜Kšœ,™,KšœA˜Ašžœ ž˜Kšžœžœ>˜EKšžœ5˜9—K˜—šžœ˜Kšœ2™2Kšœžœ˜ Kšœ˜Kšœ˜Kšœ"˜"K˜——K˜—Kšžœ˜—Kšœ˜K˜—šžœ!žœž˜0Kšœ˜šžœ˜ Kšžœ>˜@Kšžœžœ˜ K˜——šœ žœ˜šœžœ ˜ šžœžœ˜KšžœF˜HKšžœžœ˜ K˜——K˜—šœ žœ"˜0šœžœ ˜ šžœžœ˜KšžœL˜NKšžœ˜Kšžœžœ˜ Kšœ˜——Kšœ˜—KšžœU˜Wšœ;žœ˜@šœ˜Kšžœ6˜8Kšžœ˜ Kšœ˜——Kšœ ˜ Kšžœ˜Kšžœžœ0˜PKšžœ˜Kšžœ ˜Kšžœ ˜K˜K˜—šŸ œž˜Kš œžœ žœ žœžœžœ˜Qšžœ'˜)KšœJ˜J—šžœ ˜ šž˜Kšžœ.˜0—šžœ˜šœžœ$˜+šœžœ ˜ šžœžœ˜Kšžœ@˜BKšžœ˜ K˜——Kšœ˜———šœ˜Kšžœ;žœ˜C—Kšžœ ˜K˜K˜—šŸœž˜Kš œ žœžœžœ žœ˜PKšœ žœ˜'Kšœžœ˜Kšœžœžœ˜šœ*žœ˜HKš œžœ žœžœžœ˜2—KšžœI˜Kšžœžœžœ˜Kšžœ4˜6Kšžœ˜K˜—šžœ˜Kšžœ2˜5šžœ˜Kšœ‰™‰Kšœ˜Kšžœ ˜"Kšœ˜—šžœ˜Kšœν™νšžœžœ ž˜šœžœ˜'šœžœ ˜ šžœžœ˜Kšžœ@˜BKšžœžœ˜ K˜——Kšœ˜——K˜——šžœ ˜ šžœ˜Kšœ™Kšžœ/˜1K˜—šžœ˜Kšœ™šœ žœ˜%šœžœ ˜ šžœžœ˜Kšžœ@˜BKšžœžœ˜ K˜———K˜——Kšžœ˜K˜K˜—šœ˜Kšœ˜Kšœ˜KšœΈ˜ΈKšžœ˜K˜—šœ˜Kšœ ˜ Kšœ˜KšœΎ˜ΎKšžœ ˜ K˜—Kšžœ˜K˜—…—=Μ`