DIRECTORY BTreeDefs: TYPE USING [BTreeHandle, CreateAndInitializeBTree, Desc, Insert, KeyNotFound, Lookup,ReleaseBTree, TestKeys], BTreeSupportDefs: TYPE USING[FileHandle--, SetLength--], BTreeSupportExtraDefs: TYPE USING[CloseFile, OpenFile], Buttons: TYPE USING [Button, ButtonProc, Create], Containers: TYPE USING [ChildXBound, Container, Create], CWF: TYPE USING [SetWriteProcedure, SWF1, SWF2, SWF3, WF0, WF1, WF2, WF3, WF4, WFC, WFCR], DateAndTime: TYPE USING [Parse], Directory: TYPE USING[Error, Lookup, ignore], DFSubr: TYPE USING [AllocateDFSeq, DF, DFSeq, FlattenDF, FreeDFSeq, SortByFileName, StripLongName, TooManyEntries], File: TYPE USING [Capability, nullCapability], FileIO: TYPE USING [minimumStreamBufferParms, Open], FQ: TYPE USING [FileQuery, Result], IFSFile: TYPE USING [CantOpen, Error, UnableToLogin], Inline: TYPE USING [BITOR], IO: TYPE USING [card, Close, CreateDribbleStream, Flush, Handle, PutChar, PutF, PutRope, ResetUserAbort, SetUserAbort, STREAM, string, UserAbort, UserAborted], IOMisc: TYPE USING[AskUser], Labels: TYPE USING [Create, Label, Set], LeafSubr: TYPE USING [PrintLeafAccessFailure, PrintLeafProblem, StopLeaf], LongString: TYPE USING [EquivalentString], Menus: TYPE USING [CreateEntry, CreateMenu, InsertMenuEntry, Menu, MenuProc], MessageWindow: TYPE USING[Append, Blink], Process: TYPE USING [Detach, Priority, priorityBackground, priorityForeground, priorityNormal, SetPriority], ReleaseSupport: TYPE USING [TransferFiles, VerifySufficiency], Rope: TYPE USING[ROPE, Text], RopeInline: TYPE USING[InlineFlatten], UnsafeSTP: TYPE USING [Error], STPSubr: TYPE USING [SetNumberOfConnectTries, StopSTP], Stream: TYPE USING [Delete, Handle], Subr: TYPE USING [AbortMyself, AllocateString, Any, CopyString, debugflg, EndsIn, errorflg, FileError, FreeHugeZone, FreeString, GetLine, LongZone, MakeTTYProcs, NewFile, NewStream, PagesUsedInHugeZone, Prefix, Read, ReadWrite, strcpy, SubStrCopy, SubrInit, SubrStop, TTYProcs], Time: TYPE USING [Current], TypeScript: TYPE USING[Create, Destroy, TS], ViewerClasses: TYPE USING [Viewer], ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerOps: TYPE USING [PaintViewer, SetMenu, SetOpenHeight], ViewerIO: TYPE USING[CreateViewerStreams], ViewerTools: TYPE USING [MakeNewTextViewer, GetContents, SetSelection]; ReleaseImpl: MONITOR IMPORTS BTreeDefs, BTreeSupportExtraDefs, Buttons, Containers, CWF, DateAndTime, DFSubr, Directory, FileIO, FQ, IFSFile, Inline, IO, IOMisc, Labels, LeafSubr, LongString, Menus, MessageWindow, Process, ReleaseSupport, RopeInline, STP: UnsafeSTP, STPSubr, Stream, Subr, Time, TypeScript, ViewerEvents, ViewerOps, ViewerIO, ViewerTools = { LC: TYPE = LONG CARDINAL; -- try to not overflow poor Pass3 LS: TYPE = LONG STRING; -- try to not overflow poor Pass3 ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; MAXALLFILES: CARDINAL = 6000; entryHeight: CARDINAL = 15; entryVSpace: CARDINAL = 10; entryHSpace: CARDINAL = 10; bytesPerIFSPage: CARDINAL = 2048; InitialNumberOfPhase1BTreePages: CARDINAL = 1080; EPhase: TYPE = {One, Two, Three, OneThree}; Global: TYPE = RECORD[ container: Containers.Container _ NIL, typeScript: TypeScript.TS _ NIL, tty: Subr.TTYProcs, -- for typeScript in: STREAM _ NIL, -- to the dribble stream out: STREAM _ NIL, -- to the dribble stream logFile: STREAM _ NIL, -- to the log file fileNameButton: Buttons.Button _ NIL, fileNameViewer: ViewerClasses.Viewer _ NIL, phaseLabel: Labels.Label _ NIL, phaseButton: Buttons.Button _ NIL, priorityLabel: Labels.Label _ NIL, priorityButton: Buttons.Button _ NIL, useOldPhase1FileLabel: Labels.Label _ NIL, useOldPhase1FileButton: Buttons.Button _ NIL, useOldPhase3FileLabel: Labels.Label _ NIL, useOldPhase3FileButton: Buttons.Button _ NIL, updateBTreeLabel: Labels.Label _ NIL, updateBTreeButton: Buttons.Button _ NIL, verboseLabel: Labels.Label _ NIL, verboseButton: Buttons.Button _ NIL, busy: BOOL _ FALSE, phase: EPhase _ One, priority: Process.Priority _ Process.priorityNormal, checkOverwrite: BOOL _ FALSE, verbose: REF BOOL _ NIL, nProbablePages: LC _ 0, nProbableFiles: CARDINAL _ 0, dfseqall: DFSubr.DFSeq _ NIL, useOldPhase1FileCache: BOOL _ TRUE, useOldPhase3FileCache: BOOL _ TRUE, updateBTree: BOOL _ TRUE, oldPhase1FileCacheExists: BOOL _ FALSE, oldestBcdDate: LC _ 0, bTreeCap: File.Capability _ File.nullCapability, bTreeHandle: BTreeDefs.BTreeHandle _ NULL ]; NEXCEPTIONS: CARDINAL = 100; BVal: TYPE = RECORD[ version: CARDINAL _ 0, nIfsPages: CARDINAL _ 0 ]; EXSeq: TYPE = LONG POINTER TO EXSeqRecord; EXSeqRecord: TYPE = RECORD[ size: CARDINAL _ 0, body: SEQUENCE maxsize: CARDINAL OF EXItem ]; EXItem: TYPE = RECORD[ host: LS _ NIL, -- "Indigo" directory: LS _ NIL, -- "Cedar>Pilot" shortname: LS _ NIL -- "FileName.Mesa" ]; g: REF Global; destroyEventRegistration: ViewerEvents.EventRegistration; Go: Menus.MenuProc = TRUSTED { p: PROCESS; IF g.busy THEN { CWF.WF0["Still busy - try later.\n"L]; RETURN; }; p _ FORK ChooseAmong[g.phase]; Process.Detach[p]; }; ChooseAmong: ENTRY PROC [phase: EPhase] = { ENABLE UNWIND => g.busy _ FALSE; topdffilename: LS _ Subr.AllocateString[100]; dfFileRef: Rope.Text; starttime: LC _ Time.Current[]; DoPhase: PROC[phase: EPhase] = { time: LC _ Time.Current[]; Process.SetPriority[g.priority]; SELECT phase FROM One => CWF.WF0["Phase One: Verify consistency of all the DF files:\nStart by reading all DF files.\n"L]; Two => CWF.WF0["Phase Two: Verify sufficiency by invoking VerifyDF on all the DF files:\n"L]; Three => CWF.WF0["Phase Three: Transfer remote files and fix up DF files to match.\n"L]; ENDCASE => ERROR; CWF.WF1["Phase started at %lt.\n"L, @time]; Subr.errorflg _ Subr.debugflg _ FALSE; SELECT phase FROM One => VerifyConsistency[topdffilename]; Two => ReleaseSupport.VerifySufficiency[topdffilename, g.tty, g.out, g.checkOverwrite]; Three => ReleaseSupport.TransferFiles[ topdffilename, g.dfseqall, g.tty, g.in, g.out, g.logFile, g.checkOverwrite, g.useOldPhase3FileCache, g.updateBTree, g.verbose]; ENDCASE => ERROR; TemporaryStop[]; time _ Time.Current[]; CWF.WF1["Phase ended at %lt.\n"L, @time]; }; {ENABLE { UNWIND => {Subr.FreeString[topdffilename]; TemporaryStop[]}; IFSFile.Error, IFSFile.UnableToLogin => { LeafSubr.PrintLeafProblem[reason]; GOTO leave; }; IFSFile.CantOpen => { LeafSubr.PrintLeafAccessFailure[reason]; GOTO leave; }; ABORTED, Subr.AbortMyself, IO.UserAborted => { g.in.ResetUserAbort[]; CWF.WF0["\nRelease Aborted.\n"L]; GOTO leave; }; STP.Error => { CWF.WF0["FTP Error. "L]; IF error ~= NIL THEN CWF.WF2["message: %s, code %u in Stp.Mesa\n"L, error, @code]; Subr.errorflg _ TRUE; GOTO leave; }; }; STPSubr.SetNumberOfConnectTries[64000]; -- infinite number g.in.ResetUserAbort[]; g.busy _ TRUE; [] _ CWF.SetWriteProcedure[ToolTTYProc]; dfFileRef _ RopeInline.InlineFlatten[ViewerTools.GetContents[g.fileNameViewer]]; IF dfFileRef = NIL THEN { CWF.WF0["Error - must specify a df file.\n"L]; GOTO leave; }; IF NOT Subr.Any[LOOPHOLE[dfFileRef], '.] THEN CWF.SWF1[topdffilename, "%s.DF"L, LOOPHOLE[dfFileRef]] ELSE Subr.strcpy[topdffilename, LOOPHOLE[dfFileRef]]; IF NOT Subr.EndsIn[topdffilename, ".df"L] THEN { CWF.WF1["Error - %s must be a DF file name.\n"L, topdffilename]; GOTO leave; }; IF phase = OneThree THEN { DoPhase[One]; CWF.WFCR[]; PrintSeparator[]; DoPhase[Three]; CWF.WFCR[]; } ELSE DoPhase[phase]; EXITS leave => NULL; }; Subr.FreeString[topdffilename]; TemporaryStop[]; starttime _ Time.Current[] - starttime; CWF.WF1["\nTotal elapsed time for ReleaseTool %lr."L,@starttime]; IF Subr.errorflg THEN CWF.WF0["\tErrors logged."L]; CWF.WFCR[]; PrintSeparator[]; g.busy _ FALSE; }; VerifyConsistency: PROC[topdffilename: LS] = { nconflicts, nmissing, nentries, npages: CARDINAL; CardOut[g.out, "Remember: max entries in flat DF files = %d\n", MAXALLFILES]; g.out.PutRope["(Do not Destroy this window. Destroy the upper one only.)\n"]; IF g.useOldPhase1FileCache OR g.updateBTree THEN MakeBTree[]; g.nProbablePages _ 0; g.nProbableFiles _ 0; IF g.dfseqall ~= NIL THEN { hz: UNCOUNTED ZONE _ g.dfseqall.dfzone; FreeDFSeqAll[]; hz _ Subr.FreeHugeZone[hz]; -- we know it is a huge zone }; g.dfseqall _ ReadDF[topdffilename]; IF g.dfseqall.size = 0 THEN { FreeDFSeqAll[]; RETURN; }; npages _ Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; CWF.WF0["Sorting the flattened DF files.\n"L]; DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; Flush[]; VerifySpanningTree[g.dfseqall, topdffilename]; Flush[]; CheckExceptions[g.dfseqall]; Flush[]; CheckReleaseAs[g.dfseqall]; Flush[]; [nmissing, nentries] _ CheckForExistence[g.dfseqall]; CWF.WF0["Sorting the flattened DF files by shortname.\n"L]; DFSubr.SortByFileName[g.dfseqall, 0, g.dfseqall.size-1, TRUE]; Flush[]; CheckBcdDates[g.dfseqall]; Flush[]; nconflicts _ CheckConflicts[g.dfseqall]; SetDFFileCreateTimes[g.dfseqall, topdffilename]; CWF.WF2[ "Summary for Phase 1: %u conflicting versions, %u files/versions not found.\n"L, @nconflicts, @nmissing]; CWF.WF4[ "Total %u entries, %u unique entries\n\t%lu probable pages (2048 byte pgs) in release of %u probable files.\n"L, @g.dfseqall.size, @nentries, @g.nProbablePages, @g.nProbableFiles]; npages _ Subr.PagesUsedInHugeZone[g.dfseqall.dfzone]; CWF.WF1["%u pages used in HugeZone.\n"L, @npages]; Flush[]; }; ReadDF: PROC [topdffilename: LS] RETURNS [dfseq: DFSubr.DFSeq] = { time: LC _ Time.Current[]; dfseq _ DFSubr.AllocateDFSeq[maxEntries: MAXALLFILES, zoneType: huge]; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; DFSubr.FlattenDF[dfseq: dfseq, dffilename: topdffilename, h: g.tty, checkForOverwrite: g.checkOverwrite, setRecorder: TRUE, printStatus: g.verbose^, allowForceReadOnly: FALSE, skipCameFrom: TRUE, tryDollars: FALSE ! DFSubr.TooManyEntries => { CWF.WF0[ "Error - too many entries for the one data structure that holds every DF entry.\n"L]; CWF.WF0["Increase its size and try again.\n"L]; CONTINUE; } ]; time _ Time.Current[] - time; Flush[]; CWF.WF1["Time to read in all the DF files: %lr.\n"L, @time]; }; VerifySpanningTree: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = { df, dfj: DFSubr.DF; natsign, nreleaseas: CARDINAL; out: STREAM _ FileIO.Open["SpanningTree.List$", overwrite]; out.PutRope["Verify spanning tree rule: each DF file has exactly one ReleaseAs clause. (skipping CameFroms)\n"]; FOR i: CARDINAL IN [0 .. dfseq.size) DO dfseq[i].eval _ FALSE; ENDLOOP; FOR i: CARDINAL IN [0 .. dfseq.size) DO j: CARDINAL; df _ @dfseq[i]; IF df.eval OR NOT Subr.EndsIn[df.shortname, ".df"L] OR df.parentCameFrom OR df.cameFrom THEN LOOP; natsign _ nreleaseas _ 0; Flush[]; StrungOut[out, "\nFile %s referenced by:\n", df.shortname]; j _ i; DO IF j >= dfseq.size THEN EXIT; dfj _ @dfseq[j]; IF dfj.shortname.length = df.shortname.length AND LongString.EquivalentString[dfj.shortname, df.shortname] THEN { IF dfj.atsign AND NOT dfj.readonly THEN natsign _ natsign + 1; IF dfj.releaseDirectory ~= NIL AND NOT dfj.cameFrom AND NOT dfj.readonly AND NOT LongString.EquivalentString[dfj.shortname, dfj.recorder] THEN nreleaseas _ nreleaseas + 1; dfj.eval _ TRUE; StrungOut[out, " %s", dfj.recorder]; IF dfj.readonly THEN out.PutRope[" (readonly)"]; out.PutRope[","]; } ELSE EXIT; j _ j + 1; ENDLOOP; out.PutRope["\n"]; IF natsign = 0 AND NOT LongString.EquivalentString[df.shortname, topdffilename] THEN StrungOut[out, "\tWarning - no Includes for %s anywhere.\n", df.shortname]; IF nreleaseas ~= 1 THEN { CardOut[out, "\tError - %d ReleaseAs statements for ", nreleaseas]; StrungOut[out, "%s (not counting self references), exactly one required.\n", df.shortname]; }; ENDLOOP; out.Close[]; }; CheckForExistence: PROC[dfseq: DFSubr.DFSeq] RETURNS[nmissing, nentries: CARDINAL] = { df: DFSubr.DF; inCache: BOOL; nIfsPages, remoteVersion, i: CARDINAL _ 0; remoteCreateTime: LC _ 0; fres: FQ.Result; starttime: LC _ Time.Current[]; nmissing _ nentries _ 0; CWF.WF0["Now make sure all those files are really out there.\n"L]; IF NOT g.oldPhase1FileCacheExists THEN { CWF.WF0["(Running priorityForeground.)\n"L]; Process.SetPriority[Process.priorityForeground]; }; i _ 0; WHILE i < dfseq.size DO df _ @dfseq[i]; IF i > 0 AND ((df.createtime > 0 AND dfseq[i-1].createtime = df.createtime) OR (df.criterion ~= none AND dfseq[i-1].criterion ~= none)) AND LongString.EquivalentString[dfseq[i-1].shortname, df.shortname] AND LongString.EquivalentString[dfseq[i-1].directory, df.directory] AND LongString.EquivalentString[dfseq[i-1].host, df.host] THEN { df.version _ dfseq[i-1].version; i _ i + 1; -- skip this one, go to next; LOOP; }; nentries _ nentries + 1; IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; IF g.verbose^ THEN CWF.WF4[ "Check [%s]<%s>%s in %s ... "L, df.host, df.directory, df.shortname, df.recorder] ELSE CWF.WFC['&]; nIfsPages _ 0; remoteCreateTime _ 0; remoteVersion _ 0; inCache _ FALSE; fres _ notFound; IF g.oldPhase1FileCacheExists AND g.useOldPhase1FileCache AND df.createtime ~= 0 THEN { [inCache, nIfsPages] _ LookupInOldFileCache[df]; fres _ foundCorrectVersion; -- highversion and highdate have no value }; IF NOT inCache THEN [fres, nIfsPages, remoteVersion, remoteCreateTime] _ CheckFile[df]; SELECT fres FROM foundCorrectVersion => { IF g.verbose^ THEN CWF.WF1["ok. %s\n"L, IF inCache THEN "(in cache)"L ELSE ""L]; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { g.nProbableFiles _ g.nProbableFiles + 1; g.nProbablePages _ g.nProbablePages + nIfsPages; }; IF NOT inCache AND g.updateBTree THEN InsertIntoCache[df, nIfsPages]; }; foundWrongVersion => { df.createtime _ remoteCreateTime; df.version _ remoteVersion; }; notFound => nmissing _ nmissing + 1; ENDCASE => ERROR; i _ i + 1; ENDLOOP; IF NOT g.oldPhase1FileCacheExists THEN { Process.SetPriority[g.priority]; CWF.WF0["\n(Priority reset.)\n"L]; }; starttime _ Time.Current[] - starttime; CWF.WF1["Total time for CheckFiles %lr.\n"L,@starttime]; }; LookupInOldFileCache: PROC [df: DFSubr.DF] RETURNS[inCache: BOOL, nIfsPages: CARDINAL] = { bval: BVal _ []; len: CARDINAL; specialPrefix: STRING _ "[Indigo]"L; file, sfn: LS _ NIL; inCache _ FALSE; nIfsPages _ 0; IF df.createtime = 0 OR NOT g.oldPhase1FileCacheExists OR NOT g.useOldPhase1FileCache THEN RETURN; file _ Subr.AllocateString[100]; sfn _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]}; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; len _ BTreeDefs.Lookup[ g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; }; -- of ENABLE UNWIND Subr.FreeString[file]; Subr.FreeString[sfn]; IF len = BTreeDefs.KeyNotFound THEN RETURN; df.version _ bval.version; nIfsPages _ bval.nIfsPages; RETURN[TRUE, nIfsPages]; }; InsertIntoCache: PROC[df: DFSubr.DF, nIfsPages: CARDINAL] = { bval: BVal _ [version: df.version, nIfsPages: nIfsPages]; specialPrefix: STRING _ "[Indigo]"L; sfn: LS _ Subr.AllocateString[100]; file: LS _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[file]; Subr.FreeString[sfn]}; IF df.version = 0 THEN ERROR; CWF.SWF3[file, "[%s]<%s>%s"L, df.host, df.directory, df.shortname]; IF Subr.Prefix[file, specialPrefix] THEN Subr.SubStrCopy[file, file, specialPrefix.length]; CWF.SWF2[sfn, "%lu\000%s"L, @df.createtime, file]; BTreeDefs.Insert[g.bTreeHandle, MakeBTreeDesc[sfn], DESCRIPTOR[@bval, SIZE[BVal]]]; }; -- of ENABLE UNWIND Subr.FreeString[file]; Subr.FreeString[sfn]; }; CheckFile: PROC [df: DFSubr.DF] RETURNS[fres: FQ.Result, nIfsPages, remoteVersion: CARDINAL, remoteCreateTime: LC] = { remoteByteLength: LC; targetFileName: LS _ Subr.AllocateString[100]; {ENABLE UNWIND => {Subr.FreeString[targetFileName]}; nIfsPages _ 0; [fres: fres, remoteVersion: remoteVersion, remoteCreateTime: remoteCreateTime, remoteByteLength: remoteByteLength] _ FQ.FileQuery[df.host, df.directory, df.shortname, df.version, df.createtime, df.criterion = none AND df.createtime = 0, g.tty, targetFileName]; SELECT fres FROM foundCorrectVersion => { IF df.createtime > 0 AND df.version > 0 AND df.version ~= remoteVersion THEN { CWF.WF1["\n(Warning %s: "L, df.shortname]; CWF.WF4["Create date %lt has version !%u, but %s refers to !%u)\n"L, @remoteCreateTime, @remoteVersion, df.recorder, @df.version]; }; df.version _ remoteVersion; nIfsPages _ (remoteByteLength/bytesPerIFSPage) + 2; }; foundWrongVersion => { StrungOut[g.out, "\nError - %s of ", targetFileName]; CardOut[g.out, "%t not found.\n", df.createtime]; }; notFound => { StrungOut[g.out, "\nError - %s not found.\n", targetFileName]; }; ENDCASE => ERROR; IF df.createtime = 0 AND remoteCreateTime > 0 THEN { df.createtime _ remoteCreateTime; df.version _ remoteVersion; }; }; -- of ENABLE UNWIND Subr.FreeString[targetFileName]; }; CheckConflicts: PROC[dfseq: DFSubr.DFSeq] RETURNS[nconflicts: CARDINAL] = { df, dflast: DFSubr.DF; nconflicts _ 0; CWF.WF0["Check consistency of DF files.\n"L]; FOR i: CARDINAL IN [1 .. dfseq.size) DO dflast _ @dfseq[i-1]; df _ @dfseq[i]; IF dflast.createtime ~= df.createtime AND dflast.createtime > 0 AND df.createtime > 0 AND LongString.EquivalentString[dflast.shortname, df.shortname] THEN { Flush[]; CWF.WF2["%s: Conflict- %s "L, dflast.shortname, dflast.recorder]; CWF.WF3["has an entry for [%s]<%s>%s"L, dflast.host, dflast.directory, dflast.shortname]; CWF.WF4[ " dated %lt,\n\tbut %s has an entry for [%s]<%s>"L, @dflast.createtime, df.recorder, df.host, df.directory]; CWF.WF2["%s dated %lt.\n"L, df.shortname, @df.createtime]; nconflicts _ nconflicts + 1; }; ENDLOOP; IF nconflicts > 0 THEN CWF.WF1["Warning - there were %u conflicts.\n"L, @nconflicts] ELSE CWF.WF0["Success - there were no conflicts among the files.\n"L]; }; SetDFFileCreateTimes: PROC[dfseq: DFSubr.DFSeq, topdffilename: LS] = { time: LC _ Time.Current[]; df: DFSubr.DF; FOR i: CARDINAL IN [0..dfseq.size) DO df _ @dfseq[i]; IF df.atsign AND df.releaseDirectory ~= NIL AND NOT df.cameFrom AND NOT df.readonly AND Subr.EndsIn[df.shortname, ".df"L] AND (NOT LongString.EquivalentString[df.shortname, df.recorder] OR LongString.EquivalentString[df.shortname, topdffilename]) THEN { df.createtime _ time; df.version _ 0; time _ time + 1; FOR j: CARDINAL IN [0 .. dfseq.size) DO IF i ~= j AND dfseq[j].shortname.length = df.shortname.length AND LongString.EquivalentString[dfseq[j].shortname, df.shortname] THEN { IF LongString.EquivalentString[dfseq[j].host, df.host] AND LongString.EquivalentString[dfseq[j].directory, df.directory] THEN { dfseq[j].createtime _ df.createtime; dfseq[j].version _ 0; } ELSE CWF.WF4[ "Warning: %s refers to a version of [%s]<%s>%s that is not being released.\n"L, dfseq[j].recorder, dfseq[j].host, dfseq[j].directory, dfseq[j].shortname]; }; ENDLOOP; }; ENDLOOP; }; ParseExceptionList: PROC[filename: LS] RETURNS[exseq: EXSeq] = { sh: Stream.Handle; line: LS _ Subr.AllocateString[100]; host: LS _ Subr.AllocateString[100]; directory: LS _ Subr.AllocateString[100]; shortname: LS _ Subr.AllocateString[100]; longzone: UNCOUNTED ZONE _ Subr.LongZone[]; Cleanup: PROC = { Subr.FreeString[line]; Subr.FreeString[host]; Subr.FreeString[directory]; Subr.FreeString[shortname]; }; exseq _ longzone.NEW[EXSeqRecord[NEXCEPTIONS]]; {ENABLE UNWIND => Cleanup[]; sh _ Subr.NewStream[filename, Subr.Read ! Subr.FileError => { CWF.WF1["Warning - Cannot open %s.\n"L, filename]; GOTO return }]; WHILE Subr.GetLine[sh, line] DO IF line.length = 0 OR Subr.Prefix[line, "//"L] OR Subr.Prefix[line, "--"L] THEN LOOP; [] _ DFSubr.StripLongName[line, host, directory, shortname, FALSE]; IF exseq.size > exseq.maxsize THEN { CWF.WF1["Error - too many exception list entries in %s.\n"L, filename]; EXIT; }; exseq[exseq.size] _ [host: Subr.CopyString[host, longzone], directory: Subr.CopyString[directory, longzone], shortname: IF shortname.length > 0 THEN Subr.CopyString[shortname, longzone] ELSE NIL]; exseq.size _ exseq.size + 1; ENDLOOP; Stream.Delete[sh]; EXITS return => {}; }; -- of ENABLE UNWIND Cleanup[]; }; EXPrefix: PROC[dfHost, dfDir, exHost, exDir: LS] RETURNS[BOOL] = { RETURN[ LongString.EquivalentString[dfHost, exHost] AND Subr.Prefix[dfDir, exDir] AND ( dfDir.length=exDir.length OR (dfDir.length>exDir.length AND dfDir[exDir.length] = '>) ) ]; }; CheckExceptions: PROC[dfseq: DFSubr.DFSeq] = { exseq: EXSeq _ NIL; i, j: CARDINAL; dfcur, dfinner: DFSubr.DF; nrelease, ncamefrom: CARDINAL; skip: BOOL; i _ 0; WHILE i < dfseq.size DO IF g.in.UserAbort[] THEN SIGNAL Subr.AbortMyself; dfcur _ @dfseq[i]; nrelease _ ncamefrom _ 0; j _ i; skip _ FALSE; WHILE j < dfseq.size AND LongString.EquivalentString[dfcur.shortname, dfseq[j].shortname] AND LongString.EquivalentString[dfcur.directory, dfseq[j].directory] AND LongString.EquivalentString[dfcur.host, dfseq[j].host] DO dfinner _ @dfseq[j]; IF dfinner.readonly THEN { IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN CWF.WF3["Warning- %s in %s is ReadOnly but also has a %s statement.\n"L, dfinner.shortname, dfinner.recorder, IF dfinner.cameFrom THEN "CameFrom"L ELSE "ReleaseAs"L]; }; IF dfinner.releaseDirectory ~= NIL AND NOT dfinner.cameFrom THEN { IF LongString.EquivalentString[dfseq[j].directory, dfseq[j].releaseDirectory] AND LongString.EquivalentString[dfseq[j].host, dfseq[j].releaseHost] THEN CWF.WF2["Warning - %s in %s has directory and release directory that are identical.\n"L, dfseq[j].shortname, dfseq[j].recorder]; nrelease _ nrelease + 1; }; IF NOT dfinner.readonly AND dfinner.releaseDirectory = NIL THEN { Flush[]; CWF.WF2["Error - %s in %s is not ReadOnly and has no release directory.\n"L, dfinner.shortname, dfinner.recorder]; skip _ TRUE; }; IF dfinner.cameFrom THEN ncamefrom _ ncamefrom + 1; j _ j + 1; ENDLOOP; IF nrelease = 0 AND NOT skip AND ncamefrom = 0 THEN { ex: BOOL _ FALSE; IF exseq = NIL THEN { CWF.WF0["Reading exception list from 'Release.ExceptionList'.\n"L]; exseq _ ParseExceptionList["Release.ExceptionList"L]; }; FOR i: CARDINAL IN [0 .. exseq.size) DO IF EXPrefix[dfcur.host, dfcur.directory, exseq[i].host, exseq[i].directory] AND (exseq[i].shortname = NIL OR LongString.EquivalentString[dfcur.shortname, exseq[i].shortname]) THEN { ex _ TRUE; EXIT; }; ENDLOOP; IF NOT ex THEN { Flush[]; CWF.WF3[ "Error - no ReleaseAs statements for [%s]<%s>%s.\n\t(referenced in"L, dfcur.host, dfcur.directory, dfcur.shortname]; FOR k: CARDINAL IN [i .. j) DO CWF.WF1[" %s"L, dfseq[k].recorder]; ENDLOOP; CWF.WF0[").\n"L]; }; }; i _ j; ENDLOOP; IF exseq ~= NIL THEN FreeExceptionList[@exseq]; }; CheckReleaseAs: PROC[dfseq: DFSubr.DFSeq] = { destSeq: EXSeq _ ParseExceptionList["Release.Destinations"L]; exceptSeq: EXSeq _ NIL; IF destSeq = NIL OR destSeq.size = 0 THEN { CWF.WF0["No destination list found on 'Release.Destinations'.\n"L]; RETURN; }; FOR i: CARDINAL IN [0 .. dfseq.size) DO df: DFSubr.DF _ @dfseq[i]; IF df.cameFrom THEN { IF df.createtime = 0 THEN CWF.WF3["Warning - %s contains an entry for %s in a CameFrom Directory, but it has no create time for %s.\n"L, df.recorder, df.shortname, df.shortname]; FOR j: CARDINAL IN [0 .. destSeq.size) DO dest: EXItem = destSeq[j]; IF EXPrefix[df.host, df.directory, dest.host, dest.directory] THEN EXIT; REPEAT FINISHED => { IF exceptSeq = NIL THEN exceptSeq _ ParseExceptionList["Release.ExceptionList"L]; FOR k: CARDINAL IN [0 .. exceptSeq.size) DO ex: EXItem = exceptSeq[k]; IF EXPrefix[df.host, df.directory, ex.host, ex.directory] THEN EXIT; REPEAT FINISHED => CWF.WF4[ "CameFrom Warning - %s in %s is a CameFrom, but is on [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.host, df.directory]; ENDLOOP; }; ENDLOOP; }; IF df.releaseDirectory ~= NIL AND NOT df.cameFrom THEN { FOR j: CARDINAL IN [0 .. destSeq.size) DO dest: EXItem = destSeq[j]; IF EXPrefix[df.releaseHost, df.releaseDirectory, dest.host, dest.directory] THEN EXIT; REPEAT FINISHED => CWF.WF4["Warning - %s in %s is released onto [%s]<%s>, not the official release directory.\n"L, df.shortname, df.recorder, df.releaseHost, df.releaseDirectory]; ENDLOOP; IF NOT Subr.EndsIn[df.shortname, ".df"L] AND NOT Subr.EndsIn[df.shortname, ".boot"L] AND Subr.EndsIn[df.releaseDirectory, ">Top"L] THEN CWF.WF2[ "Warning - %s is released onto %s but is not a DF file or a boot file.\n"L, df.shortname, df.releaseDirectory]; }; ENDLOOP; FreeExceptionList[@destSeq]; IF exceptSeq#NIL THEN FreeExceptionList[@exceptSeq]; }; CheckBcdDates: PROC[dfseq: DFSubr.DFSeq] = { f: BOOL _ TRUE; df: DFSubr.DF; FOR i: CARDINAL IN [0 .. dfseq.size) DO df _ @dfseq[i]; IF Subr.EndsIn[df.shortname, ".bcd"L] AND df.createtime < g.oldestBcdDate THEN { IF f THEN { CardOut[ g.out, "These files were created before the earliest Bcd date (%t):\n", g.oldestBcdDate]; f _ FALSE; }; StrungOut[g.out, "\t%s of ", df.shortname]; CardOut[g.out, "%t in", df.createtime]; StrungOut[g.out, "%s\n", df.recorder]; }; ENDLOOP; }; FreeExceptionList: PROC[pexseq: POINTER TO EXSeq] = { FOR i: CARDINAL IN [0 .. pexseq.size) DO Subr.FreeString[pexseq[i].host]; Subr.FreeString[pexseq[i].directory]; Subr.FreeString[pexseq[i].shortname]; ENDLOOP; Subr.LongZone[].FREE[pexseq]; }; PrintSeparator: PROC = { CWF.WF0["===============================\n"L]; }; MakeBTree: PROC = { fileHandle: BTreeSupportDefs.FileHandle; g.oldPhase1FileCacheExists _ TRUE; g.bTreeCap _ Directory.Lookup[ fileName: "ReleaseTool.Phase1BTreeFile$"L, permissions: Directory.ignore ! Directory.Error => { g.oldPhase1FileCacheExists _ FALSE; CONTINUE; }]; IF NOT g.oldPhase1FileCacheExists THEN g.bTreeCap _ Subr.NewFile[ "ReleaseTool.Phase1BTreeFile$"L, Subr.ReadWrite, InitialNumberOfPhase1BTreePages]; fileHandle _ BTreeSupportExtraDefs.OpenFile[g.bTreeCap]; g.bTreeHandle _ BTreeDefs.CreateAndInitializeBTree[ fileH: fileHandle, initializeFile: NOT g.oldPhase1FileCacheExists, isFirstGreaterOrEqual: IsFirstGEQ, areTheyEqual: AreTheyEQ]; }; CleanupBTree: PROC = { IF g.bTreeCap ~= File.nullCapability THEN { BTreeSupportExtraDefs.CloseFile[BTreeDefs.ReleaseBTree[g.bTreeHandle]]; g.bTreeCap _ File.nullCapability; g.oldPhase1FileCacheExists _ FALSE; }; }; MakeBTreeDesc: PROC [s: LS] RETURNS [d: BTreeDefs.Desc] = { RETURN[DESCRIPTOR[LOOPHOLE[s, LONG POINTER], (s.length + 1)/2 + 2]]}; IsFirstGEQ: BTreeDefs.TestKeys = { aS: LS = LOOPHOLE[BASE[a]]; bS: LS = LOOPHOLE[BASE[b]]; FOR i: CARDINAL IN [0..MIN[aS.length, bS.length]) DO aC: CHAR = Inline.BITOR[aS[i], 40B]; bC: CHAR = Inline.BITOR[bS[i], 40B]; SELECT aC FROM > bC => RETURN [TRUE]; < bC => RETURN [FALSE]; ENDCASE; ENDLOOP; RETURN [aS.length >= bS.length] }; AreTheyEQ: BTreeDefs.TestKeys = { aS: LS = LOOPHOLE[BASE[a]]; bS: LS = LOOPHOLE[BASE[b]]; IF aS.length ~= bS.length THEN RETURN [FALSE]; FOR i: CARDINAL IN [0..aS.length) DO IF Inline.BITOR[aS[i], 40B] ~= Inline.BITOR[bS[i], 40B] THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE] }; BuildOuter: PROC = { menu: Menus.Menu _ Menus.CreateMenu[]; g _ NEW[Global _ []]; g.verbose _ NEW[BOOL _ TRUE]; [dt: g.oldestBcdDate] _ DateAndTime.Parse["4-Aug-82 8:00:00 PDT"]; g.container _ Containers.Create[info: [name: "ReleaseTool", iconic: FALSE, scrollable: FALSE]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry[" Go", Go]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Abort", MyAbort]]; Menus.InsertMenuEntry[menu, Menus.CreateEntry["Reset", MyReset]]; ViewerOps.SetMenu[g.container, menu]; BuildUserInput[]; ViewerOps.PaintViewer[g.container, all]; SetupTypescriptPart[]; [g.in, g.out, g.logFile] _ SetUpLogStreams[g.typeScript]; g.tty _ Subr.MakeTTYProcs[g.in, g.out, g.typeScript, MyConfirm]; [] _ CWF.SetWriteProcedure[ToolTTYProc]; destroyEventRegistration _ ViewerEvents.RegisterEventProc[MyDestroy, destroy]; }; BuildUserInput: PROC = { heightSoFar: CARDINAL; l: ViewerClasses.Viewer; CreateButton: PROC[bname, lname: Rope.Text, newLine: BOOL] RETURNS[button: Buttons.Button, label: Labels.Label] = { x: CARDINAL; IF newLine THEN { IF l = NIL THEN heightSoFar _ entryVSpace/2 ELSE heightSoFar _ heightSoFar + entryVSpace + l.wh; x _ 0; } ELSE x _ l.wx + l.ww + entryHSpace; l _ button _ Buttons.Create[ info: [name: bname, parent: g.container, border: FALSE, wx: x, wy: heightSoFar], proc: PushButton]; IF lname ~= NIL THEN l _ label _ Labels.Create[info: [ name: lname, parent: g.container, wx: button.wx + button.ww + entryHSpace, wy: heightSoFar, border: TRUE]]; }; [g.phaseButton, g.phaseLabel] _ CreateButton["Phase:", "OneThree", TRUE]; IF g.phase = One THEN Labels.Set[g.phaseLabel, "One"]; [g.useOldPhase1FileButton, g.useOldPhase1FileLabel] _ CreateButton["UseOldPhase1BTree:", "FALSE", FALSE]; IF g.useOldPhase1FileCache THEN Labels.Set[g.useOldPhase1FileLabel, "TRUE"]; [g.useOldPhase3FileButton, g.useOldPhase3FileLabel] _ CreateButton["UseOldPhase3BTree:", "FALSE", FALSE]; IF g.useOldPhase3FileCache THEN Labels.Set[g.useOldPhase3FileLabel, "TRUE"]; [g.updateBTreeButton, g.updateBTreeLabel] _ CreateButton["UpdateBTrees:", "FALSE", FALSE]; IF g.updateBTree THEN Labels.Set[g.updateBTreeLabel, "TRUE"]; [g.priorityButton, g.priorityLabel] _ CreateButton["Priority:", "Background", TRUE]; IF g.priority = Process.priorityNormal THEN Labels.Set[g.priorityLabel, "Normal"]; [g.verboseButton, g.verboseLabel] _ CreateButton["Verbose:", "FALSE", FALSE]; IF g.verbose^ THEN Labels.Set[g.verboseLabel, "TRUE"]; [g.fileNameButton,] _ CreateButton["DFFileName:", NIL, FALSE]; l _ g.fileNameViewer _ ViewerTools.MakeNewTextViewer[info: [parent: g.container, wx: l.wx+l.ww+entryHSpace, wy: heightSoFar, ww: 150, wh: entryHeight, data: NIL, scrollable: FALSE, border: FALSE], paint: FALSE]; Containers.ChildXBound[g.container, g.fileNameViewer]; heightSoFar _ heightSoFar+entryVSpace+l.wh; ViewerOps.SetOpenHeight[g.container, heightSoFar]; }; SetupTypescriptPart: PROC = { g.typeScript _ TypeScript.Create[info: [name: "Release Tool TypeScript", iconic: FALSE]]; }; PushButton: Buttons.ButtonProc = CHECKED { SELECT NARROW[parent, ViewerClasses.Viewer] FROM g.phaseButton => { phaseString: ARRAY EPhase OF Rope.Text _ ["One", "Two", "Three", "OneThree"]; g.phase _ IF g.phase = OneThree THEN One ELSE SUCC[g.phase]; Labels.Set[g.phaseLabel, phaseString[g.phase]]; }; g.priorityButton => { g.priority _ IF g.priority = Process.priorityNormal THEN Process.priorityBackground ELSE Process.priorityNormal; Labels.Set[ g.priorityLabel, IF g.priority = Process.priorityNormal THEN "Normal" ELSE "Background"]; }; g.fileNameButton => { ViewerTools.SetSelection[g.fileNameViewer, NIL]; }; g.verboseButton => { g.verbose^ _ NOT g.verbose^; Labels.Set[g.verboseLabel, IF g.verbose^ THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase1FileButton => { g.useOldPhase1FileCache _ NOT g.useOldPhase1FileCache; Labels.Set[ g.useOldPhase1FileLabel, IF g.useOldPhase1FileCache THEN "TRUE" ELSE "FALSE"]; }; g.useOldPhase3FileButton => { g.useOldPhase3FileCache _ NOT g.useOldPhase3FileCache; Labels.Set[ g.useOldPhase3FileLabel, IF g.useOldPhase3FileCache THEN "TRUE" ELSE "FALSE"]; }; g.updateBTreeButton => { g.updateBTree _ NOT g.updateBTree; Labels.Set[ g.updateBTreeLabel, IF g.updateBTree THEN "TRUE" ELSE "FALSE"]; }; ENDCASE => ERROR; }; SetUpLogStreams: PROC[ts: TypeScript.TS] RETURNS[in, out, file: STREAM] = { file _ FileIO.Open[ fileName: "ReleaseTool.Log", accessOptions: overwrite, streamBufferParms: FileIO.minimumStreamBufferParms]; [in, out] _ ViewerIO.CreateViewerStreams[viewer: ts, name: "Release Tool TypeScript", editedStream: FALSE]; out _ IO.CreateDribbleStream[out, file]; }; MyConfirm: SAFE PROC [in, out: STREAM, data: REF ANY, msg: ROPE, dch: CHAR] RETURNS [CHAR] = CHECKED { value: ATOM; value _ IOMisc.AskUser[ msg: msg, in: in, out: out, keyList: LIST[$Yes, $No, $All, $Local, $Quit]]; -- order is important SELECT value FROM $All => RETURN['a]; $Local => RETURN['l]; $No => RETURN['n]; $Quit => RETURN['q]; $Yes => RETURN['y]; ENDCASE => ERROR; }; ToolTTYProc: PROC[ch: CHAR] = { g.out.PutChar[ch]; }; Flush: PROC = { g.out.Flush[]; }; StrungOut: PROC [out: STREAM, msg: ROPE, string: LS] = { out.PutF[msg, IO.string[string]]; }; CardOut: PROC [out: STREAM, msg: ROPE, card: LC] = { out.PutF[msg, IO.card[card]]; }; FreeDFSeqAll: PROC = { dfseq: DFSubr.DFSeq _ g.dfseqall; DFSubr.FreeDFSeq[@dfseq]; g.dfseqall _ NIL; }; MyAbort: Menus.MenuProc = CHECKED { g.in.SetUserAbort[]; }; TemporaryStop: PROC = { CleanupBTree[]; STPSubr.StopSTP[]; LeafSubr.StopLeaf[]; Flush[]; }; NullTTYProc: PROC[ch: CHAR] = {}; -- prints nothing MyDestroy: ViewerEvents.EventProc = TRUSTED { p: PROCESS; IF g = NIL OR event ~= destroy OR viewer ~= g.container THEN RETURN; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; [] _ CWF.SetWriteProcedure[NullTTYProc]; -- turn off printing g.busy _ TRUE; CWF.WF0["Destroying ReleaseTool.\n"L]; TemporaryStop[]; -- does flush FreeDFSeqAll[]; Subr.SubrStop[]; p _ FORK DestroyTypeScript[g.typeScript]; -- separate process for monitor lock Process.Detach[p]; g.logFile.Close[]; g^ _ []; g _ NIL; ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy]; }; DestroyTypeScript: PROC[ts: TypeScript.TS] = { TypeScript.Destroy[ts]; -- destroys the typescript window }; MyReset: Menus.MenuProc = TRUSTED { ENABLE UNWIND => g.busy _ FALSE; IF g.busy THEN { MessageWindow.Append[message: "ReleaseTool still busy, try later.", clearFirst: TRUE]; MessageWindow.Blink[]; RETURN; }; g.busy _ TRUE; CWF.WF0["Resetting memory... "L]; TemporaryStop[]; FreeDFSeqAll[]; Subr.SubrStop[]; [] _ Subr.SubrInit[256]; CWF.WF0["ReleaseTool memory reset.\n"L]; PrintSeparator[]; g.busy _ FALSE; }; BuildOuter[]; }. èReleaseImpl.Mesa last edit March 16, 1983 6:54 pm last edit May 23, 1983 9:03 pm, Russ Atkinson (short STRING to LONG STRING) last edit June 27, 1983 3:20 pm, Doug Wyatt (suppress some CameFrom Warnings) last edit July 22, 1983 3:06 pm, Doug Wyatt (UserExec.AskUser => IOMisc.AskUser) Phase Meaning 1 verify consistency of all DF files: make sure there are no version conflicts and all files exist on remote servers 2 verify sufficiency: run VerifyDF on all DF files 3 transfer files to new homes, produce new DF files note that only Phase 2 involves looking at the internals of the files Phase 2 and 3 are in Release23Impl.Mesa number of entries in all df files number of bytes in an IFS page (all page counts are in IFS pages, not Pilot pages) size in pages for btree (used to be 100) all global data in a record to help scoping viewers data fields program data btree data mds usage end of mds usage Phase 1 make btree only if it will be used DANGEROUS: make sure there are no outstanding pointers this sorts with shortname highest precedence sorting must precede CheckExceptions, CheckForExistence, and VerifySpanningTree sorting must precede CheckExceptions and CheckForExistence this step is necessary since phase 3 depends on this being sorted by shortname the nested entries are definitely NOT to be forced to be readonly! must be sorted by shortname this is a df we have not seen before (not nested in a cameFrom DF) invariant: after completion, df.version = 0 means highest version or no such file, otherwise df.version is the correct one dfseq must be sorted check for existence now verify each and every file is out there skip if same create or adjacent entries are for ~= or > fixup for phase 3: might as well get the highest ones note this may screw up consistency checking specialPrefix is removed specialPrefix is removed handles cases where there is a naked entry for phase 3: make up create times for the DF files we will write out dfseq must be sorted if there are no releaseAs statements and no CameFrom statements, then this if ReadOnly everywhere (Remember that the sort is not stable, so we can't simply look at dfcur) also complains if there is a CameFrom without an explicit date look for CameFrom's that are on directories that are not release directories this looks suspicious DKW: don't complain about CameFrom's on ExceptionList directories special case for >Top> directories called after the CheckFiles to look at bcd dates this is called by START code any Bcd before this is assumed to be Cedar 3.2 or earlier this is the set of user buttons this is the typescript part first line second line rule: Rules.Rule; now the line above the typescript rule _ Rules.Create[g.container, 0, heightSoFar, 0, 1]; Containers.ChildXBound[g.container, rule]; heightSoFar _ heightSoFar+entryVSpace; now the typescript g.typeScript _ TypeScript.CreateChild[parent: g.container, x: 0, y: heightSoFar, w: 0, h: 800, border: FALSE]; 800 due to viewers bug Containers.ChildXBound[g.container, g.typeScript]; Containers.ChildYBound[g.container, g.typeScript]; ViewerOps.SetOpenHeight[g.container, heightSoFar + 200]; create separate typescript due to lack of Split command in TypeScript.CreateChild since the file will be flushed frequently, we use the minimum this crappy little routine is here to avoid storage overflow in Pass 3 of the compiler this crappy little routine is here to avoid storage overflow in Pass 3 of the compiler just terminates connections, etc.; does not free memory cannot print anything in this, monitor is locked Ê'˜šœ™Jšœ ™ JšœK™KJšœM™MJšœP™P—J˜šœ ™ šœ&™&Jšœ,™,Jšœ!™!—Jšœ3™3Jšœ4™4—JšœE™EJ˜J˜Jšœ'™'J˜šÏk ˜ šœ œ˜Jšœb˜b—Jšœœœ Ïcœ˜8Jšœœœ˜7Jšœ œœ˜1Jšœ œœ"˜8šœœ˜Jšœ>œœ˜J—Jšœ œœ ˜ Jšœ œœ˜-šœœ˜JšœœN˜`—Jšœœœ˜.Jšœœœ!˜4Jšœœœ˜#Jšœ œœ"˜5Jšœœœœ˜šœœ˜Jšœhœ"˜—Jšœœœ ˜Jšœœœ˜)Jšœ œœ6˜JJšœ œœ˜*Jšœœœ=˜NJšœœœ˜)šœ œ˜JšœX˜X—Jšœœœ$˜>Jšœœœœ˜Jšœ œœ˜&Jšœ œœ ˜Jšœ œœ$˜7Jšœœœ˜$šœœ˜Jšœ†˜†—Jšœœœ ˜Jšœ œœœ˜,Jšœœœ ˜$šœœœ1˜IJ˜—Jšœ œœ(˜=Jšœ œœ˜*Jšœ œœ0˜GJ˜—šœ ˜š˜Jš œ7œ*œœdœd˜Æ—Jšœ˜J˜Jš œœœœž!˜;Jš œœœœž!˜9Jšœœœ˜Jšœœœœ˜J˜Jšœ!™!Jš œœ˜J˜Jšœ œ˜Jšœ œ˜Jšœ œ˜J˜Jšœ™Jšœ3™3Jšœœ˜!J™Jšœ(™(Jšœ!œ˜1J˜Jšœœ˜+J˜Jšœ+™+šœœœ˜Jšœ ™ Jšœ"œ˜'Jšœœœ˜ Jšœž˜&Jšœœœž˜,Jšœœœž˜-Jšœ œœž˜*Jšœ™Jšœ!œ˜%Jšœ'œ˜+Jšœœ˜Jšœœ˜"Jšœœ˜"Jšœ!œ˜%Jšœ&œ˜*Jšœ)œ˜-Jšœ&œ˜*Jšœ)œ˜-Jšœ!œ˜%Jšœ$œ˜(Jšœœ˜!Jšœ œ˜$Jšœ ™ Jšœœœ˜J˜J˜4Jšœœœ˜Jšœ œœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœœ˜#Jšœœœ˜#Jšœ œœ˜šœœœ˜'Jšœœ˜Jšœ ™ —J˜0Jšœ%˜)J˜J˜—Jš œœ˜J˜šœœœ˜Jšœ œ˜Jšœ œ˜J˜J˜—Jš œœœœœ ˜*šœ œœ˜Jšœœ˜Jšœœ œœ˜*J˜—šœœœ˜Jšœœœž ˜Jšœ œœž˜'Jšœ œœž˜&J˜—J˜—šœ ™ Jšœœ˜J˜9Jšœ™J˜—šœœ˜Jšœœ˜ šœœ˜Jšœ#˜&Jšœ˜J˜—Jšœœ˜J˜J˜J˜—šÏn œœœ˜+Jšœœ œ˜ Jšœœ˜-J˜Jšœ œ˜J˜šŸœœ˜ Jšœœ˜J˜ Jšœ˜Jšœœ^˜hJšœœS˜]Jšœ œL˜XJšœœ˜Jšœ(˜+Jšœ œ˜&šœ˜˜J˜!—˜J˜P—˜˜J˜——Jšœœ˜—J˜J˜Jšœ&˜)J˜J˜—šœœ˜ Jšœ6˜<˜)J˜"Jšœ˜ J˜—˜J˜(Jšœ˜ J˜—šœœ˜.J˜Jšœ˜!Jšœ˜ J˜—šœ ˜Jšœ˜šœ œœ˜šœ,˜/J˜——Jšœœ˜Jšœ˜ J˜—J˜J˜Jšœ(ž˜:J˜Jšœ œ˜Jšœœ ˜(J˜Pšœ œœ˜Jšœ+˜.Jšœ˜ J˜—šœœ œ˜(Jšœœ ˜;Jšœœ ˜5—šœœ$œ˜0Jšœ=˜@Jšœ˜ J˜—šœœ˜J˜ Jšœœ˜ J˜J˜Jšœœ˜ J˜—Jšœ˜Jšœ œ˜J˜—Jšœ˜J˜J˜'Jšœ>˜AJšœœœ˜3Jšœœ˜ J˜Jšœ œ˜J˜J˜—šŸœœœ˜.Jšœ™Jšœ(œ˜1J˜Jšœ@ œ˜MJšœN˜NJšœ"™"Jšœœœ ˜=J˜J˜šœœœ˜Jšœ œœ˜'Jšœ6™6J˜Jšœž˜8J˜—J˜#šœœ˜J˜Jšœ˜J˜—J˜5Jšœ/˜2J˜Jšœ+˜.Jšœ,™,Jšœ8œ˜>JšœO™OJ˜J˜.Jšœ:™:J˜J˜J˜J˜J˜J˜5Jšœ8˜;JšœN™NJšœ8œ˜>J˜J˜J˜J˜(J˜0šœ˜JšœP˜PJ˜—šœ˜Jšœp˜pJ˜C—J˜5Jšœ/˜2J˜J˜J˜—šŸœœœœ˜BJšœœ˜Jšœ) œ˜FJšœœœ˜1JšœB™B˜CJšœ2œ˜PJšœœœ˜@˜šœ˜JšœU˜U—Jšœ,˜/Jšœ˜ J˜—J˜—J˜J˜Jšœ9˜˜>J˜—Jšœœ˜—šœœœ˜4Jšœ*™*J˜!J˜J˜—Jšœžœ˜—Jšœ ˜ ˜J˜——šŸœœœ œ˜KJšœœ˜J˜Jšœ*˜-šœœœ˜'J˜J˜Jšœ#˜%Jšœ˜Jšœ˜Jšœ<˜?šœ˜J˜Jšœ>˜AJšœV˜Yšœ˜Jšœ3˜3J˜8—Jšœ7˜:J˜J˜—Jšœ˜—šœ˜šœ˜Jšœ:˜=—š˜Jšœ>˜A——J˜J˜—šŸœœ%œ˜FJšœD™DJšœœ˜Jšœ œ˜šœœœ˜%J˜šœ ˜ Jšœœ˜Jšœœ ˜Jšœœ ˜Jšœ#˜&šœœ8˜@Jšœ:˜<—šœ˜J˜J˜J˜šœœœ˜'šœ˜ Jšœ0˜3Jšœ?œ˜Hšœ4˜6Jšœ>˜Ašœ˜J˜$J˜J˜—šœœ˜ JšœO˜OJ˜6J˜——J˜—Jšœ˜—J˜——Jšœ˜—J˜J˜—šŸœœ œœ˜@J˜Jšœœ˜$Jšœœ˜$Jšœ œ˜)Jšœ œ˜)Jšœ œœ˜+šŸœœ˜Jšœ-˜-Jšœ7˜7J˜—Jšœœ œ˜/šœ˜Jšœ˜˜'˜Jšœ/˜2Jšœ˜ J˜——šœ˜Jšœ˜Jšœ˜Jšœœœ˜&Jšœ<œ˜Cšœœ˜$JšœD˜GJšœ˜J˜—˜;J˜0šœ œœ˜(Jšœ%œœ˜/——J˜Jšœ˜—J˜Jšœ˜Jšœžœ˜—Jšœ ˜ J˜J˜—š Ÿœœœœœ˜Bšœ˜Jšœ+˜+Jšœ˜šœ˜Jšœ˜Jšœœ˜;Jšœ˜—Jšœ˜—J˜—J˜šŸœœ˜.Jšœ™Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜ J˜šœ˜Jšœœœ˜1J˜J˜J˜Jšœœ˜ šœ˜JšœA˜DJšœA˜DJšœ7˜=J˜šœœ˜š œœœœœ˜AšœE˜HJ˜%Jšœœ œ˜8——J˜—š œœœœœ˜BJšœK˜MšœBœ˜JšœU˜XJ˜'——J˜J˜—š œœœœœ˜AJ˜šœI˜LJ˜%—Jšœœ˜ J˜—šœ˜J˜—J˜ Jšœ˜—Jšœª™ªš œœœœœ˜5Jšœœœ˜šœ œœ˜Jšœ@˜CJ˜5J˜—šœœœ˜'JšœI˜Kšœœ˜JšœB˜Dšœ˜Jšœœ˜ Jšœ˜J˜——Jšœ˜—šœœœ˜J˜šœ˜JšœF˜FJ˜.—šœœœ ˜Jšœ ˜#Jšœ˜—Jšœ˜J˜—J˜—J˜Jšœ˜—Jšœ œœ˜/J˜J˜—šŸœœ˜-Jšœ>™>J˜=Jšœœ˜šœ œœœ˜+Jšœ@˜CJšœ˜J˜—šœœœ˜'Jšœ œ ˜šœ œ˜šœœ˜šœk˜nJ˜)——JšœL™LJšœ™šœœœ˜)Jšœ˜Jšœ<œœ˜HJš˜šœ˜ J™AJšœ œœ:˜Qšœœœ˜+Jšœ˜Jšœ8œœ˜DJš˜šœœ˜Jšœ›˜›—Jšœ˜—J˜—Jšœ˜—J˜—šœ˜Jšœœ ˜šœ˜šœœœ˜)Jšœ˜JšœJœœ˜VJš˜šœœ\˜kJ˜@—Jšœ˜—Jšœ"™"šœœ"˜(Jšœœ$˜+šœ+œ˜3šœ˜JšœK˜KJ˜#———J˜——Jšœ˜—J˜Jšœ œœ˜4J˜J˜—šŸ œœ˜,Jšœ0™0Jšœœœ˜Jšœ œ˜šœœœ˜'J˜Jšœ$˜&šœ!œ˜*šœœ˜ šœ˜Jšœ˜J˜@Jšœ˜—Jšœœ˜ J˜—Jšœ+˜+Jšœ'˜'Jšœ&˜&J˜—Jšœ˜—J˜J˜—šŸœœ œœ ˜5šœœœ˜(J˜ J˜%J˜%Jšœ˜—Jšœœ ˜J˜J˜—šŸœœ˜Jšœ+˜.J˜J˜—šŸ œœ˜J˜(Jšœœ˜"˜J˜H˜Jšœœ˜#Jšœ˜ J˜——šœœœ˜'˜J˜ J˜J˜!——J˜8˜3J˜Jšœœ˜/Jšœ"˜"J˜—J˜J˜—šŸ œœ˜šœ#œ˜+J˜GJ˜!Jšœœ˜#J˜—J˜J˜—šŸ œœœœ˜;Jš œ œœœœ˜EJ˜—šœ"˜"Jšœœœœ˜Jšœœœœ˜š œœœœ˜4Jšœœ œ ˜$Jšœœ œ ˜$šœ˜Jšœœœ˜Jšœœœ˜Jšœ˜—Jšœ˜—Jšœ˜Jšœ˜J˜—šœ!˜!Jšœœœœ˜Jšœœœœ˜Jšœœœœ˜.šœœœ˜$Jš œœœ œœœ˜LJšœ˜—Jšœœ˜ Jšœ˜J˜—šŸ œœ˜Jšœ™J˜&Jšœœ˜Jšœ œœœ˜Jšœ9™9J˜BJšœDœœ˜_J˜AJ˜BJ˜BJ˜%Jšœ™J˜J˜(Jšœ™J˜J˜9J˜@Jšœœ ˜(J˜NJ˜J˜—šŸœœ˜Jšœ œ˜J˜J˜šŸ œœ#œ˜;Jšœ1˜8Jšœœ˜ šœ˜ šœ˜šœ˜ Jšœ˜ Jšœ0˜4—J˜J˜—Jšœ˜#—˜Jšœ1œ˜PJšœ˜—šœ œ˜˜!Jšœdœ˜k—J˜J˜——Jšœ ™ JšœCœ˜IJšœœ!˜6˜5Jšœ,œ˜3—Jšœœ-˜L˜5Jšœ,œ˜3—Jšœœ-˜L˜+Jšœ'œ˜.—Jšœœ(˜=šœ ™ JšœNœ˜TJšœ%œ'˜RJšœFœ˜MJšœ œ$˜6Jšœ2œœ˜>˜PJ˜FJš œœœ œ œ˜<—J˜6J˜+J˜2—˜J˜——šŸœœ˜Jšœ™šœ!™!Jšœ7™7Jšœ*™*Jšœ&™&Jšœ™šœ:™:JšœJ™J—Jšœ2™2Jšœ2™2Jšœ8™8J™—JšœQ™QJšœQœ˜Y˜J˜——šœ!œ˜*šœœ˜0˜Jšœ œœ1˜MJš œ œœœœ ˜