DIRECTORY AlpineCmds USING [Copy], AlpineFS USING [OpenFile, OpenFileFromStream], Commander USING [Handle, Register, CommandProc], CommandTool USING [FileWithSearchRules], Convert USING [IntFromRope], File USING [Error, Handle, Info, PageCount, Read, wordsPerPage], FS USING [ComponentPositions, Error, OpenFile, nullOpenFile, Close, Create, ExpandName, GetInfo, Open, SetPageCount, StreamFromOpenFile, StreamOpen], FSBackdoor USING [GetFileHandle], IagoOps USING [FileError], IO, RefText USING [line, Fetch, Length, TrustTextAsRope], Rope, UserCredentials USING [Get], VM USING [AddressForPageNumber, Allocate, Free, Interval, PagesForWords, nullInterval, SwapIn, Unpin], WalnutDefs USING [Segment], WalnutKernelDefs USING [LogInfoFromRoot, RootEntry], WalnutRoot USING [EraseDB, Open, Shutdown, StartTransaction], WalnutSchema USING [SetSchemaVersion], WalnutStream USING [logFileInfo, Open, WriteEntry, SetHighWaterMark]; CreateWalnutWorld: CEDAR PROGRAM IMPORTS AlpineCmds, AlpineFS, Commander, CommandTool, Convert, File, FS, FSBackdoor, IagoOps, IO, RefText, Rope, UserCredentials, VM, WalnutRoot, WalnutSchema, WalnutStream = BEGIN STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; defaultServer: ROPE = "Luther"; serverRope: ROPE = "\001ServerAndDirectory\002"; serverLen: NAT = Rope.Length[serverRope]; RootEntry: TYPE = WalnutKernelDefs.RootEntry; LogInfoFromRoot: TYPE = WalnutKernelDefs.LogInfoFromRoot; field1: REF TEXT = NEW[TEXT[RefText.line]]; CheckAndInit: Commander.CommandProc = {DoInit[cmd, TRUE]}; InitWalnutFiles: Commander.CommandProc = {DoInit[cmd, FALSE]}; JustDoFiles: Commander.CommandProc = { rootFile: ROPE; h: IO.STREAM; IF cmd = NIL THEN RETURN; h _ IO.RIS[cmd.commandLine]; rootFile _ h.GetTokenRope[IO.IDProc ! IO.EndOfStream => { rootFile _ NIL; CONTINUE }].token; h.Close[]; IF rootFile = NIL THEN RETURN; [] _ InitFiles[rootFile, cmd]; }; DoInit: PROC[cmd: Commander.Handle, checkDumpFile: BOOL] = { userKey, alpineServer, localName, remoteName: ROPE; userRName: ROPE = UserCredentials.Get[].name; serverAndDirectory: ROPE; h: IO.STREAM; IF cmd = NIL THEN RETURN; h _ IO.RIS[cmd.commandLine]; userKey _ h.GetRopeLiteral[ ! IO.EndOfStream => { userKey _ NIL; CONTINUE }]; IF userKey = NIL THEN userKey _ IO.PutFR["%g's Mail Database", IO.rope[userRName]] ELSE alpineServer _ h.GetTokenRope[IO.IDProc ! IO.EndOfStream => { alpineServer _ NIL; CONTINUE }].token; h.Close[]; IF alpineServer = NIL THEN alpineServer _ defaultServer; serverAndDirectory _ IO.PutFR["[%g.alpine]<%g>", IO.rope[alpineServer], IO.rope[userRName] ]; localName _ IO.PutFR["///Users/%g/Walnut.Root", IO.rope[userRName] ]; IF checkDumpFile THEN { dumpFile: ROPE = IO.PutFR["///Walnut/%g/Walnut.tempLog", IO.rope[userRName] ]; ok: BOOL _ FALSE; handle: File.Handle; openFile: FS.OpenFile; openFile _ FS.Open[dumpFile ! FS.Error => { cmd.out.PutRope[error.explanation]; CONTINUE} ]; IF openFile # FS.nullOpenFile THEN handle _ FSBackdoor.GetFileHandle[openFile]; IF handle = NIL THEN { cmd.out.PutF["\nDump file %g not found.", IO.rope[dumpFile] ]; ok _ Confirm[cmd]; IF ~ok THEN cmd.out.PutRope["Not continuing\n"]; } ELSE ok _ CheckPages[handle, dumpFile, cmd.out]; IF openFile # FS.nullOpenFile THEN FS.Close[openFile]; IF ~ok THEN RETURN; }; IF ~CreateRootFile[cmd, userKey, localName, serverAndDirectory, userRName] THEN RETURN; remoteName _ Rope.Concat[serverAndDirectory, "Walnut.Root"]; AlpineCmds.Copy[to: remoteName, from: localName]; [] _ InitFiles[remoteName, cmd]; }; DoOtherInit: Commander.CommandProc = { userKey, localName, remoteName, baseName: ROPE; serverAndDirectory: ROPE; userRName: ROPE = UserCredentials.Get[].name; h: IO.STREAM; IF cmd = NIL THEN RETURN; h _ IO.RIS[cmd.commandLine]; baseName _ h.GetTokenRope[IO.IDProc ! IO.EndOfStream => CONTINUE].token; IF baseName = NIL THEN { cmd.out.PutRope["No base name for database given - quitting\n"]; RETURN; }; IF baseName.Fetch[0] = '[ THEN serverAndDirectory _ baseName ELSE IF baseName.Fetch[0] = '/ THEN serverAndDirectory _ FS.ExpandName[baseName].fullFName ELSE serverAndDirectory _ IO.PutFR["[%g.alpine]<%g>", IO.rope[defaultServer], IO.rope[baseName]]; userKey _ h.GetRopeLiteral[ ! IO.EndOfStream => CONTINUE]; h.Close[]; IF userKey = NIL THEN userKey _ IO.PutFR["%g's Walnut Database", IO.rope[baseName]]; localName _ IO.PutFR["///Users/%g/Walnut.Root", IO.rope[userRName] ]; IF ~CreateRootFile[cmd, userKey, localName, serverAndDirectory, baseName] THEN RETURN; remoteName _ Rope.Concat[serverAndDirectory, "Walnut.Root"]; AlpineCmds.Copy[to: remoteName, from: localName]; [] _ InitFiles[remoteName, cmd]; }; Confirm: PROC[cmd: Commander.Handle] RETURNS[continue: BOOL] = { char: CHAR; line: ROPE; cmd.err.PutRope["\nType y(CR) or Y(CR) or CR to continue anyway. "]; line _ cmd.in.GetLineRope[]; IF line.Length[] = 0 THEN RETURN[TRUE]; -- only CR typed continue _ (char _ line.Fetch[0]) = 'y OR char = 'Y OR char = '\n; }; CreateRootFile: PROC[ cmd: Commander.Handle, userKey, localName, serverAndDirectory, mailForName: ROPE] RETURNS[BOOL] = { template, rootStream: STREAM; rootOF: FS.OpenFile; templateRoot: ROPE; BEGIN ENABLE UNWIND => { IF template # NIL THEN template.Close[]; IF rootStream # NIL THEN rootStream.Close[]; IF rootOF # FS.nullOpenFile THEN FS.Close[rootOF]; }; templateRoot _ CommandTool.FileWithSearchRules["Template-Walnut", ".root", cmd, FALSE]; IF templateRoot = NIL THEN { cmd.out.PutRope["Couldn't find Template-Walnut.root"]; RETURN[FALSE] }; template _ FS.StreamOpen[templateRoot ! FS.Error => { cmd.out.PutRope[error.explanation]; template _ NIL; CONTINUE}]; IF template = NIL THEN RETURN[FALSE]; rootOF _ FS.Create[name: localName, setKeep: TRUE, keep: 2 ! FS.Error => { cmd.out.PutRope[error.explanation]; rootOF _ FS.nullOpenFile; template.Close[]; CONTINUE}]; IF rootOF = FS.nullOpenFile THEN RETURN[FALSE]; rootStream _ FS.StreamFromOpenFile[openFile: rootOF, accessRights: $write ! FS.Error => { cmd.out.PutRope[error.explanation]; template.Close[]; FS.Close[rootOF]; rootStream _ NIL; CONTINUE}]; IF rootStream = NIL THEN RETURN[FALSE]; DO BEGIN line: REF TEXT; pos: INTEGER; IF template.EndOf[] THEN EXIT; line _ template.GetLine[field1 ! IO.EndOfStream => GOTO done]; IF Rope.Find[RefText.TrustTextAsRope[line], "UserSuppliedKey"] = 0 THEN { rootStream.PutRope[userKey]; rootStream.PutChar['\n]; LOOP }; IF Rope.Find[RefText.TrustTextAsRope[line], "UserRName"] = 0 THEN { rootStream.PutRope[mailForName]; rootStream.PutChar['\n]; LOOP }; IF (pos_ Rope.Find[RefText.TrustTextAsRope[line], serverRope]) = -1 THEN { rootStream.PutText[line]; rootStream.PutChar['\n]; LOOP }; FOR i: NAT IN [0 .. pos) DO rootStream.PutChar[RefText.Fetch[line, i]]; ENDLOOP; rootStream.PutRope[serverAndDirectory]; FOR i: NAT IN [pos+serverLen .. RefText.Length[line]) DO rootStream.PutChar[RefText.Fetch[line, i]]; ENDLOOP; rootStream.PutChar['\n]; EXITS done => EXIT; END; ENDLOOP; template.Close[]; rootStream.Flush[]; rootStream.Close[]; RETURN[TRUE]; END; }; InitFiles: PROC[rootFile: ROPE, cmd: Commander.Handle] RETURNS[BOOL]= { rootEntry: RootEntry; logInfoList: LIST OF LogInfoFromRoot; logInfoCount: INT _ 0; keyValue, mailFor, dbName, newMailLog, readArchiveLog: ROPE; fileFailure: ROPE = "Couldn't initialize file %g\n"; rootStream: STREAM; finished: BOOL _ FALSE; IF rootFile = NIL THEN { cmd.out.PutRope["\nNo file specified\n"]; RETURN[FALSE] }; rootStream _ WalnutStream.Open[name: rootFile, readOnly: TRUE].strm; IF rootStream = NIL THEN { cmd.out.PutF["Can't open rootFile: %g\n", IO.rope[rootFile] ]; -- couldn't open RETURN[FALSE] }; BEGIN ENABLE IO.EndOfStream, IO.Error => GOTO cantParse; rootStream.SetIndex[0]; DO curPos: INT _ rootStream.GetIndex[]; rootEntry_ NextRootEntry[rootStream]; DO IF rootEntry.ident.Equal["Key", FALSE] THEN { keyValue_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["MailFor", FALSE] THEN { mailFor_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["Database", FALSE] THEN { dbName_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["NewMailLog", FALSE] THEN { newMailLog_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["ReadArchiveLog", FALSE] THEN { readArchiveLog_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["LogInfo", FALSE] THEN { logInfoList _ CONS[rootEntry.info, logInfoList]; logInfoCount _ logInfoCount + 1; EXIT }; IF rootEntry.ident.Equal["End", FALSE] THEN { finished_ TRUE; EXIT }; rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; cmd.out.PutF["Unknown entry in RootFile at pos %g\n", IO.int[curPos]]; RETURN[FALSE] ENDLOOP; IF finished THEN EXIT; ENDLOOP; rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; EXITS cantParse => { rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; cmd.out.PutRope["Couldn't parse root file\n"]; RETURN[FALSE] }; END; IF keyValue.Length[] = 0 OR dbName.Length[] = 0 OR mailFor.Length[] = 0 OR newMailLog.Length[] = 0 OR readArchiveLog.Length[] = 0 OR logInfoCount # 2 THEN { cmd.out.PutRope["Incomplete RootFile - missing entries\n"]; RETURN[FALSE] }; BEGIN cp: FS.ComponentPositions; fn: ROPE; rServer, oServer: ROPE; CheckServer: PROC[name: ROPE] RETURNS[ok: BOOL] = { [fn, cp, ] _ FS.ExpandName[name]; oServer _ Rope.Substr[fn, cp.server.start, cp.server.length]; IF ~(ok _ Rope.Equal[rServer, oServer, FALSE]) THEN cmd.out.PutF["\nserver for file %g does not agree with that for %g\n", IO.rope[name], IO.rope[rootFile]]; }; [fn, cp, ] _ FS.ExpandName[rootFile]; rServer _ Rope.Substr[fn, cp.server.start, cp.server.length]; IF ~CheckServer[dbName] THEN RETURN[FALSE]; IF ~CheckServer[newMailLog] THEN RETURN[FALSE]; IF ~CheckServer[readArchiveLog] THEN RETURN[FALSE]; IF ~CheckServer[logInfoList.first.fileName] THEN RETURN[FALSE]; IF ~CheckServer[logInfoList.rest.first.fileName] THEN RETURN[FALSE]; END; cmd.out.PutRope["\n Initializing log files\n"]; IF ~EnsureEmptyFile[cmd, newMailLog, 200] THEN RETURN[FALSE]; IF ~EnsureEmptyFile[cmd, readArchiveLog, 0] THEN RETURN[FALSE]; FOR lil: LIST OF LogInfoFromRoot _ logInfoList, lil.rest UNTIL lil = NIL DO seqNo: ROPE _ lil.first.logSeqNo; fileName: ROPE; IF seqNo.Fetch[0] # 'E THEN { -- write the LogFileInfo entry in the file strm: STREAM; realSeqNo: INT; IF ~EnsureEmptyFile[cmd, fileName_ lil.first.fileName, 200] THEN RETURN[FALSE]; strm _ WalnutStream.Open[fileName].strm; realSeqNo _ Convert.IntFromRope[seqNo]; WalnutStream.logFileInfo.key _ keyValue.ToRefText[]; WalnutStream.logFileInfo.internalFileID _ lil.first.internalFileID; WalnutStream.logFileInfo.logSeqNo _ realSeqNo; []_ WalnutStream.WriteEntry[strm, WalnutStream.logFileInfo]; strm.Close[]; } ELSE IF ~EnsureEmptyFile[cmd, fileName_ lil.first.fileName, 0] THEN RETURN[FALSE]; ENDLOOP; BEGIN inaccessible: BOOL _ FALSE; cmd.out.PutRope[" Initializing segment file\n"]; BEGIN segment: WalnutDefs.Segment; segment _ WalnutRoot.Open[ rootName: rootFile, readOnly: FALSE, newSegmentOk: TRUE].segment; WalnutRoot.StartTransaction[]; WalnutRoot.EraseDB[]; WalnutSchema.SetSchemaVersion[segment ! ABORTED => GOTO cant]; WalnutRoot.Shutdown[]; EXITS cant => { WalnutRoot.Shutdown[]; cmd.out.PutF["Couldn't initialize database %g\n", IO.rope[dbName] ]; RETURN[FALSE] }; END; END; cmd.out.PutF[" Walnut files have been properly intialized\n"]; RETURN[TRUE]; }; NextRootEntry: PROC[strm: STREAM] RETURNS[rte: RootEntry] = { []_ strm.SkipWhitespace[flushComments: TRUE]; rte.ident _ strm.GetTokenRope[].token; IF rte.ident.Equal["End", FALSE] THEN RETURN; []_ strm.SkipWhitespace[flushComments: TRUE]; IF rte.ident.Equal["LogInfo", FALSE] THEN { lif: LogInfoFromRoot; lif.fileName _ strm.GetTokenRope[IO.IDProc].token; []_ strm.SkipWhitespace[flushComments: TRUE]; lif.internalFileID _ strm.GetInt[]; []_ strm.SkipWhitespace[flushComments: TRUE]; lif.seqNoPos _ strm.GetIndex[]; lif.logSeqNo _ strm.GetTokenRope[].token; []_ strm.SkipWhitespace[flushComments: TRUE]; rte.info _ lif; } ELSE IF rte.ident.Equal["Key", FALSE] THEN rte.value _ strm.GetLineRope[] ELSE { rte.value _ strm.GetTokenRope[IO.IDProc].token; []_ strm.SkipWhitespace[flushComments: TRUE]; }; RETURN[rte]; }; nonZeroLen: ROPE = "File %g has nonZero (%g bytes) length\n"; EnsureEmptyFile: PROC[cmd: Commander.Handle, fileName: ROPE, pagesWanted: INT _ 2] RETURNS[ok: BOOL] = { strm: STREAM _ WalnutStream.Open[name: fileName].strm; numPages: INT; openFile: AlpineFS.OpenFile _ AlpineFS.OpenFileFromStream[strm]; length: INT _ strm.GetLength[]; IF length # 0 THEN { continue: BOOL _ FALSE; cmd.out.PutRope[IO.PutFR[nonZeroLen, IO.rope[fileName], IO.int[length]]]; continue _ Confirm[cmd]; IF ~continue THEN { strm.Close[]; RETURN[FALSE] }; }; numPages_ FS.GetInfo[openFile].pages; IF pagesWanted > numPages THEN FS.SetPageCount[openFile, pagesWanted]; strm.SetIndex[0]; strm.SetLength[0]; [] _ WalnutStream.SetHighWaterMark[strm, 0, pagesWanted]; strm.Close[]; RETURN[TRUE]; }; CheckPages: PROC[handle: File.Handle, name: ROPE, out: STREAM] RETURNS[ok: BOOL] = { ENABLE FS.Error => { out.PutRope[" ... "]; IF error.code = $invalidPropertyPage THEN out.PutRope["not an FS file"] ELSE out.PutRope[error.explanation]; CONTINUE }; size: File.PageCount _ File.Info[handle].size; pageSpace: VM.Interval _ VM.nullInterval; numPages: INT = 25; ok _ TRUE; { ENABLE UNWIND => FreeSpace[pageSpace]; data: LONG POINTER; [pageSpace, data] _ GetSpace[numPages]; out.PutF["\nReading pages from %g file (%g pages)\n", IO.rope[name], IO.int[size]]; TRUSTED { i: File.PageCount _ 0; WHILE i < size DO IF i # 0 THEN IF i MOD 100 = 0 THEN IF i MOD 1000 = 0 THEN out.PutF["(%g)", IO.int[i]] ELSE out.PutChar['~]; File.Read[handle, [i], numPages, data ! File.Error => { IF why # unknownPage THEN { out.PutF["\nFile error reading page %g\n", IO.int[diskPage]]; out.PutRope[" ... "]; out.PutRope[IagoOps.FileError[why]]; ok _ FALSE; }; EXIT}]; i _ i + numPages; ENDLOOP; }; FreeSpace[pageSpace]; }; }; GetSpace: PROC[num: INT] RETURNS[space: VM.Interval, data: LONG POINTER] = TRUSTED { space _ VM.Allocate[VM.PagesForWords[num*File.wordsPerPage]]; data _ VM.AddressForPageNumber[space.page]; VM.SwapIn[interval: space, kill: TRUE, pin: TRUE]; }; FreeSpace: PROC[space: VM.Interval] = TRUSTED { IF space = VM.nullInterval THEN RETURN; VM.Unpin[space]; VM.Free[space]; }; Commander.Register["CheckAndInit", CheckAndInit, "Checks the WalnutDump file and then creates the files for walnut"]; Commander.Register["InitWalnutFiles", InitWalnutFiles, "Create the files for walnut"]; Commander.Register["InitOtherWalnutFiles", DoOtherInit, "Create the files for walnut"]; Commander.Register["JustDoFiles", JustDoFiles, "Create the files for walnut"]; END. dCreateWalnutWorld.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. special purpose code to construct a Walnut RootFile from the Template-Walnut.Root, store it on the appropriate alpine server and initialize the appropriate files on the server Last Edited by: Willie-Sue, January 8, 1986 11:28:21 am PST Types and variables Procedures Creates a rootFile, stores it on alpine, and initializes all the files names therein, erases the database and sets its version stamp to the correct value. Returns FALSE if operation fails. Creates a rootFile, stores it on alpine, and initializes all the files names therein, erases the database and sets its version stamp to the correct value. Returns FALSE if operation fails. This is for non-mail walnut databases check that all the alpine-server names are the same page paramter to AlpineFS.Open is ignored Κg– "Cedar" style˜head™Icodešœ Οmœ1™J˜šŸ œ˜&Jšœ žœ˜Jšœžœžœ˜ Jšžœžœžœžœ˜Jšœžœžœ˜šœ ˜ Jš œžœ žœžœžœ ˜Q—J˜ Jšžœ žœžœžœ˜Jšœ˜J˜—J˜šŸœžœ'žœ˜=J™ΌJ˜Jšœ.žœ˜3Jšœ žœ˜-Jšœžœ˜Jšœžœžœ˜ Jšžœžœžœžœ˜Jšœžœžœ˜Jšœžœžœžœ˜Mšžœ žœž˜Jšœ žœžœ˜<šžœžœ žœ˜@Jšœžœžœ ˜(——J˜ J˜Jšžœžœžœ˜8šœ˜Jšžœžœžœ˜H—Jšœ žœ"žœ˜Ešžœžœ˜Jšœ žœžœ;˜NJšœžœžœ˜Jšœ˜Jšœ žœ ˜šœ žœžœ ˜)Jšœ&žœ˜2—Jšžœ žœžœ-˜Ošžœ žœžœ˜Jšœ*žœ˜>Jšœ˜Jšžœžœ%˜0J˜Jšžœ,˜0—Jšžœ žœžœžœ˜6Jšžœžœžœ˜J˜—J˜šžœH˜JJšžœžœ˜ —J˜Jšœ<˜—J˜Jšœ žœ"žœ˜EJ˜šžœG˜IJšžœžœ˜ —J˜Jšœ<˜šžœAžœ˜IJšœ˜Jšœ˜Jšž˜J˜—šžœ;žœ˜CJšœ ˜ Jšœ˜Jšž˜J˜—šžœBžœ˜JJšœ˜Jšœ˜Jšž˜J˜—šžœžœžœ ž˜Jšœ+˜+Jšžœ˜—Jšœ'˜'šžœžœžœ)ž˜8Jšœ+˜+Jšžœ˜—Jšœ˜šž˜Jšœžœ˜ —Jšžœ˜Jšžœ˜J˜—Jšœ˜Jšœ˜Jšœ˜Jšžœžœ˜ Jšžœ˜J˜—J˜š Ÿ œžœ žœžœžœ˜GJ˜Jšœ žœžœ˜%Jšœžœ˜Jšœ7žœ˜˜J˜šž˜šœž˜ Jšœ˜Jšœ2žœ˜DJšžœžœ˜ J˜———Jšžœ˜—Jšžœ˜J˜Jšœ>˜>Jšžœžœ˜ J˜—J˜šŸ œžœžœžœ˜=Jšœ'žœ˜-J˜&Jšžœžœžœžœ˜-J˜Jšœ'žœ˜-šžœžœžœ˜+Jšœ˜Jšœ!žœ˜2Jšœ'žœ˜-Jšœ#˜#Jšœ'žœ˜-Jšœ˜Jšœ)˜)Jšœ'žœ˜-Jšœ˜J˜Jšž˜Jšžœžœžœ ˜Ešžœ˜Jšœžœ˜/Jšœ'žœ˜-J˜J˜——Jšžœ˜ J˜—J˜Jšœ žœ-˜=J˜šŸœžœ"žœžœ˜RJšžœžœ˜Jšœžœ*˜6Jšœ)™)Jšœ žœ˜Jšœ@˜@Jšœžœ˜J˜šžœ žœž˜Jšœ žœžœ˜Jšœžœžœžœ˜IJ˜Jšžœ žœžœžœ˜2J˜—Jšœ žœ˜%Jšžœžœžœ%˜FJ˜J˜Jšœ9˜9J˜ Jšžœžœ˜ J˜—J˜š Ÿ œžœžœžœžœžœ˜Tšžœžœ ˜L˜šžœ"˜$Lšžœ˜"Lšžœ ˜$—Lšž˜Lšœ˜—Lšœ.˜.Lšœ žœ žœ˜)Lšœ žœ˜Lšœžœ˜ šœ˜Lšžœžœ˜&Lšœžœžœ˜L˜'Lšœ6žœ žœ ˜Sšžœ˜ Lšœ˜šžœ ž˜š žœžœžœžœ ž˜#Lš žœžœ žœžœ žœ˜I—šœ7˜7šžœžœ˜Lšœ+žœ˜=Lšœ:˜:Lšœžœ˜ L˜Lšžœ˜——L˜Lšžœ˜—Lšœ˜—L˜Lšœ˜—Lšœ˜—L˜šŸœžœžœžœžœžœžœžœ˜TLšœžœ žœ'˜=Lšœžœ"˜+Lšžœžœžœ˜2Lšœ˜—L˜šŸ œžœžœ žœ˜/Lšžœ žœžœžœ˜'Lšžœ˜Lšžœ ˜Lšœ˜—J˜šœ0˜0JšœD˜D—JšœV˜VJšœW˜WJšœN˜NNšžœ˜——…—9 OΥ