DIRECTORY BasicTime, Commander, DFOperations, DFUtilities, FS, IO, MessageWindow, Process, ReleaseToolVerify, Rope, SymTab, VersionMap, ViewerIO; ReleaseToolDriver: CEDAR PROGRAM IMPORTS BasicTime, Commander, DFUtilities, FS, IO, MessageWindow, Process, ReleaseToolVerify, Rope, SymTab, VersionMap, ViewerIO SHARES VersionMap = 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; Map: TYPE = VersionMap.Map; MapList: TYPE = VersionMap.MapList; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; WhiteSpaceItem: TYPE = DFUtilities.WhiteSpaceItem; ReleaseToolCommand: Commander.CommandProc = { EachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM incl: REF DFUtilities.IncludeItem => { name: ROPE = incl.path1; IO.PutRope[outV, "\n\nVerifyDF of "]; IO.PutRope[outV, name]; IF Rope.Equal[ShortName[name], shortDfFile, FALSE] THEN { IO.PutRope[outV, "\n not done, short name equals root DF name.\n"]; RETURN; }; IO.PutF[outV, "\n starting at %g\n", [time[BasicTime.Now[]]]]; [] _ ReleaseToolVerify.Verify[ name, bcdCache, sourceMaps, symbolsMaps, Interact, outV, outV]; }; ENDCASE; }; debugging,verifying,moving: BOOL _ FALSE; inV,outV: STREAM _ NIL; inStream: STREAM _ NIL; dfTable: SymTab.Ref _ NIL; dfFile: ROPE _ "CurrentCedar.df"; shortDfFile: ROPE _ dfFile; mapPrefix: ROPE _ "Cedar"; releaseDirectory: ROPE _ NIL; releaseHost: ROPE _ NIL; ris: STREAM = IO.RIS[cmd.commandLine]; sourceMapName,symbolsMapName: ROPE _ NIL; sourceMaps,symbolsMaps: MapList _ 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; $verify => verifying _ TRUE; $move => moving _ TRUE; ENDCASE; list _ list.rest; ENDLOOP; }; ENDCASE; dfFile _ DefaultExtension[ IO.GetTokenRope[ris, IO.IDProc ! IO.EndOfStream => CONTINUE].token, ".df"]; shortDfFile _ ShortName[dfFile]; 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; sourceMapName _ DefaultExtension[mapPrefix, "Source.VersionMap"]; sourceMaps _ LIST[VersionMap.RestoreMapFromFile[sourceMapName]]; symbolsMapName _ DefaultExtension[mapPrefix, "Symbols.VersionMap"]; symbolsMaps _ LIST[VersionMap.RestoreMapFromFile[symbolsMapName]]; inStream _ FS.StreamOpen[dfFile]; [inV,outV] _ ViewerIO.CreateViewerStreams[ "ReleaseTool.log", NIL, "ReleaseTool.log", FALSE]; {ENABLE UNWIND => { TimedMessage["\n\n**** Aborting at %g ****\n"]; Cleanup[]; }; IF verifying THEN { TimedMessage["\n\nVerification Phase of ReleaseTool starting at %g\n"]; bcdCache _ ReleaseToolVerify.CreateBcdCache[80]; DFUtilities.ParseFromStream[inStream, EachItem]; TimedMessage["\n\nVerification Phase of ReleaseTool ending at %g\n"]; IO.Close[inStream]; inStream _ NIL; ReleaseToolVerify.FlushBcdCache[bcdCache]; bcdCache _ NIL; }; IF moving THEN { TimedMessage["\n\nFile Moving Phase of ReleaseTool starting at %g\n"]; dfTable _ BuildDFTable[dfFile, outV, releaseHost, releaseDirectory]; TimedMessage["\nDF table built at %g\n"]; MoveFiles[dfTable, outV, releaseHost, releaseDirectory, debugging]; TimedMessage["\n\nFile Moving Phase of ReleaseTool 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; DFTableEntryRep: TYPE = RECORD [ tempName: ROPE, destName: ROPE, time: BasicTime.GMT ]; BuildDFTable: PROC [topDF: ROPE, log: STREAM, host,dir: ROPE] RETURNS [tab: SymTab.Ref] = { now: BasicTime.GMT = BasicTime.Now[]; index: INT _ 0; EachDF: PROC [dfName: ROPE, dfDate: Date _ []] = { EachDFPass[dfName, dfDate, TRUE]; EachDFPass[dfName, dfDate, FALSE]; }; EachDFPass: PROC [dfName: ROPE, dfDate: Date _ [], selfReferenceOnly: BOOL] = { inStream: STREAM _ NIL; currentDir: REF DirectoryItem _ NIL; EachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM dirItem: REF DirectoryItem => { currentDir _ dirItem; }; fileItem: REF FileItem => { SELECT TRUE FROM NOT selfReferenceOnly => RETURN; currentDir = NIL => IO.PutF[log, "\n**** NIL directory: %g", [rope[dfName]]]; currentDir.path2 = NIL => IO.PutF[log, "\n**** no ReleaseAs clause for: %g", [rope[dfName]]]; currentDir.path2IsCameFrom => { IO.PutF[log, "\n**** CameFrom clause for : %g", [rope[dfName]]]; }; ENDCASE => { destName: ROPE _ Rope.Concat[ ReplaceHostAndDir[currentDir.path2, host, dir], RemoveVersion[fileItem.name]]; entry.destName _ destName; }; stop _ selfReferenceOnly; }; inclItem: REF IncludeItem => { IF selfReferenceOnly THEN RETURN; 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 => { IF selfReferenceOnly THEN RETURN; entry _ e; }; ENDCASE => { entry _ NEW[DFTableEntryRep _ [ tempName: IO.PutFR["Temp.%g.df", [integer[index]]], destName: ReplaceHostAndDir[dfName, host, dir], time: BasicTime.Update[now, index] ]]; IO.PutF[log, "\nDF table entry for: %g", [rope[dfName]]]; index _ index + 1; [] _ SymTab.Store[tab, key, entry]; }; {ENABLE UNWIND => Cleanup[]; filter: Filter _ [filterA: source, filterB: public, filterC: defining]; IF selfReferenceOnly THEN { using: REF DFUtilities.UsingList _ NEW[DFUtilities.UsingList[1]]; using[0] _ [name: ShortName[dfName]]; using.nEntries _ 1; filter _ [filterA: source, filterB: all, filterC: defining, list: using]; }; 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]; }; ReplaceHostAndDir: PROC [name,host,dir: ROPE] RETURNS [dest: ROPE] = { rbPos: INT = Rope.SkipTo[dest _ name, 1, "]"]; laPos: INT = Rope.SkipTo[dest, rbPos+1, "<"]; raPos: INT = Rope.SkipTo[dest, laPos+1, ">"]; IF dir # NIL THEN dest _ Rope.Replace[name, laPos+1, raPos-laPos-1, dir]; IF host # NIL THEN dest _ Rope.Replace[dest, 1, rbPos-1, host]; dest _ RemoveVersion[dest]; }; 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]]; }; 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, log: STREAM, host, dir: ROPE, debugging: BOOL] = { nonDfAction: SymTab.EachPairAction = { quit _ FALSE; totalCount _ MoveDFContents[key, table, log, host, dir, totalCount, debugging]; }; dfAction: SymTab.EachPairAction = { quit _ FALSE; totalCount _ MoveDFTemp[key, val, log, totalCount, debugging]; }; totalCount: INT _ 0; [] _ SymTab.Pairs[table, nonDfAction]; [] _ SymTab.Pairs[table, dfAction]; }; MoveDFContents: PROC [dfName: ROPE, table: SymTab.Ref, log: STREAM, host,dir: ROPE, count: INT, debugging: BOOL] RETURNS [INT _ 0] = { entry: DFTableEntry _ NIL; inStream,outStream: STREAM _ NIL; currentDirItem: REF DirectoryItem; currentSourcePrefix: ROPE _ NIL; currentDestPrefix: ROPE _ NIL; EachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM dirItem: REF DirectoryItem => { IF NOT dirItem.path2IsCameFrom AND dirItem.path2 # NIL THEN { currentSourcePrefix _ dirItem.path1; currentDestPrefix _ ReplaceHostAndDir[dirItem.path2, host, dir]; dirItem.path1 _ currentDestPrefix; dirItem.path2 _ currentSourcePrefix; dirItem.path2IsCameFrom _ TRUE; }; currentDirItem _ dirItem; }; file: REF FileItem => { date: Date _ file.date; sourceName: ROPE _ Rope.Concat[currentSourcePrefix, file.name]; destName: ROPE _ Rope.Concat[currentDestPrefix, RemoveVersion[file.name]]; destExists: BOOL _ FALSE; realName,tempName: ROPE _ NIL; [realName, date] _ FindNonDF[table, sourceName, file.date]; IF realName = NIL THEN { [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; GO TO skip1; }; file.date _ date; IO.PutF[log, "\nCopy %g\n to ", [rope[sourceName _ realName]]]; IF debugging THEN IO.PutF[log, "%g\n (not copied, debugging)", [rope[destName]]] ELSE { ENABLE FS.Error => IF error.group # bug THEN { IO.PutF[log, "\n**** Copy failed: %g", [rope[error.explanation]]]; GO TO skip1 }; gmt: BasicTime.GMT; realName _ NIL; [fullFName: realName, created: gmt] _ FS.FileInfo[destName ! FS.Error => IF error.group # bug THEN CONTINUE]; IF realName # NIL AND gmt = date.gmt THEN { destName _ realName; IO.PutRope[log, destName]; IO.PutRope[log, "\n (already there)"]; } ELSE { tempName: ROPE _ "ReleaseTool.Temp"; IF date.gmt # BasicTime.nullGMT AND ReleaseToolVerify.IsInFileCache[sourceName, date] THEN { tempName _ sourceName; } ELSE { tempName _ FS.Copy[from: sourceName, to: tempName]; }; destName _ FS.Copy[from: tempName, to: destName]; IO.PutRope[log, destName]; date.format _ explicit; date.gmt _ gmt; }; file.name _ Rope.Substr[destName, Rope.Length[currentDestPrefix]]; }; MessageWindow.Append[ IO.PutFR[" %g files moved.", [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 _ imports.path1; imports.path1 _ destName; imports.date _ destDate; }; }; include: REF IncludeItem => { IF NOT include.path2IsCameFrom OR include.path2 = NIL THEN { 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 _ TRUE; include.path2 _ include.path1; include.path1 _ destName; include.date _ destDate; }; }; }; ENDCASE; DFUtilities.WriteItemToStream[outStream, 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; }]; IO.Close[inStream]; FS.SetByteCountAndCreatedTime[FS.OpenFileFromStream[outStream], -1, entry.time]; IO.Close[outStream]; RETURN [count]; EXITS quit => {}; }; MoveDFTemp: PROC [name: ROPE, val: REF, log: STREAM, count: INT, debugging: BOOL] RETURNS [INT] = { WITH val SELECT FROM entry: DFTableEntry => { IO.PutF[log, "\nCopy %g (%g)\n to %g", [rope[name]], [rope[entry.tempName]], [rope[entry.destName]]]; IF debugging THEN IO.PutRope[log, "\n (not copied, debugging)"] ELSE [] _ FS.Copy[from: entry.tempName, to: 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]; }; ENDCASE; RETURN [count]; }; Commander.Register[ "TestMovingPhase", ReleaseToolCommand, "tests the file moving phase of the ReleaseTool without moving files. The command line has, in order (all optional): ", LIST[$debug, $move]]; Commander.Register[ "VerifyRelease", ReleaseToolCommand, "verifies the files in a release. We assume that the version maps are valid. The command line has, in order (all optional): ", LIST[$verify]]; Commander.Register[ "MoveRelease", ReleaseToolCommand, "moves the files in a release. 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. `ReleaseToolDriver.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Russ Atkinson, March 12, 1985 3:27:09 pm PST [item: REF ANY] RETURNS [stop: BOOL _ FALSE] 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 fileter we can use to just get the Includes clauses We are ONLY looking for the self-reference, so make the list say that. In this case, the file is a DF file in our table, so we return NIL to indicate that it should NOT be moved! [key: Key, val: Val] RETURNS [quit: BOOL] [key: Key, val: Val] RETURNS [quit: BOOL] 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, realName is the real long name of the sourceFile, and date is the real explicit date of the source file. First, check to see if the file needs moving at all (based on the create date). No move needed, since it it already there! Move the file from the old place to the new place. 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! This is an imported file NOT being released. This is an imported file that is being released. This is an included file NOT being released. This is an included file that is being released. ÊÀ˜codešœ™Kšœ Ïmœ1™˜@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šœ$˜$Kšœ@˜@Kšœ"˜"Kšœ$˜$Kšœžœ˜Kšœ˜—Kšœ˜K˜—šœžœ˜Kšœ˜Kšœ žœ/˜?Kšœ žœ<˜JKšœ žœžœ˜Kšœžœžœ˜Kšœ;˜;šžœ žœžœ˜Kšœ†™†KšœA˜Ašžœžœžœžœ˜1Kšžœ<˜>Kšžœžœ˜ K˜—šžœ.˜0Kšœ8˜8—K˜Kšžœžœ˜ K˜K˜—Kšœw™wK˜Kšžœ@˜Bšžœ ˜ Kšžœžœ?˜Fšžœ˜šžœžœ ˜šžœžœ˜Kšžœ@˜BKšžœžœ˜ K˜——Kšœžœ˜Kšœ žœ˜K˜KšœO™Ošœ&žœ˜:Kš œžœ žœžœžœ˜2—šžœ žœžœ˜$šžœ˜Kšœ*™*Kšœ˜Kšžœ˜Kšžœ'˜)K˜—šžœ˜Kšœ2™2Kšœ žœ˜$šžœ˜Kšžœ2˜5šžœ˜Kšœ‰™‰Kšœ˜Kšœ˜—šžœ˜Kšœí™íKšœžœ&˜3K˜——Kšœžœ$˜1Kšžœ˜K˜Kšœ˜K˜——KšœB˜BK˜——šœ˜Kšžœ;žœ˜C—Kšžœ ˜K˜—šœ žœ˜Kšœžœ˜K˜KšœP˜Pšžœ ž˜šžœ˜Kšœ,™,KšœA˜Ašžœ ž˜Kšžœžœ>˜EKšžœ5˜9—K˜—šžœ˜Kšœ0™0Kšœ˜Kšœ˜Kšœ˜K˜——K˜—šœ žœ˜š žœžœžœžœžœ˜˜EKšžœ5˜9—K˜—šžœ˜Kšœ0™0Kšœžœ˜Kšœ˜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šžœžœ0˜PKšžœ˜Kšžœ ˜Kšžœ ˜K˜K˜—šŸ œžœžœžœžœ žœ žœžœžœ˜cšžœžœž˜šœ˜šžœ'˜)Kšœ>˜>—šžœ ˜ šž˜Kšžœ.˜0—šžœ˜šœžœ.˜5šœžœ ˜ šžœžœ˜Kšžœ@˜BKšžœ˜ K˜——Kšœ˜———šœ˜Kšžœ;žœ˜C—Kšœ˜—Kšžœ˜—Kšžœ ˜K˜K˜—šœ˜Kšœ˜Kšœ˜Kšœ®˜®Kšžœ˜K˜—šœ˜Kšœ˜Kšœ˜Kšœ¶˜¶Kšžœ ˜K˜—šœ˜Kšœ˜Kšœ˜Kšœš˜šKšžœ ˜ K˜—Kšžœ˜K˜—…—>š\º