DIRECTORY AlpFile USING [AccessFailed], BasicTime USING [nullGMT, Now, ToPupTime], FS USING [Error, ErrorFromStream, PagesForBytes], GVBasics USING [Timestamp], IO, RefText USING [line, Equal, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope], Rope, ViewerTools USING [TiogaContents, TiogaContentsRec], WalnutKernelDefs USING [MsgLogEntry], WalnutMiscLog, WalnutRoot USING [CommitAndContinue, GetNewMailStream, GetReadArchiveStream, ReturnNewMailStream, ReturnReadArchiveStream], WalnutSendOps USING [simpleUserName, userRName, TiogaTextFromStrm, RopeFromStream], WalnutStream USING [addMsg, createMsg, createMsgSet, hasbeenRead, moveMsg, Aborted, AbortStream, ConstructMsgID, FlushStream, MsgEntryInfoFromStream, SetHighWaterMark, WriteEntry, WriteMsgBody]; WalnutMiscLogImpl: CEDAR PROGRAM IMPORTS AlpFile, BasicTime, FS, IO, RefText, Rope, WalnutRoot, WalnutSendOps, WalnutStream EXPORTS WalnutMiscLog = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TiogaContents: TYPE = ViewerTools.TiogaContents; newMailStream: STREAM _ NIL; readArchiveStream: STREAM _ NIL; msgIDRope: ROPE = "gvMsgID"; categoriesRope: ROPE = "Categories"; activeMsgSet: REF TEXT = "Active"; newMsgSetList: LIST OF REF TEXT; generalBuffer: REF TEXT; msgIDBuffer: REF TEXT; GetNewMailLog: PUBLIC PROC[lengthRequired: INT, pagesWanted: INT] RETURNS[STREAM] = { BEGIN ENABLE IO.Error, FS.Error, AlpFile.AccessFailed => IF newMailStream # NIL THEN { AbortMailLog[]; GOTO exit; }; IF newMailStream # NIL THEN AbortMailLog[]; IF (newMailStream _ WalnutRoot.GetNewMailStream[lengthRequired, pagesWanted].strm) = NIL THEN RETURN[NIL]; EXITS exit => IF newMailStream # NIL THEN AbortMailLog[]; END; RETURN[newMailStream]; }; CloseNewMailLog: PUBLIC PROC = { strm: STREAM _ newMailStream; IF strm = NIL THEN RETURN; newMailStream _ NIL; WalnutRoot.ReturnNewMailStream[strm ! IO.Error, FS.Error => CONTINUE]; }; AbortMailLog: PROC = { IF newMailStream = NIL THEN RETURN; WalnutStream.AbortStream[newMailStream ! IO.Error, FS.Error => CONTINUE]; CloseNewMailLog[]; }; CreateReadArchiveLog: PUBLIC PROC[ fileToRead: STREAM, msgSet: ROPE, reportProc: PROC[msg1, msg2, msg3: ROPE _ NIL]] RETURNS[ok: BOOL, reason: ROPE] = { lastCommitPosInFileToRead: INT; fileToReadPages: INT _ FS.PagesForBytes[fileToRead.GetLength[]]; possibleArchiveLength: INT _ MAX[20, fileToReadPages + (fileToReadPages/5)]; -- 120% ok _ FALSE; reason _ NIL; BEGIN ENABLE BEGIN IO.Error => { reason _ FS.ErrorFromStream[fileToRead].explanation; IF reason = NIL THEN reason _ FS.ErrorFromStream[readArchiveStream].explanation; IF reason = NIL THEN reason _ "IO error during createReadArchiveLog"; GOTO exit; }; FS.Error => { reason _ error.explanation; GOTO exit }; AlpFile.AccessFailed => { reason _ "AlpFile.AccessFailed"; GOTO exit}; UNWIND => GOTO exit; END; IF (readArchiveStream _ WalnutRoot.GetReadArchiveStream[possibleArchiveLength]) = NIL THEN RETURN[FALSE, "Can't get readArchiveStream"]; readArchiveStream.SetIndex[0]; WalnutStream.SetHighWaterMark[strm: readArchiveStream, hwmBytes: 0, numPages: -1]; WalnutRoot.CommitAndContinue[]; [ok, lastCommitPosInFileToRead] _ ArchiveReader[ archiveStream: readArchiveStream, fileToRead: fileToRead, msgSet: msgSet, reportProc: reportProc, posToStartInFileToRead: 0]; IF ~ok THEN CloseReadArchiveLog[]; EXITS exit => IF readArchiveStream # NIL THEN { WalnutRoot.ReturnReadArchiveStream[readArchiveStream]; readArchiveStream _ NIL; }; END; }; CloseReadArchiveLog: PUBLIC PROC = { WalnutRoot.ReturnReadArchiveStream[readArchiveStream]; readArchiveStream _ NIL; }; Shutdown: PUBLIC PROC = { newMailStream _ readArchiveStream _ NIL }; ArchiveFileType: TYPE = {unknown, old, new}; ArchiveReader: PUBLIC PROC[archiveStream, fileToRead: STREAM, msgSet: ROPE, reportProc: PROC[msg1, msg2, msg3: Rope.ROPE _ NIL], posToStartInFileToRead: INT] RETURNS [ok: BOOL, lastCommitPosInFileToRead: INT] = { num: INT _ 0; BadFormat: PROC[s: ROPE] = { IF reportProc = NIL THEN RETURN; reportProc[ IO.PutFR["Bad log file format at %g: %g\n", IO.int[fileToRead.GetIndex[]], IO.rope[s]]]; }; BEGIN ENABLE BEGIN ABORTED => { ok _ FALSE; GOTO GiveUp }; FS.Error => { IF reportProc # NIL THEN reportProc[error.explanation]; ok _ FALSE; GOTO GiveUp }; IO.Error => { IF reportProc # NIL THEN { reason: ROPE _ FS.ErrorFromStream[fileToRead].explanation; IF reason # NIL THEN reason _ IO.PutFR["IOError on file being read, reported as %g, at pos %g", IO.rope[reason], IO.int[fileToRead.GetIndex[]] ] ELSE reason _ FS.ErrorFromStream[archiveStream].explanation; IF reason # NIL THEN reason _ IO.PutFR["IOError on tempLog being written, reported as %g, at pos %g", IO.rope[reason], IO.int[archiveStream.GetIndex[]] ] ELSE reason _ IO.PutFR["IOError at writePos %g",IO.int[archiveStream.GetIndex[]] ]; reportProc["\n", reason, "\n\n"]; }; ok _ FALSE; GOTO GiveUp }; IO.EndOfStream => {BadFormat["Unexpected EOF"]; ok _ FALSE; GOTO GiveUp}; END; archiveFileType: ArchiveFileType _ unknown; entryLength, prefixLength, beginMsgPos: INT; entryChar: CHAR; msgSetRT: REF TEXT _ msgSet.ToRefText[]; bytesWritten: INT _ 0; bytesToWriteBeforeCommit: INT = 100000; -- try this lengthOfFileToRead: INT _ fileToRead.GetLength[]; startPosOnArchive: INT; newMsgSetList _ NIL; -- global for each file read ok _ TRUE; generalBuffer _ RefText.ObtainScratch[RefText.line]; msgIDBuffer _ RefText.ObtainScratch[RefText.line]; lastCommitPosInFileToRead _ posToStartInFileToRead; fileToRead.SetIndex[posToStartInFileToRead]; DO [beginMsgPos, prefixLength, entryLength, entryChar] _ FindStartOfEntry[fileToRead]; IF entryLength = -1 OR (entryLength=0) OR (entryLength=prefixLength) AND NOT (entryChar = '_) THEN { RefText.ReleaseScratch[generalBuffer]; RefText.ReleaseScratch[msgIDBuffer]; reportProc[IO.PutFR["\n %g messages read from archive file", IO.int[num]] ]; IF bytesWritten = 0 THEN RETURN[TRUE, lengthOfFileToRead]; IF Flush[archiveStream] THEN RETURN[TRUE, lengthOfFileToRead]; RETURN[FALSE, lastCommitPosInFileToRead]; }; SELECT entryChar FROM '-, '+, '_ => { BadFormat[IO.PutFR["Non-message entry (entrychar = %g)", IO.char[entryChar]]]; fileToRead.SetIndex[beginMsgPos+entryLength] }; ENDCASE => { -- regular message entry DO -- fake DO so can use exit msgID: REF TEXT; categories: ROPE; catList: LIST OF REF TEXT; outOfSynch, notInActive: BOOL; headersPos: INT _ beginMsgPos+prefixLength; body: ViewerTools.TiogaContents; IF archiveFileType = unknown THEN archiveFileType _ DetermineFileType[fileToRead]; IF archiveFileType = old THEN { [msgID, categories, outOfSynch] _ ReadPrefixInfo[fileToRead, headersPos]; IF outOfSynch THEN { BadFormat[IO.PutFR["\nPrefix Info not found at %g", IO.int[headersPos]]]; EXIT}; body _ WalnutSendOps.TiogaTextFromStrm[ -- careful here fileToRead, headersPos, entryLength-prefixLength]; IF body.formatting.Length[] <= 2 THEN body.formatting _ NIL; --glitch IF RefText.Length[msgID] = 0 THEN { -- need to construct an ID ts: GVBasics.Timestamp; id, from: ROPE; mle: WalnutKernelDefs.MsgLogEntry = WalnutStream.createMsg; mle.textLen _ entryLength - prefixLength; fileToRead.SetIndex[headersPos]; WalnutStream.MsgEntryInfoFromStream[fileToRead, mle]; from _ IF mle.sender.Equal[WalnutSendOps.userRName, FALSE] OR mle.sender.Equal[WalnutSendOps.simpleUserName, FALSE] THEN Rope.Concat["To: ", mle.to] ELSE mle.sender; IF from.Length[] = 0 THEN from _ "NoKnownGVid"; IF mle.date = BasicTime.nullGMT THEN mle.date _ BasicTime.Now[]; ts _ [net: 3, host: 14, time: BasicTime.ToPupTime[mle.date]]; id _ WalnutStream.ConstructMsgID[ts, from]; msgID _ id.ToRefText[]; fileToRead.SetIndex[beginMsgPos+entryLength]; }; IF body.formatting.Length[] = 0 THEN { pos: INT _ 0; DO -- replace \240's with spaces (used by laurel) pos _ body.contents.Find["\240", pos]; IF pos = -1 THEN EXIT; body.contents _ body.contents.Replace[start: pos, len: 1, with: " "]; ENDLOOP; }; } ELSE { this: BOOL; [msgID, categories, body, this] _ ReadNewStyleArchiveFile[ fileToRead, headersPos, entryLength - (headersPos-beginMsgPos) - 1]; IF ~this THEN { BadFormat[ IO.PutFR["\nCouldn't read entry at %g", IO.int[beginMsgPos]] ]; EXIT; }; }; BEGIN ENABLE BEGIN FS.Error => IF error.code = $transAborted THEN GOTO aborted ELSE REJECT; IO.Error => IF WalnutStream.Aborted[archiveStream] THEN GOTO aborted ELSE REJECT; END; WalnutStream.createMsg.msg _ msgID; WalnutStream.createMsg.textLen _ body.contents.Length[]; WalnutStream.createMsg.formatLen _ body.formatting.Length[]; startPosOnArchive _ WalnutStream.WriteEntry[archiveStream, WalnutStream.createMsg]; WalnutStream.WriteMsgBody[archiveStream, body]; IF RefText.Length[msgSetRT] # 0 THEN categories _ NIL; [catList, notInActive] _ CheckCategories[archiveStream, msgSetRT, categories]; IF entryChar # '? THEN { WalnutStream.hasbeenRead.msg _ msgID; [] _ WalnutStream.WriteEntry[archiveStream, WalnutStream.hasbeenRead]; }; BEGIN first: BOOL _ TRUE; FOR mL: LIST OF REF TEXT _ catList, mL.rest UNTIL mL=NIL DO IF first AND notInActive THEN { WalnutStream.moveMsg.msg _ msgID; WalnutStream.moveMsg.from _ activeMsgSet; WalnutStream.moveMsg.to _ mL.first; [] _ WalnutStream.WriteEntry[archiveStream, WalnutStream.moveMsg] } ELSE { WalnutStream.addMsg.msg _ msgID; WalnutStream.addMsg.to _ mL.first; [] _ WalnutStream.WriteEntry[archiveStream, WalnutStream.addMsg]; }; first _ FALSE; ENDLOOP; END; IF (bytesWritten _ bytesWritten + archiveStream.GetLength[]-startPosOnArchive) >= bytesToWriteBeforeCommit THEN { WalnutStream.FlushStream[archiveStream]; WalnutRoot.CommitAndContinue[]; lastCommitPosInFileToRead _ beginMsgPos+entryLength; bytesWritten _ 0; }; EXITS aborted => { RefText.ReleaseScratch[generalBuffer]; RefText.ReleaseScratch[msgIDBuffer]; RETURN[FALSE, lastCommitPosInFileToRead]; }; END; -- of Enable FS.Error, IO.Error above - writing on archiveStream IF (num _ num + 1) MOD 10 = 0 THEN { IF num MOD 100 = 0 THEN reportProc[IO.PutFR["(%g)", IO.int[num]]] ELSE reportProc["~"]; }; EXIT; ENDLOOP; fileToRead.SetIndex[beginMsgPos+entryLength]; }; ENDLOOP; EXITS GiveUp => NULL; END; RefText.ReleaseScratch[generalBuffer]; RefText.ReleaseScratch[msgIDBuffer]; reportProc[IO.PutFR["\n %g messages read from archive file", IO.int[num]] ]; RETURN[ok, lastCommitPosInFileToRead]; }; Flush: PROC[strm: STREAM] RETURNS[ok: BOOL] = { ENABLE FS.Error => IF error.code = $transAborted THEN GOTO aborted ELSE REJECT; WalnutStream.FlushStream[strm]; WalnutRoot.CommitAndContinue[]; RETURN[TRUE]; EXITS aborted => RETURN[FALSE] }; CheckCategories: PROC[archiveStream: STREAM, msgSet: REF TEXT, categories: ROPE] RETURNS[catList: LIST OF REF TEXT, notInActive: BOOL] = { h: STREAM; CheckMsgSet: PROC[ms: REF TEXT] RETURNS[notActive: BOOL] = { IF RefText.Equal[ms, activeMsgSet, FALSE] THEN RETURN[FALSE]; FOR mL: LIST OF REF TEXT _ newMsgSetList, mL.rest UNTIL mL = NIL DO IF RefText.Equal[mL.first, ms, FALSE] THEN RETURN[TRUE]; ENDLOOP; WalnutStream.createMsgSet.msgSet _ ms; [] _ WalnutStream.WriteEntry[archiveStream, WalnutStream.createMsgSet]; newMsgSetList _ CONS[ms, newMsgSetList]; -- to be able to check next time RETURN[TRUE]; }; catList _ NIL; notInActive _ TRUE; IF categories = NIL THEN IF RefText.Length[msgSet] = 0 THEN RETURN[NIL, FALSE] ELSE { notInActive _ CheckMsgSet[msgSet]; IF notInActive THEN RETURN[LIST[msgSet], notInActive] ELSE RETURN[NIL, FALSE]; }; IF RefText.Length[msgSet] # 0 THEN { notInActive _ CheckMsgSet[msgSet]; IF notInActive THEN catList _ LIST[msgSet]; }; h _ IO.RIS[categories]; UNTIL h.EndOf[] DO token: ROPE _ h.GetTokenRope[breakProc: IO.IDProc].token; IF token.Length[] # 0 THEN { tokenRT: REF TEXT _ token.ToRefText[]; notActive: BOOL _ CheckMsgSet[tokenRT _ token.ToRefText[]]; notInActive _ notInActive AND notActive; IF notActive THEN catList _ CONS[tokenRT, catList]; }; ENDLOOP; h.Close[]; }; DetermineFileType: PROC[strm: STREAM] RETURNS[aft: ArchiveFileType] = { curPos: INT _ strm.GetIndex[]; aft _ old; IF strm.GetChar[] = '@ THEN { -- might be new or Hardy file itemType: INT; [] _ strm.GetInt[]; IF (itemType _ strm.GetInt[]) = WalnutMiscLog.TiogaControlItemType THEN aft _ new; }; strm.SetIndex[curPos]; }; ReadPrefixInfo: PROC[fileToRead: STREAM, headersPos: INT] RETURNS[msgID: REF TEXT, categories: ROPE, outOfSynch: BOOL] = { curPos: INT; outOfSynch _ FALSE; UNTIL (curPos _ fileToRead.GetIndex[]) = headersPos DO tag: REF TEXT; IF curPos > headersPos THEN {outOfSynch _ TRUE; RETURN}; tag _ fileToRead.GetToken[buffer: generalBuffer].token; IF Rope.Equal[RefText.TrustTextAsRope[tag], msgIDRope, FALSE] THEN { [] _ fileToRead.GetChar[]; -- the : [] _ fileToRead.SkipWhitespace[]; msgID _ fileToRead.GetLine[msgIDBuffer]; LOOP; }; IF Rope.Equal[RefText.TrustTextAsRope[tag], categoriesRope, FALSE] THEN { [] _ fileToRead.GetChar[]; -- the : [] _ fileToRead.SkipWhitespace[]; categories _ fileToRead.GetLineRope[]; LOOP; }; ENDLOOP; }; ReadNewStyleArchiveFile: PROC[ fileToRead: STREAM, headersPos, bodyLen: INT] RETURNS[msgID: REF TEXT, categories: ROPE, body: TiogaContents, ok: BOOL] = { len, type, pos, formatLen: INT; ok _ FALSE; body _ NEW[ViewerTools.TiogaContentsRec]; IF fileToRead.GetChar[] # '@ THEN RETURN; len _ fileToRead.GetInt[]; type _ fileToRead.GetInt[]; formatLen _ fileToRead.GetInt[]; [] _ fileToRead.GetChar[]; -- the \n IF type = WalnutMiscLog.TiogaControlItemType THEN { msgID _ fileToRead.GetLine[msgIDBuffer]; categories _ fileToRead.GetLineRope[]; IF formatLen # 0 THEN { body.formatting _ WalnutSendOps.RopeFromStream[fileToRead, fileToRead.GetIndex[], formatLen]; [] _ fileToRead.GetChar[]; -- cr }; }; [] _ fileToRead.GetChar[]; -- the other @ pos _ fileToRead.GetIndex[]; body.contents _ WalnutSendOps.RopeFromStream[fileToRead, pos, bodyLen]; [] _ fileToRead.GetChar[ ! IO.EndOfStream => CONTINUE]; -- the trailing \n ok _ TRUE; }; FindStartOfEntry: PROC[fileToRead: STREAM] RETURNS[startPos, prefixLength, entryLength: INT, entryChar: CHAR] = { ENABLE IO.EndOfStream => GOTO eoS; line: REF TEXT; relPos: INT _ 0; startFound: BOOL; prefixLength _ entryLength _ 0; entryChar _ '\000; -- not a valid entryType char IF fileToRead.EndOf[] THEN RETURN; startPos _ fileToRead.GetIndex[]; line _ fileToRead.GetLine[generalBuffer]; [startFound, relPos] _ IsStart[line]; IF NOT startFound THEN UNTIL startFound DO IF fileToRead.EndOf[] THEN RETURN; startPos _ fileToRead.GetIndex[]; line _ fileToRead.GetLine[generalBuffer]; [startFound, relPos] _ IsStart[line]; ENDLOOP; -- Read entry info line from log, e.g.: 00101 00029 US+ startPos _ startPos + relPos; entryLength _ fileToRead.GetInt[]; prefixLength _ fileToRead.GetInt[]; [] _ fileToRead.GetChar[]; -- space [] _ fileToRead.GetChar[]; -- flag [] _ fileToRead.GetChar[]; -- flag entryChar _ fileToRead.GetChar[]; -- Laurel's march char IF fileToRead.GetChar[] # '\n THEN entryLength _ -1; EXITS eoS => {entryLength _ -1; RETURN}; }; starStartStar: ROPE = "*start*"; IsStart: PROC[line: REF TEXT] RETURNS[startFound: BOOL, relPos: INT] = { relPos _ 0; IF RefText.Length[line] = 0 THEN RETURN[FALSE, relPos]; IF Rope.Equal[starStartStar, RefText.TrustTextAsRope[line]] THEN RETURN[TRUE, relPos]; IF (relPos _ RefText.Length[line] - starStartStar.Length[]) <=0 THEN RETURN[FALSE, relPos]; IF Rope.Find[s1: RefText.TrustTextAsRope[line], s2: starStartStar, pos1: relPos] = relPos THEN RETURN[TRUE, relPos]; RETURN[FALSE, relPos]; }; END. \WalnutMiscLogImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Willie-Sue, November 5, 1985 10:39:09 am PST Walnut NewMailLog and ReadArchiveLog Operations Implementation NOTE: Currently this ONLY parses old-style Archive logs & old style dump files Types Variables Procedures Writing and Reading NewMail/ReadArchive files opens the NewMailLog (aborting any current stream that might be open); truncates it to the required length; returns NIL if the log is shorter than required, or if the file could not be opened reads file and writes the ReadArchiveLogFile; returns ok if all went well ******************************************************** Read *start* from log back up and parse from the strm -- ProcessCategories line _ fileToRead.GetLine[generalBuffer]; entryChar _ RefText.Fetch[line, RefText.Length[line]-1]; ÊU˜šÏb™Jšœ Ïmœ1™™>J™J™OJ™J˜šÏk ˜ JšœŸœ˜Jšœ Ÿœ˜*JšŸœŸœ)˜1Jšœ Ÿœ ˜JšŸœ˜JšœŸœG˜TJšœ˜Jšœ Ÿœ#˜4JšœŸœ˜%Jšœ˜Jšœ Ÿœk˜{JšœŸœ@˜Sšœ Ÿœ8˜JJšœh˜hJšœ˜—J˜—šœŸœŸ˜ J˜šŸ˜JšœŸœŸœ˜*Jšœ'˜'J˜—šŸ˜J˜ J˜—JšœŸ˜J™J™š™J˜JšŸœŸœŸœ˜JšŸœŸœŸœŸœ˜JšœŸœ˜0—J™š ™ J™JšœŸœŸœ˜JšœŸœŸœ˜ J˜Jšœ Ÿœ ˜JšœŸœ˜$JšœŸœŸœ ˜"Jš œŸœŸœŸœŸœ˜ J˜JšœŸœŸœ˜Jšœ ŸœŸœ˜—Ihead1š ™ š-™-š Ïn œŸœŸœŸœŸœ˜AJšŸœŸœ˜J™ÁšŸœŸœŸœŸœ˜8šŸœŸœŸœ˜Jšœ˜JšŸœ˜ J˜—JšŸœŸœŸœ˜+J˜šŸœ˜Jš œAŸœŸœŸœŸœ˜V—J˜šŸ˜JšœŸœŸœŸœ˜3—JšŸœ˜—JšŸœ˜J˜—J˜š œŸœŸœ˜ JšœŸœ˜JšŸœŸœŸœŸœ˜JšœŸœ˜Jšœ&ŸœŸœ Ÿœ˜FJšœ˜—J˜š  œŸœ˜JšŸœŸœŸœŸœ˜#Jšœ)ŸœŸœ Ÿœ˜IJšœ˜J˜—J˜š œŸœŸœŸœ ŸœŸœŸœŸœŸœŸœ Ÿœ˜˜J™JJšœŸœ˜JšœŸœŸœ'˜@JšœŸœŸœ-Ïc˜TJšœŸœ˜ Jšœ Ÿœ˜ šŸœŸœŸ˜šŸœ ˜ Jšœ Ÿœ)˜4JšŸœ ŸœŸœ Ÿœ0˜PJšŸœ ŸœŸœ1˜EJšŸœ˜ J˜—JšŸœ(Ÿœ˜6Jšœ;Ÿœ˜FJšŸœŸœ˜JšŸœ˜J˜šŸœPŸœŸ˜ZJšŸœŸœ!˜-—J˜Jšœ˜JšœR˜RJ˜J˜J˜šœ0˜0Jšœ9˜9JšœC˜C—J˜JšŸœŸœ˜"J˜šŸ˜šœ˜šŸœŸœŸœ˜!Jšœ6˜6JšœŸœ˜J˜———JšŸœ˜—J˜—J˜š œŸœŸœ˜$Jšœ6˜6JšœŸœ˜J˜—J˜š œŸœŸœ˜Jšœ&Ÿœ˜,——J˜Jšœ8™8J™JšœŸœ˜,˜Jš  œŸ œŸœ ŸœŸœŸœŸœŸœŸœŸœŸœ˜ÔJ˜JšœŸœ˜ š  œŸœŸœ˜JšŸœŸœŸœŸœ˜ šœ ˜ JšŸœ*ŸœŸœ ˜X—J˜J˜šŸœŸœŸ˜JšŸœ ŸœŸœ ˜'šŸœ ˜ JšŸœŸœŸœ˜7JšœŸœŸœ˜J˜—šŸœ ˜ šŸœŸœŸœ˜JšœŸœŸœ)˜:šŸœ ŸœŸ˜šœ Ÿœ?˜JJšŸœŸœ˜0—JšŸœ Ÿœ,˜<—šŸœ ŸœŸ˜šœ˜šŸœE˜GJšŸœŸœ ˜3——šŸœ ˜ JšŸœ Ÿœ!˜E——Jšœ!˜!J˜—JšœŸœŸœ˜Jšœ˜—JšŸœ3ŸœŸœ ˜IJšŸœ˜—J˜J˜+Jšœ(Ÿœ˜,Jšœ Ÿœ˜Jšœ ŸœŸœ˜(JšœŸœ˜JšœŸœ ¡ ˜4JšœŸœ˜1JšœŸœ˜J˜JšœŸœ¡˜2JšœŸœ˜ Jšœ4˜4Jšœ2˜2Jšœ3˜3Jšœ,˜,J˜šŸ˜Jšœ™JšœS˜Sš ŸœŸœŸœŸœŸœŸœ˜eJ˜&J˜$Jšœ Ÿœ1Ÿœ ˜MJšŸœŸœŸœŸœ˜:JšŸœŸœŸœŸœ˜>JšŸœŸœ˜)J˜—J˜šŸœ Ÿ˜šœ˜Jšœ Ÿœ-Ÿœ˜NJšœ,˜,Jšœ˜J˜—šŸœ¡˜'JšŸœ¡˜JšœŸœ˜Jšœ Ÿœ˜Jšœ ŸœŸœŸœ˜JšœŸœ˜Jšœ Ÿœ˜+Jšœ ˜ J˜šŸœŸ˜!Jšœ0˜0—J˜šŸœŸœ˜JšœI˜IšŸœ Ÿœ˜Jšœ Ÿœ(Ÿœ˜IJšŸœ˜—šœ)¡˜8Jšœ2˜2—JšŸœŸœŸœ¡˜E—˜šŸœŸœ¡˜>Jšœ™J˜Jšœ Ÿœ˜Jšœ;˜;Jšœ)˜)Jšœ ˜ Jšœ5˜5šœŸœ+ŸœŸ˜=šœ/Ÿœ˜5JšŸœ˜ JšŸœ ˜——JšŸœŸœ˜/JšŸœŸœ˜@J˜Jšœ=˜=Jšœ+˜+J˜Jšœ-˜-J˜—J˜šŸœŸœ˜&JšœŸœ˜ šŸœ¡.˜2Jšœ&˜&JšŸœ ŸœŸœ˜JšœE˜EJšŸœ˜—J˜—J˜J˜šŸœ˜JšœŸœ˜ šœ!˜!šœ˜JšœD˜D——šŸœŸœ˜šœ ˜ JšŸœ&Ÿœ˜?—JšŸœ˜J˜—J˜—J˜——šŸœŸœŸ˜šŸœ ˜ Jš ŸœŸœŸœ ŸœŸœ˜<—šŸœ ˜ Jš Ÿœ%ŸœŸœ ŸœŸœ˜E—JšŸœ˜J˜Jšœ#˜#Jšœ8˜8Jšœ<˜<šœ˜Jšœ?˜?—Jšœ/˜/J˜JšŸœŸœŸœ˜6JšœN˜NJ˜šŸœŸœ˜Jšœ%˜%JšœF˜FJ˜—J˜Jšœ™šŸ˜JšœŸœŸœ˜šŸœŸœŸœŸœŸœŸœŸœŸ˜;šŸœŸœ Ÿœ˜Jšœ!˜!Jšœ)˜)Jšœ#˜#JšœA˜AJ˜—šŸœ˜Jšœ ˜ Jšœ"˜"JšœA˜AJ˜—JšœŸœ˜JšŸœ˜——JšŸœ˜J˜šŸœiŸœ˜qJ˜(J˜Jšœ4˜4Jšœ˜J˜—J˜šŸ˜šœ Ÿ˜ J˜&J˜$JšŸœŸœ˜)J˜——JšŸœ¡@˜FJ˜šŸœŸœŸœ˜$Jš ŸœŸœ Ÿœ ŸœŸœ ˜AJšœŸœ˜J˜—JšŸœ˜JšŸœ˜Jšœ-˜-Jšœ˜——JšŸœ˜˜JšŸœ Ÿœ˜—JšŸœ˜—J˜J˜&J˜$Jšœ Ÿœ1Ÿœ ˜MJšŸœ ˜&Jšœ˜—J˜š  œŸœŸœŸœŸœ˜/JšŸœŸœ ŸœŸœŸœ ŸœŸœ˜PJ˜J˜J˜JšŸœŸœ˜ šŸ˜Jšœ ŸœŸœ˜—Jšœ˜J˜—š œŸœŸœ ŸœŸœŸœŸœ ŸœŸœŸœŸœŸœ˜ŠJšœŸœ˜ š   œŸœŸœŸœ Ÿœ˜<šŸœ!Ÿœ˜)JšŸœŸœŸœ˜—J˜šŸœŸœŸœŸœŸœŸœŸœŸ˜CJš ŸœŸœŸœŸœŸœ˜8JšŸœ˜—J˜J˜&JšœG˜GJšœŸœ¡ ˜JJšŸœŸœ˜ J˜—J˜Jšœ Ÿœ˜JšœŸœ˜šŸœŸœŸ˜š ŸœŸœŸœŸœŸœ˜5šŸœ˜Jšœ"˜"šŸœ ŸœŸœŸœ˜5JšŸœŸœŸœŸœ˜—J˜———J˜šŸœŸœ˜$Jšœ"˜"JšŸœ Ÿœ Ÿœ ˜+J˜J˜—JšœŸœŸœ ˜šŸœ Ÿ˜JšœŸœŸœ˜9šŸœŸœ˜Jšœ ŸœŸœ˜&Jšœ Ÿœ,˜;JšœŸœ ˜(JšŸœ Ÿœ Ÿœ˜3J˜—JšŸœ˜Jšœ ˜ —J˜—J˜š œŸœŸœŸœ˜GJšœŸœ˜Jšœ ˜ šŸœŸœ¡˜ŸœŸœŸœ ˜[šŸœXŸ˜^JšŸœŸœ ˜—J˜JšŸœŸœ ˜J˜——J˜JšŸœ˜——…—<SÃ