DIRECTORY Atom, BasicTime, CedarProcess, Commander, Convert, EditSpan, IO, MailBasics, MailBasicsFileTypes, MailMessage, MailRetrieve, MailSend, MailUtils, MessageWindow, PeanutCredentials, PeanutProfile, PeanutRetrieve, PeanutWindow, Process, Rope, TEditDisplay, TEditDocument, TextEdit, TextNode, Tioga, TiogaFileOps, TiogaIO, TiogaOps, TiogaOpsDefs, XNSCredentials, UserProfile, ViewerClasses, ViewerOps, XNSAuth; PeanutRetrieveImpl: CEDAR MONITOR IMPORTS Atom, BasicTime, CedarProcess, Commander, Convert, EditSpan, IO, MailMessage, MailRetrieve, MailUtils, MessageWindow, PeanutProfile, PeanutWindow, Process, TiogaIO, Rope, TEditDisplay, TextEdit, TextNode, TiogaFileOps, TiogaOps, XNSCredentials, UserProfile, ViewerOps, XNSAuth EXPORTS PeanutCredentials, PeanutRetrieve, TiogaFileOps = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; RName: TYPE = MailBasics.RName; RefTextNode: TYPE = REF NodeBody; NodeBody: PUBLIC TYPE = Tioga.NodeRep; flushMsgs: BOOL ¬ TRUE; questionName: ROPE = "?"; ReportRope: PROCEDURE [r: ROPE] = { PeanutWindow.OutputRope[r] }; GetNewMsgs: PUBLIC ENTRY PROC [open: BOOL ¬ TRUE] = { ENABLE UNWIND => NULL; [] ¬ InternalGetNewMsgs[open]; IF flushMsgs THEN PeanutWindow.SetNewMail[FALSE]; }; InternalGetNewMsgs: INTERNAL PROC [open: BOOL ¬ TRUE] RETURNS [numRetrieved: INT] = { allOK: BOOL¬ TRUE; mailViewer: Viewer; firstHeader: RefTextNode; StashNewMessages: PROC [retrieveOK: BOOL] RETURNS[doRemoteFlush: BOOL] = { saveOK: BOOL ~ ViewerOps.SaveViewer[mailViewer]; allOK ¬ allOK AND retrieveOK; RETURN[saveOK AND retrieveOK AND flushRemoteMail AND mailViewer#NIL AND NOT mailViewer.destroyed]; }; mailViewer ¬ PeanutWindow.InternalGetMailViewer[PeanutProfile.activeMailFile]; [numRetrieved, firstHeader] ¬ AddNewMessages[StashNewMessages, mailViewer]; IF numRetrieved = 0 THEN { ReportRope["\nNo messages were retrieved"]; RETURN}; IF mailViewer.iconic AND PeanutProfile.automaticNewMail THEN NULL ELSE { WITH mailViewer.data SELECT FROM tdd: TEditDocument.TEditDocumentData => { TEditDisplay.EstablishLine[tdd, [firstHeader,0]]; ViewerOps.PaintViewer[mailViewer, client]; }; ENDCASE => NULL; }; IF NOT allOK THEN ReportRope["\nSome messages may not have been retrieved"]; IF open AND mailViewer.iconic THEN ViewerOps.OpenIcon[mailViewer]; }; msgPollingInterval: INT¬ 300; -- Number of seconds between mailbox polling. flushRemoteMail: BOOLEAN¬ TRUE; myRetrieveHandle: RetrieveHandle; -- cookie for receiving messages. RetrieveHandle: TYPE = REF RetrieveHandleRec; RetrieveHandleRec: TYPE = RECORD[ creds: LIST OF MailUtils.Credentials, mHandle: MailRetrieve.Handle ]; OpenConnection: PUBLIC PROC[user: LIST OF RName] = { CloseConnection[]; NewUser[user] ; }; CloseConnection: PUBLIC PROC[] = { IF myRetrieveHandle # NIL THEN{ MailRetrieve.Close[myRetrieveHandle.mHandle]; myRetrieveHandle¬ NIL}; }; NewUser: PUBLIC PROC[user: LIST OF RName] = { IF myRetrieveHandle = NIL THEN NewHandle[user]; } ; userRNameList: PUBLIC MailBasics.RNameList ¬ NIL; sendingCredentials: PUBLIC MailSend.SendingCredentialsList ¬ NIL; simpleUserName: PUBLIC LIST OF ROPE ¬ NIL; -- user name without registry NewHandle: PROC[user: LIST OF RName] = { doGV: BOOL ¬ UserProfile.Boolean["Peanut.gvMail", TRUE]; doXNS: BOOL ¬ UserProfile.Boolean["Peanut.xnsMail", TRUE]; creds: LIST OF MailUtils.Credentials; SELECT TRUE FROM ( doGV AND doXNS ) => creds ¬ MailUtils.GetUserCredentials[]; doGV => creds ¬ MailUtils.GetUserCredentials[$gv]; ENDCASE => creds ¬ MailUtils.GetUserCredentials[$xns]; myRetrieveHandle ¬ NEW[RetrieveHandleRec]; myRetrieveHandle.mHandle ¬ MailRetrieve.Create[ msgPollingInterval, WatchMailBox ]; myRetrieveHandle.creds ¬ creds; userRNameList ¬ NIL; simpleUserName ¬ NIL; sendingCredentials ¬ NIL; FOR cL: LIST OF MailUtils.Credentials _ creds, cL.rest UNTIL cL = NIL DO loggedInName: ROPE; newRName: MailBasics.RName; thisCred: MailUtils.Credentials _ cL.first; thisrName: MailBasics.RName _ thisCred.rName; MailRetrieve.NewUser[myRetrieveHandle.mHandle, thisrName, thisCred.password]; loggedInName _ MailUtils.GetLoggedInUser[thisrName.ns]; newRName _ [thisrName.ns, loggedInName]; userRNameList _ CONS[thisrName, userRNameList]; userRNameList _ CONS[newRName, userRNameList]; simpleUserName _ CONS[MailUtils.LocalNameFromRName[thisrName], simpleUserName]; simpleUserName _ CONS[MailUtils.LocalNameFromRName[newRName], simpleUserName]; sendingCredentials _ CONS[NEW[MailSend.SendingCredentialsRec _ [thisCred, TRUE, thisrName]], sendingCredentials]; sendingCredentials _ CONS[NEW[MailSend.SendingCredentialsRec _ [thisCred, TRUE, newRName]], sendingCredentials]; ENDLOOP; }; lastStateReported: MailRetrieve.MBXState ¬ unknown; WatchMailBox: PROC[mHandle: MailRetrieve.Handle, newState: MailRetrieve.MBXState] = { status: ROPE ~ SELECT newState FROM badName => "Your user name is invalid, please log in", badPwd => "Your password is invalid", cantAuth => "Can't check your credentials at this time", userOK => "Your credentials are OK", allDown, someEmpty, allEmpty, notEmpty => NIL, ENDCASE => "Bad MBXState!"; mailViewer: Viewer ~ PeanutWindow.FindMailViewer[PeanutProfile.activeMailFile]; IF newState # unknown THEN { IF newState = notEmpty OR NOT (PeanutProfile.automaticNewMail AND (mailViewer = NIL OR mailViewer.iconic)) THEN PeanutWindow.SetNewMail[newState=notEmpty]; IF status # NIL THEN { PeanutWindow.OutputRope["\n"]; PeanutWindow.OutputRope[status]; }; IF newState = notEmpty AND lastStateReported # notEmpty AND PeanutProfile.automaticNewMail THEN { mailViewer: Viewer ~ PeanutWindow.FindMailViewer[PeanutProfile.activeMailFile]; IF mailViewer = NIL OR mailViewer.iconic THEN Process.Detach[FORK ReadMail[]]; }; }; }; ReadMail: ENTRY PROC = { ENABLE UNWIND => NULL; numRetrieved: INT; CedarProcess.SetPriority[background]; IF (numRetrieved ¬ InternalGetNewMsgs[FALSE])=0 THEN RETURN; MessageWindow.Append[IO.PutFR1["New mail retrieved: %g", IO.time[]], TRUE]; }; MessageState: TYPE = { noMore, wasArchived, wasDeleted, OK, retrieveFailed } ; AddNewMessages: PUBLIC PROC[ FinishedWithServer: PROC[BOOL] RETURNS[BOOL], mailViewer: Viewer] RETURNS[numRetrieved: INT, firstHeader: RefTextNode] = { serverKnown: BOOLEAN ¬ FALSE; mailDoc: RefTextNode ¬ TiogaOps.ViewerDoc[mailViewer]; xnsName: XNSAuth.Name ~ XNSAuth.GetIdentityDetails[XNSCredentials.GetIdentity[]].name; IF myRetrieveHandle = NIL THEN NewHandle[NIL]; -- Open the connection if it's closed. IF myRetrieveHandle.creds = NIL THEN { ReportRope["\n You need to do a login - quit out of Peanut, do xnslogin and restart\n"]; RETURN[0, NIL]; }; SELECT MailRetrieve.MailboxState[myRetrieveHandle.mHandle] FROM badName => ReportRope["\nSome mailbox reported badName - possibly no mailBox\n"]; badPwd => ReportRope["\nSome mailbox reported badPwd\n"]; cantAuth => ReportRope["\nSome server not found\n"]; ENDCASE; --ok to try numRetrieved ¬ 0; ReportRope[IO.PutFR["\nCheck for new mail for %g, %g", [rope[myRetrieveHandle.creds.first.rName.name]], [time[BasicTime.Now[]]]] ]; DO -- Loops over servers. m, formatting: ROPE; messageState: MessageState; timeStamp: MailBasics.Timestamp; sender: RName; messages: CARDINAL ¬ 0; -- the number of messages read from server. archivedReported: BOOLEAN ¬ FALSE; -- seen an archived message? noMore: BOOLEAN; -- TRUE if no more servers. serverState: MailRetrieve.ServerState; -- The state of the server. headerNode: RefTextNode; serverName: MailBasics.RName; headerNode ¬ TiogaOps.LastChild[mailDoc]; [noMore, serverState] ¬ MailRetrieve.NextServer[myRetrieveHandle.mHandle]; IF noMore THEN EXIT; -- Last server? Then done. serverKnown ¬ TRUE; serverName ¬ MailRetrieve.ServerName[myRetrieveHandle.mHandle]; ReportRope[IO.PutFR["\n(%g) %g: ", [atom[serverName.ns]], [rope[serverName.name]] ] ]; IF serverState # notEmpty THEN { IF serverState = empty THEN ReportRope["empty"] ELSE ReportRope["didn't respond"] ; LOOP; -- Skip to the next server. }; DO [messageState, m, formatting, timeStamp, sender] ¬ ReadMessageRecord[] ; SELECT messageState FROM noMore => EXIT ; wasArchived => IF NOT archivedReported THEN { archivedReported ¬ TRUE; ReportRope["(archived messages exist)"]}; OK => NULL; wasDeleted => NULL; retrieveFailed => EXIT; ENDCASE => ERROR; IF NOT (messageState = wasDeleted) THEN { DeleteTrailingCRs: PROC [m: ROPE] RETURNS [ROPE] = { len: INT ¬ Rope.Size[m]; WHILE len > 0 AND Rope.Fetch[m, len-1] = '\r DO len ¬ len-1; ENDLOOP; RETURN [Rope.Substr[m,0,len]] }; GetField: PROC[fieldName: ROPE] RETURNS[contents: ROPE] = { firstCH: CHAR ~ Rope.Fetch[m, 0]; start: INT ¬ IF ( firstCH = '\r ) OR ( firstCH = '\l ) THEN 1 ELSE 0; DO cr: INT ~ Rope.Index[s1: m, pos1: start, s2: "\r"]; IF cr=start THEN EXIT; IF Rope.Find[s1: m, s2: fieldName, pos1: start, case: FALSE]=start THEN { i: INT ¬ start+Rope.Length[fieldName]; IF Rope.Fetch[m, i]=': THEN { i ¬ i+1; IF Rope.Fetch[m, i]=' THEN i ¬ i+1; RETURN[Rope.Substr[m, i, cr-i]]; }; }; start ¬ cr+1; ENDLOOP; RETURN[NIL]; }; Truncate: PROC [contents: ROPE, maxLen: INT] RETURNS [ROPE] = { len: INT ~ Rope.Length[contents]; IF len=0 THEN RETURN[questionName] ELSE IF len<=maxLen THEN RETURN[contents] ELSE RETURN[Rope.Concat[Rope.Substr[base: contents, len: maxLen-3], "..."]] }; GetDateFromGMT: PROC[time: BasicTime.GMT] RETURNS[ROPE] = { unpacked: BasicTime.Unpacked = BasicTime.Unpack[time]; day: NAT = unpacked.day; month: ROPE = Convert.RopeFromUnpackedTime[unpacked, months, months]; year: NAT = unpacked.year; RETURN[IO.PutFR["%2g-%g-%02g", IO.int[day], IO.rope[month.Substr[len: 3]], IO.int[year MOD 100]]]; }; AppendNextMessage: PROC = { StripTail: PROC [tail: ROPE] RETURNS [found: BOOL] ~ { i: INT ~ headerName.Length[] - tail.Length[]; IF i > 0 AND headerName.Fetch[i - 1] = ': AND Rope.EqualSubstrs[s1: headerName, start1: i, s2: tail, case: FALSE] THEN { headerName ¬ headerName.Substr[len: i - 1]; RETURN [TRUE]; } ELSE RETURN [FALSE]; }; headerDate, headerName, headerSubject, header: ROPE; headerNode ¬ TiogaFileOps.InsertNode[headerNode, FALSE]; IF firstHeader=NIL THEN firstHeader ¬ headerNode; headerDate ¬ GetDateFromGMT[MailUtils.GetTimeFromPostmark[timeStamp]]; IF IsUser[sender.name] THEN { headerName ¬ Rope.Concat["To: ", GetField["To"]]; TiogaFileOps.SetFormat[headerNode, "sentHeader"]; } ELSE { headerName ¬ GetField["From"]; IF headerName.IsEmpty[] THEN headerName ¬ GetField["Sender"]; TiogaFileOps.SetFormat[headerNode, "receivedHeader"]; }; IF StripTail[xnsName.organization] THEN [] ¬ StripTail[xnsName.domain]; headerName ¬ Truncate[headerName, 25]; headerSubject ¬ GetField["Subject"]; header ¬ Rope.Cat[ Rope.Concat["\t", headerDate], Rope.Concat["\t", headerName], Rope.Concat["\t", headerSubject] ]; TiogaFileOps.SetContents[headerNode, header]; IF PeanutProfile.commentNewHeaders THEN TextEdit.PutComment[headerNode, TRUE]; IF formatting=NIL THEN { messageNode: RefTextNode ~ TiogaFileOps.InsertNode[headerNode, TRUE]; TiogaFileOps.SetContents[messageNode, DeleteTrailingCRs[m]]; TiogaFileOps.SetFormat[messageNode, Atom.GetPName[PeanutProfile.plainTextMessageFormat]]; } ELSE { messageRoot: RefTextNode ¬ NIL; messageRoot ¬ TiogaIO.FromPair[[m,formatting] ! UNCAUGHT => CONTINUE]; IF messageRoot=NIL THEN messageRoot ¬ TiogaIO.FromRope[Rope.Concat[m, "\r*** WARNING: Tioga formatting was corrupted - reverting to plaintext (PeanutRetrieveImpl.AppendNextMessage) ***"]]; [] ¬ EditSpan.Move[ destRoot: TextNode.Root[headerNode], sourceRoot: messageRoot, dest: TextNode.MakeNodeLoc[headerNode], source: TextNode.MakeNodeSpan[TextNode.FirstChild[messageRoot], TextNode.LastWithin[messageRoot]], where: after, nesting: 1]; -- Should this use an event?? }; ViewerOps.SetNewVersion[mailViewer]; IF ( messages ¬ messages + 1) MOD 10 = 0 THEN ReportRope[IO.PutFR1["%g ", [integer[messages]]] ] ELSE ReportRope["."]; }; TiogaOps.Lock[mailDoc]; AppendNextMessage[ ! UNWIND => TiogaOps.Unlock[mailDoc] ]; TiogaOps.Unlock[mailDoc]; }; ENDLOOP ; -- Finished reading messages from this server. IF FinishedWithServer[messageState#retrieveFailed] THEN { IF flushMsgs THEN MailRetrieve.Accept[myRetrieveHandle.mHandle ! MailRetrieve.Failed => { ReportRope["\nFlush of remote messages failed; you may get these messages again"]; CONTINUE }] ELSE ReportRope["\n Messages not flushed - flushMsgs is FALSE"]; }; IF messageState#retrieveFailed THEN ReportRope[ IO.PutFR1[": retrieved %g messages.", [integer[messages]] ]]; numRetrieved ¬ numRetrieved + messages; ENDLOOP ; -- End of servers loop, exit. IF NOT serverKnown THEN ReportRope[" No mail boxes"]; }; IsUser: PROC[this: ROPE] RETURNS[yes: BOOL] = { FOR rL: MailBasics.RNameList ¬ userRNameList, rL.rest UNTIL rL = NIL DO IF this.Equal[rL.first.name, FALSE] THEN RETURN[TRUE]; ENDLOOP; FOR rL: LIST OF ROPE ¬ simpleUserName, rL.rest UNTIL rL = NIL DO IF this.Equal[rL.first, FALSE] THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE] }; bogusItems: INT ¬ 0; ReadMessageRecord: PROC RETURNS[ messageState: MessageState, m, formatting: ROPE ¬ NIL, timeStamp: MailBasics.Timestamp, sender: RName] = { ENABLE MailRetrieve.Failed --[why: FailureReason, text: ROPE]-- => { ReportRope[SELECT why FROM $communicationFailure => "Communication failure", $noSuchServer => "No such server", $connectionRejected => "Connection rejected", $badCredentials => "Bad credentials", $unknownFailure => "Unknown failure", ENDCASE => "Undefined error"]; IF text.Size[]>0 THEN { ReportRope[" -- "]; ReportRope[text] }; GOTO thisFailed; }; msgExists, archived, deleted: BOOLEAN; sysMessage, header, formattedHeader, bodies: ROPE ¬ NIL; attachments, unknowns: ROPE ¬ NIL; [msgExists, archived, deleted] ¬ MailRetrieve.NextMessage[ myRetrieveHandle.mHandle ]; IF archived THEN messageState ¬ wasArchived ELSE messageState¬ OK; IF deleted THEN { messageState ¬ wasDeleted; RETURN}; IF NOT msgExists THEN { messageState ¬ noMore; RETURN}; [timeStamp, sender, ] ¬ MailRetrieve.StartMessage[myRetrieveHandle.mHandle]; [m, formatting, , , ] ¬ MailMessage.ReadOneMessageX[myRetrieveHandle.mHandle, timeStamp, sender.name]; EXITS thisFailed => messageState ¬ retrieveFailed; }; PeanutEnableFlushMsgs: Commander.CommandProc = { flushMsgs ¬ TRUE }; PeanutDisableFlushMsgs: Commander.CommandProc = { flushMsgs ¬ FALSE }; Commander.Register["PeanutEnableFlushMsgs", PeanutEnableFlushMsgs]; Commander.Register["PeanutFlushMsgs", PeanutEnableFlushMsgs]; Commander.Register["PeanutDisableFlushMsgs", PeanutDisableFlushMsgs]; Commander.Register["PeanutDontFlushMsgs", PeanutDisableFlushMsgs]; END. ˜PeanutRetrieveImpl.mesa Copyright Σ 1984, 1985, 1986, 1989, 1990, 1992 Xerox Corporation. All rights reserved. Created by Paxton, April 1, 1983 2:17 pm Last edited by Bill Paxton, May 12, 1983 4:11 pm Last edited by: Pausch, July 14, 1983 3:26 pm Last Edited by: Gasbarro June 16, 1986 5:03:42 pm PDT Willie-Sue, December 14, 1989 3:08:25 pm PST Bertrand Serlet August 30, 1988 10:58:11 am PDT Pier, July 6, 1992 4:39 pm PDT Last changed by Pavel on March 7, 1990 5:48 pm PST Michael Plass, January 10, 1992 3:41 pm PST Doug Wyatt, January 14, 1992 4:02 pm PST Jules Bloomenthal July 1, 1992 1:55 pm PDT Willie-s, October 26, 1992 1:54 pm PST ************************************************************************ Calling SetNewMail[FALSE] here is a crock, and not strictly accurate (if, for example, messages with attachments are not flushed), but it's correct more often than not, and the next poll will restore the truth. This problem should really be fixed in the MailRetrieve stuff (which should call WatchMailBox immediately when a mailbox has been flushed) but messing with XNSMailUser looked too risky to me. -- DKW reads any new mail SaveViewer should return TRUE only if the save operation finished, and was successful; in other words, the retrieved messages have really been safely stored, and it's ok to tell the mail server to flush them. don't do remote flush if NOT retrieveOK *********************************************************************** This establishes a retrieve connection, and sets up a Mail Polling proc This closes the connection, and invalidates the connection handle. Establish a new user on this connection. This is called whenever the condition of the mailbox changes Adjust new-mail flag; don't put it down if the active msg set is iconic and using automaticNewMail Report abnormal states to the user Perform automatic mail retrieval if appropriate * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * This is the routine that actually reads the mail & makes the log entry. It calls FinishedWithServer to commit the log before it flushes any particular mail server. Cycle through the servers, until you find one that has mail. If it has mail, then go for it. Step through the servers. Returns a string of the form dd-mmm-yy (for example, 17-Nov-83) Flush the mailbox if desired, we've stashed the messages. PeanutWindow.OutputRope[IO.PutFR1["\nIsUser[\"%q\"] ...", [rope[this]]]]; PeanutWindow.OutputRope[IO.PutFR1["\n\t\"%q\"", [rope[rL.first.name]]]]; PeanutWindow.OutputRope[IO.PutFR["\n\t\"%q\"", [rope[rL.first]]]]; This routine reads the messages on this connection, returning messageState = noMore when there aren't any more. Κk–(cedarcode) style•NewlineDelimiter ˜codešœ™Kšœ ΟeœL™WKšœ(™(K™0Kšœ-™-K™5K™,K™/K™K™2K™+K™(K™*K™&—K˜šΟk œ=žœΦ˜ K˜—KšΠlnœžœž˜!Kšžœ>žœΥ˜œKšžœ0˜7Kšœž˜K˜KšœH™HKšžœžœžœ˜Kšžœžœžœžœ˜Kšœžœ˜$Kšœžœ˜K˜Kšœ žœžœ ˜!Kšœ ž œ˜&K˜Kšœ žœžœ˜Kšœžœ˜K˜KšΟn œž œžœ#˜AK˜š   œžœžœžœžœžœ˜5Kšžœžœžœ˜K˜šžœ žœžœ˜1Kšœžœ‚™š—Kšœ˜K˜—š œžœžœžœžœžœžœ˜SKšœ™Kšœ˜Kšœžœžœ˜K˜Kšœ˜K˜š  œžœžœžœžœ˜HKšœ˜šœžœ$˜0Kšœžœ³™Π—Kšœžœ ˜Kšœžœ ™'Kšžœžœ žœžœ žœžœžœ˜bKšœ˜—K˜K˜NK˜Kšžœžœ˜Kšœ,žœ˜4—Kšžœžœ žœž˜Ašžœ˜šžœžœž˜ šœ)˜)Kšœ1˜1Kšœ*˜*Kšœ˜—Kšžœžœ˜—Kšœ˜—Kšžœžœžœ;˜LKšžœžœžœ ˜BKšœ˜K˜—KšœG™GK˜KšœžœΟc-˜LKšœžœžœ˜Kšœ"‘!˜CKšœžœžœ˜.šœžœžœ˜!Kšœžœžœ˜%K˜K˜—K˜Kš  œžœžœžœžœ ˜4šœG™GK˜K˜K˜K˜—Kš œžœžœ˜"šœB™Bšžœžœžœ˜Kšœ@žœ˜E—K˜K˜—š  œžœžœžœžœ ˜-Kšœ)™)Kšžœžœžœ˜/K˜K˜—Kšœžœžœ˜1Kšœžœ#žœ˜AKš œžœžœžœžœžœ‘˜HK˜š  œžœžœžœ ˜(Kšœžœ(žœ˜8Kšœžœ)žœ˜:Kšœžœžœ˜%K˜šžœžœž˜Kšœžœ3˜=K˜2Kšžœ/˜6—Kšœžœ˜+K˜SK˜Kšœžœ˜Kšœžœ˜Kšœžœ˜š žœžœžœ(žœžœž˜HKšœžœ˜Kšœ˜Kšœ+˜+Kšœ-˜-KšœM˜MKšœ œ˜7Kšœ(˜(Kšœžœ˜/Kšœžœ˜.Kšœžœ:˜OKšœžœ9˜Nšœ˜Kšžœžœ-žœ#˜\—šœ˜Kšžœžœ-žœ"˜[—Kšžœ˜—K˜—K˜K˜3K˜š  œžœC˜UKšœ<™<šœžœ˜šžœ ž˜Kšœ6˜6Kšœ%˜%Kšœ8˜8Kšœ$˜$Kšœ*žœ˜.Kšžœ˜——K˜OK˜šžœžœ˜šœb™bš žœžœžœ!žœžœžœž˜oKšœ+˜+——K˜™"šžœ žœžœ˜Kšœ˜Kšœ ˜ K˜——K˜™/šžœžœžœ žœ˜aK˜Ošžœžœžœž˜-Kšœžœ ˜ —K˜——Kšœ˜—Kšœ˜K˜—š œžœžœ˜Kšžœžœžœ˜Kšœžœ˜Kšœ%˜%Kšžœ$žœžœžœ˜žœžœ˜xK˜+Kšžœžœ˜Kšœ˜—šž˜Kšžœžœ˜—K˜—Kšœ/žœ˜4K˜Kšœ1žœ˜8Kšžœ žœžœ˜1K˜Fšžœžœ˜K˜1Kšœ1˜1Kšœ˜—šžœ˜K˜šžœž˜K˜ —Kšœ5˜5K˜—šžœ!ž˜'K˜—K˜&K˜$˜Kšœ˜Kšœ?˜?Kšœ˜—Kšœ-˜-šžœ žœ˜(Kšœ žœ˜&—šžœ ž˜šžœ˜Kšœ?žœ˜EKšœ<˜