From 7f0355ec67387eb9766ddf5d7d4a92135d73652a Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sat, 26 Nov 2022 22:39:56 +0000 Subject: [PATCH] core: only send voice messages without acceptance (#1444) * core: only send voice messages without acceptance * remove some unnecessary changes * update * refactor receiveInlineMode --- src/Simplex/Chat.hs | 50 +++++++++++--------- src/Simplex/Chat/Controller.hs | 7 ++- src/Simplex/Chat/Mobile.hs | 1 + src/Simplex/Chat/Options.hs | 8 ++++ src/Simplex/Chat/Protocol.hs | 6 +++ src/Simplex/Chat/View.hs | 1 + tests/ChatClient.hs | 1 + tests/ChatTests.hs | 82 +++++++++++++++++++-------------- tests/fixtures/logo.jpg | Bin 0 -> 32080 bytes 9 files changed, 98 insertions(+), 58 deletions(-) create mode 100644 tests/fixtures/logo.jpg diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index c0c8e21da3..803dedd6d9 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -132,8 +132,9 @@ createChatDatabase filePrefix key yesToMigrations = do pure ChatDatabase {chatStore, agentStore} newChatController :: ChatDatabase -> Maybe User -> ChatConfig -> ChatOpts -> Maybe (Notification -> IO ()) -> IO ChatController -newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, tbqSize, defaultServers} ChatOpts {smpServers, networkConfig, logConnections, logServerHosts} sendToast = do - let config = cfg {subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers} +newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, tbqSize, defaultServers, inlineFiles} ChatOpts {smpServers, networkConfig, logConnections, logServerHosts, allowInstantFiles} sendToast = do + let inlineFiles' = if allowInstantFiles then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False} + config = cfg {subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles'} sendNotification = fromMaybe (const $ pure ()) sendToast firstTime = dbNew chatStore activeTo <- newTVarIO ActiveNone @@ -305,7 +306,7 @@ processChatCommand = \case where setupSndFileTransfer :: Contact -> m (Maybe (FileInvitation, CIFile 'MDSnd, FileTransferMeta)) setupSndFileTransfer ct = forM file_ $ \file -> do - (fileSize, chSize, fileInline) <- checkSndFile file 1 + (fileSize, chSize, fileInline) <- checkSndFile mc file 1 (agentConnId_, fileConnReq) <- if isJust fileInline then pure (Nothing, Nothing) @@ -351,7 +352,7 @@ processChatCommand = \case where setupSndFileTransfer :: GroupInfo -> Int -> m (Maybe (FileInvitation, CIFile 'MDSnd, FileTransferMeta)) setupSndFileTransfer gInfo n = forM file_ $ \file -> do - (fileSize, chSize, fileInline) <- checkSndFile file $ fromIntegral n + (fileSize, chSize, fileInline) <- checkSndFile mc file $ fromIntegral n let fileName = takeFileName file fileInvitation = FileInvitation {fileName, fileSize, fileConnReq = Nothing, fileInline} fileStatus = if fileInline == Just IFMSent then CIFSSndTransfer else CIFSSndStored @@ -1160,18 +1161,18 @@ processChatCommand = \case contactMember Contact {contactId} = find $ \GroupMember {memberContactId = cId, memberStatus = s} -> cId == Just contactId && s /= GSMemRemoved && s /= GSMemLeft - checkSndFile :: FilePath -> Integer -> m (Integer, Integer, Maybe InlineFileMode) - checkSndFile f n = do + checkSndFile :: MsgContent -> FilePath -> Integer -> m (Integer, Integer, Maybe InlineFileMode) + checkSndFile mc f n = do fsFilePath <- toFSFilePath f unlessM (doesFileExist fsFilePath) . throwChatError $ CEFileNotFound f ChatConfig {fileChunkSize, inlineFiles} <- asks config fileSize <- getFileSize fsFilePath let chunks = - ((- fileSize) `div` fileChunkSize) - pure (fileSize, fileChunkSize, inlineFileMode inlineFiles chunks n) - inlineFileMode InlineFilesConfig {offerChunks, sendChunks, totalSendChunks} chunks n + pure (fileSize, fileChunkSize, inlineFileMode mc inlineFiles chunks n) + inlineFileMode mc InlineFilesConfig {offerChunks, sendChunks, totalSendChunks} chunks n | chunks > offerChunks = Nothing - | chunks > sendChunks || chunks * n > totalSendChunks = Just IFMOffer - | otherwise = Just IFMSent + | chunks <= sendChunks && chunks * n <= totalSendChunks && isVoice mc = Just IFMSent + | otherwise = Just IFMOffer updateProfile :: User -> Profile -> m ChatResponse updateProfile user@User {profile = p@LocalProfile {profileId, localAlias}} p'@Profile {displayName} | p' == fromLocalProfile p = pure CRUserProfileNoChange @@ -2200,7 +2201,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = case featureProhibited forContact ct content of Just f -> void $ newChatItem (CIRcvChatFeatureRejected f) Nothing _ -> do - ciFile_ <- processFileInvitation fileInvitation_ $ \db -> createRcvFileTransfer db userId ct + ciFile_ <- processFileInvitation fileInvitation_ content $ \db -> createRcvFileTransfer db userId ct ChatItem {formattedText} <- newChatItem (CIRcvMsgContent content) ciFile_ when (enableNtfs chatSettings) $ showMsgToast (c <> "> ") content formattedText setActive $ ActiveC c @@ -2210,10 +2211,10 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = toView . CRNewChatItem $ AChatItem SCTDirect SMDRcv (DirectChat ct) ci pure ci - processFileInvitation :: Maybe FileInvitation -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> IO RcvFileTransfer) -> m (Maybe (CIFile 'MDRcv)) - processFileInvitation fInv_ createRcvFT = forM fInv_ $ \fInv@FileInvitation {fileName, fileSize} -> do + processFileInvitation :: Maybe FileInvitation -> MsgContent -> (DB.Connection -> FileInvitation -> Maybe InlineFileMode -> Integer -> IO RcvFileTransfer) -> m (Maybe (CIFile 'MDRcv)) + processFileInvitation fInv_ mc createRcvFT = forM fInv_ $ \fInv@FileInvitation {fileName, fileSize} -> do chSize <- asks $ fileChunkSize . config - inline <- receiveInlineMode fInv chSize + inline <- receiveInlineMode fInv (Just mc) chSize ft@RcvFileTransfer {fileId} <- withStore' $ \db -> createRcvFT db fInv inline chSize (filePath, fileStatus) <- case inline of Just IFMSent -> do @@ -2265,7 +2266,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = case groupFeatureProhibited gInfo content of Just f -> void $ newChatItem (CIRcvChatFeatureRejected f) Nothing _ -> do - ciFile_ <- processFileInvitation fInv_ $ \db -> createRcvGroupFileTransfer db userId m + ciFile_ <- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m ChatItem {formattedText} <- newChatItem (CIRcvMsgContent content) ciFile_ let g = groupName' gInfo when (enableNtfs chatSettings) $ showMsgToast ("#" <> g <> " " <> c <> "> ") content formattedText @@ -2318,7 +2319,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = processFileInvitation' ct@Contact {localDisplayName = c} fInv@FileInvitation {fileName, fileSize} msg msgMeta = do checkIntegrityCreateItem (CDDirectRcv ct) msgMeta chSize <- asks $ fileChunkSize . config - inline <- receiveInlineMode fInv chSize + inline <- receiveInlineMode fInv Nothing chSize RcvFileTransfer {fileId} <- withStore' $ \db -> createRcvFileTransfer db userId ct fInv inline chSize let ciFile = Just $ CIFile {fileId, fileName, fileSize, filePath = Nothing, fileStatus = CIFSRcvInvitation} ci <- saveRcvChatItem user (CDDirectRcv ct) msg msgMeta (CIRcvMsgContent $ MCFile "") ciFile @@ -2330,7 +2331,7 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = processGroupFileInvitation' :: GroupInfo -> GroupMember -> FileInvitation -> RcvMessage -> MsgMeta -> m () processGroupFileInvitation' gInfo m@GroupMember {localDisplayName = c} fInv@FileInvitation {fileName, fileSize} msg msgMeta = do chSize <- asks $ fileChunkSize . config - inline <- receiveInlineMode fInv chSize + inline <- receiveInlineMode fInv Nothing chSize RcvFileTransfer {fileId} <- withStore' $ \db -> createRcvGroupFileTransfer db userId m fInv inline chSize let ciFile = Just $ CIFile {fileId, fileName, fileSize, filePath = Nothing, fileStatus = CIFSRcvInvitation} ci <- saveRcvChatItem user (CDGroupRcv gInfo m) msg msgMeta (CIRcvMsgContent $ MCFile "") ciFile @@ -2339,11 +2340,13 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = showToast ("#" <> g <> " " <> c <> "> ") "wants to send a file" setActive $ ActiveG g - receiveInlineMode :: FileInvitation -> Integer -> m (Maybe InlineFileMode) - receiveInlineMode FileInvitation {fileSize, fileInline} chSize = case fileInline of - inline@(Just _) -> do - rcvChunks <- asks $ receiveChunks . inlineFiles . config - pure $ if fileSize <= rcvChunks * chSize then inline else Nothing + receiveInlineMode :: FileInvitation -> Maybe MsgContent -> Integer -> m (Maybe InlineFileMode) + receiveInlineMode FileInvitation {fileSize, fileInline} mc_ chSize = case fileInline of + Just mode -> do + InlineFilesConfig {receiveChunks, receiveInstant} <- asks $ inlineFiles . config + pure $ if fileSize <= receiveChunks * chSize then inline' receiveInstant else Nothing + where + inline' receiveInstant = if mode == IFMOffer || (receiveInstant && maybe False isVoice mc_) then fileInline else Nothing _ -> pure Nothing xFileCancel :: Contact -> SharedMsgId -> MsgMeta -> m () @@ -2408,6 +2411,9 @@ processAgentMessage (Just user@User {userId}) corrId agentConnId agentMessage = receiveInlineChunk ft chunk meta receiveInlineChunk :: RcvFileTransfer -> FileChunk -> MsgMeta -> m () + receiveInlineChunk RcvFileTransfer {fileId, fileStatus = RFSNew} FileChunk {chunkNo} _ + | chunkNo == 1 = throwChatError $ CEFileLargeSentInline fileId + | otherwise = pure () receiveInlineChunk ft chunk meta = do case chunk of FileChunk {chunkNo} -> when (chunkNo == 1) $ startReceivingFile ft diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index d188201422..7855c562c1 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -81,7 +81,8 @@ data InlineFilesConfig = InlineFilesConfig { offerChunks :: Integer, sendChunks :: Integer, totalSendChunks :: Integer, - receiveChunks :: Integer + receiveChunks :: Integer, + receiveInstant :: Bool } defaultInlineFilesConfig :: InlineFilesConfig @@ -90,7 +91,8 @@ defaultInlineFilesConfig = { offerChunks = 15, -- max when chunks are offered / received with the option - limited to 255 on the encoding level sendChunks = 6, -- max per file when chunks will be sent inline without acceptance totalSendChunks = 30, -- max per conversation when chunks will be sent inline without acceptance - receiveChunks = 8 -- max when chunks are accepted + receiveChunks = 8, -- max when chunks are accepted + receiveInstant = True -- allow receiving instant files, within receiveChunks limit } data ActiveTo = ActiveNone | ActiveC ContactName | ActiveG GroupName @@ -534,6 +536,7 @@ data ChatErrorType | CEFileImageType {filePath :: FilePath} | CEFileImageSize {filePath :: FilePath} | CEFileNotReceived {fileId :: FileTransferId} + | CEFileLargeSentInline {fileId :: FileTransferId} | CEInvalidQuote | CEInvalidChatItemUpdate | CEInvalidChatItemDelete diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index a3c12ce5c5..87210fb588 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -129,6 +129,7 @@ mobileChatOpts = chatCmd = "", chatCmdDelay = 3, chatServerPort = Nothing, + allowInstantFiles = True, maintenance = True } diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index e933ac2a3e..9f76a61f12 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -34,6 +34,7 @@ data ChatOpts = ChatOpts chatCmd :: String, chatCmdDelay :: Int, chatServerPort :: Maybe String, + allowInstantFiles :: Bool, maintenance :: Bool } @@ -126,6 +127,12 @@ chatOpts appDir defaultDbFileName = do <> help "Run chat server on specified port" <> value Nothing ) + allowInstantFiles <- + switch + ( long "--allow-instant-files" + <> short 'f' + <> help "Send and receive instant files without acceptance" + ) maintenance <- switch ( long "maintenance" @@ -144,6 +151,7 @@ chatOpts appDir defaultDbFileName = do chatCmd, chatCmdDelay, chatServerPort, + allowInstantFiles, maintenance } where diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index fc75850427..f45d8a07c6 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -265,6 +265,7 @@ cmToQuotedMsg = \case _ -> Nothing data MsgContentTag = MCText_ | MCLink_ | MCImage_ | MCVoice_ | MCFile_ | MCUnknown_ Text + deriving (Eq) instance StrEncoding MsgContentTag where strEncode = \case @@ -341,6 +342,11 @@ durationText duration = | n <= 9 = '0' : show n | otherwise = show n +isVoice :: MsgContent -> Bool +isVoice = \case + MCVoice {} -> True + _ -> False + msgContentTag :: MsgContent -> MsgContentTag msgContentTag = \case MCText _ -> MCText_ diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 6ae7c052cb..b5793e0262 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -1083,6 +1083,7 @@ viewChatError = \case CEFileImageType _ -> ["image type must be jpg, send as a file using " <> highlight' "/f"] CEFileImageSize _ -> ["max image size: " <> sShow maxImageSize <> " bytes, resize it or send as a file using " <> highlight' "/f"] CEFileNotReceived fileId -> ["file " <> sShow fileId <> " not received"] + CEFileLargeSentInline _ -> ["A small file sent without acceptance - you can enable receiving such files automatically with -f option."] CEInvalidQuote -> ["cannot reply to this message"] CEInvalidChatItemUpdate -> ["cannot update this item"] CEInvalidChatItemDelete -> ["cannot delete this item"] diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 11ab2771d7..3af1fcbad9 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -59,6 +59,7 @@ testOpts = chatCmd = "", chatCmdDelay = 3, chatServerPort = Nothing, + allowInstantFiles = True, maintenance = False } diff --git a/tests/ChatTests.hs b/tests/ChatTests.hs index 991ca8ecc1..ead53c08d2 100644 --- a/tests/ChatTests.hs +++ b/tests/ChatTests.hs @@ -1595,9 +1595,12 @@ testInlineFileTransfer = connectUsers alice bob bob ##> "/_files_folder ./tests/tmp/" bob <## "ok" - alice #> "/f @bob ./tests/fixtures/test.jpg" + alice ##> "/_send @2 json {\"msgContent\":{\"type\":\"voice\", \"duration\":10, \"text\":\"\"}, \"filePath\":\"./tests/fixtures/test.jpg\"}" + alice <# "@bob voice message (00:10)" + alice <# "/f @bob ./tests/fixtures/test.jpg" -- below is not shown in "sent" mode -- alice <## "use /fc 1 to cancel sending" + bob <# "alice> voice message (00:10)" bob <# "alice> sends file test.jpg (136.5 KiB / 139737 bytes)" -- below is not shown in "sent" mode -- bob <## "use /fr 1 [
c<}Mc`~0Y#5^}&$MA(8XZgTvMhEO3%{I7lk&oxQK6pW&l+au8&jN} zh=ScCx7R#gJiDDH=EG#8-R|9*{{Tz3nPolXB!=qP9gVREn&SqiS7KaH*_L85!~9gYI5r<{UeO?iL+L`6~c&UMgq*?PufAGhgrIX3%#!Gwq_ za={5-toP(9rfagDI3$P$;M2TH>AQaf}XraH|H(>KGy!7#r+Fpzf=N?x>P*dEQF zBQbQQD{?OO4SRRimzO?kts?M5j?4Dq8E$A*sLgAcG}rN59vlc0J*(JxH{qBCK9hP0 zrDp6Q#W?*720-J gY{U zwmmVbwS<)3Gh0t?uGk9trCL;4vKb@=ffakOCGz5b{0Qnw3%+Xp(JWCoS I;V7W(l{{-kII?N1#Cdsk$Sea{z5E`POh# zwnD!v$Mjv6j8%>($#x|+c;7UHXo&<1>X{Rjvqn?Rd(2lNoE5HWF=IejN6c8)7#6o- zL-2v08c~u!Pm5CgDNnfvFE3Fj`rxX90hi_Ys Pz!*w4I(+`t} zokN(XYjv3nl`cnq@~y9e4$5!gh|DW`^`h0QXb`gAHw*lHRnHG$jnOM~0>cYH!ZT4# z*{V44WvNkrx_-7)m#_A+iJFq1d=*%@?Wwu6i0D}Cq=)g^p??nTq^DL96Hf%j=0Z|| z1GRk_- D6Bs)Frt~{# z5U9ASl0|rI*neXX3Qv(PuBvx+xMo9@*h`L_S#v$8`d7vRP&UBxiT{PCGH10gr=%<4 z)8SWGEfRe4L$wJj^%EqvzHr@?ZU4c)ch_Icnd~H=1F+Vp9uux_&d#|bxZ9KaN4MJ= zb$n` YrKCJOCG)JZqaJ-pnrn M@2d+tAxH`;kPOLcMtt zpyvR72WB!0X>n;W;oxWVIpBsoR|f+r1^Wx;@}Z{b?7I(l_Ut&hd&m6FWMGaqzuZO^ zk|S!D2YX`<3-O{pm^RH`3Miwe3%}SVh9llsi&*~ATA6q=T0Td$uJFjyI3)lj_+eur zRb}Y($}$;%ceoBsm&YXv4-z@RuBmwS$QLXf!Ms){x{gF<8BykqWogU7z%`2t8K4N^ zWp}Oc9Xe+^7Y2qk^NqY49_v{P<+u+vhBJZcTEBFs_Oriv={%aO+sYLGxh=I_$YgE* z)vo*Rw3+AQrv$_7hUAYM@;67RmhB_C%{dWb6Oq>Dj;aum$+#SqiQ;lq$gTYxm2x5K z_5|gT8*ew9RT?z;KS^ii+QjuNRX1@P(Al%GMO3OuiqXe)kIp()TiP0EE;te`JrX9d zGw`Py&4x^o&(xo3^L8BYoC7YMQ25qscZWJ~iZfvd57|jeAy(?vih23}{TvLuDK5Uu z>y0V8-=yP(*SG8p3(`tRJUs_^Y&@~yu)9UJBwoXAwy9VL=UUOG`dioj!j)}0*9)G_ zLIm0fwLzAl_wCdZ?5(Rp7k@~7zPsW0iWnl*O>pJJ`+fm35-fwo6>k?iR<$`tc6V#c zutlI?uF<2?lSi)E&c+A?BF!nirHOL@d!Te=u@e1@<|zL+v~sd-55JouF?FW1a?bP( z`xq^{dz?ttodYg@u`A`*xJA6Ae``-_g)95sSZ12dk?Bs53%x2$q0cyp`lTPYK}^(L z9$JLIwSt6*)bk?MLh+UR3_^s7gqdj>RzDQm0I$ac`VY905nnA{9~p#m;Yvuh*q&!t znqh5tvmd)XdpDkWSzm5g1XJk=FI(2!MxwJ=Wa>pm>Vq9?j@glBnyI5q&}=(mVrvPM z0 ))!+g%yM!JJgdTNM`iPv?GSH*VwR9gdd2SJE6Ay5GF9MD~M4v3NS795so9)TqE zx~L>N#+AeSocvOA`B>t5pYWp`m}1K&!^&S&NQ06iM7mQ|m7ghyYcSA9W5>HHQhL=j zan#)mJg0udg-F6r+(NWQD_-LHQsE83^>Xo@+s-G?D@zxjLH9o}JkqgJkkB@Mele{9 zqkh)=p{^YqfLxLlzZ{B{spQt+jW9)Its+qTXE~f^KXPmzxMUSP(0nnD0htwd7mj`) zY+);eh)Qv??UeeVrolDa1%l#~f4rg>DavfC(U{baKg)UOd<4E=7O}_O?Uz@xhK}t1 z-0NZq7vzM^ux-OmfF!0vL&BX6POYvA(l6WB(>^@VO&hbsJ*&1@*({fg@)iR*ET_$Y z_zQ3vT;DrnYs0mpIoFl^_wse=exi%|blJ-N3tFa@bhOzB;claaQmazYFD(NPrYput zPKHQF?HduW#g2kspHCuBsd13>B^L3CxLQ5SV4@Dw$ZM2?bfuoeMCX!wvsoGyU#A}G zq4U>3QT_C+A~7~orlcNxwRw0)Ws>H9FXYZA|0lH40!X!o!)NeQ#uF>(?edBT6|ab5 z*uzHZE)7MSuyLEa qJFSQ9U?jUEEkyiMX=e7 zX){QT*4O?u$<`q+=ojRPX875NBU@KCQFABN0EEEl#(vE4UBXP_se&p?DqSVq|C72i z7OArxyrO4Xuj?FK=suWZYi|%=YIwb#MZxBpwS4s#&ZbajT63fC+4#KfMXpdTVtXs$ zr2m*@G5S>Zon2V&;ox`H#J~L)o!hHgufFwA1?O{eCCHh+yz29Kbb$Vo(({IMz>JEv z*Xxlgc{#evP>cMSXn6s8-aKa4Yt5#m2KBU)s9;c@m1!zxDy%bCbBULquohdZ!HN&9 zWJ+Dlw1a53^R}#k^E9g}?=!J$r72y8N2-_)?a`V }Td-TxrPSZXU5Qi3W)Vt{J|s+ZbWcoyZ#Zef|ehkzwL!#s2S2r-IID_-bV} z7iyO1KAfMW>Rva#>PwmUSrHdYWRGbZfz&2{4hW9wd7xxyJBIYYZQ`X5&H>*0=Kzwp zQS7ZV GtdGCncJO>y`!|KEo z3YHEY2?~iqlUOzkRn+NzZDO86s*)If5EZ59&{s`I+-J=;pE8#krtxvGJGtX;Bg|7@ zIK;LK5*1sQc|yQMrC5l~nUCF!)DH!98}Q^0-pzA>aYswoFSei{hBy4X|4 {yPiYqFE<^n3Cr(<}! zbAUYl**BgXSN1uuW5%j-$(K(qa$I(J8Ot*8ZM4lFa6-Lkt2RYA86rfw)41rhqZk PuXR{ts|cfA0U!U)k^L>gr F)Wd) z>KDnbMNvh<$w?hL3v2{n mm!cYw z)+89-{*AkDDATfY +)JeLBrnuRaMr*3IMw%<2b zMNbk6uW7>_%rU4V%HQ`Gd+D0?g5pDy8^!VSq9uWA%6TJs4`BuU^rRgusK8oYwq0gO z`d}H`MggdKjS$0Nu;*2Ov%aTIga5oKzn`n%TVJ5h^Q!bdhgPa3h4ytYlUYgMmSz&_ zN23>S$m^F=^~_%W+|XX##P#-b&2;0m(mgPbj{c;hL!Em|YU2;7Tl2eJ0rc~z%7YQp z?8gCZEZpG^_Uv$V=$@AEgr`WipVwfjHOPadcv!_WmBRUQRBI_*Lirr<5}JYy=l=wC zi1Xjpw#s&{pXb)*{eU6{sIshE#3fLGfy3}-dHO2zlj3DJ%t^*GJ38-cG;5HJyqnAb zV`_BhZPNYajzr$OV`QGwyJP5?P@Req(847N78I)RQJ(kILC6?9%Ox6B5DTH?*1sS3 zdwQfS&V92PT5(kwp}Ny)@kx%Bm>-?bHE6VeK#85GnqhugFj^HAe>PgE#h7%%U6s#% zM%(lF>BP<%rTL<1ol^tUX5>niwGp-$W3+Zk^mO%?ZEJYJ%RvGohBh QL z^0mFNPfJcd3OP9v>S%7t?0Bn;h0B~;B|$s`j602g58k2`PMH+jDUVi^4V(D`)hQ%+ zFh{u?F4C>}bMf=Z`dK%biqO*en1y>Zu2$7dpl8lVT+Q90f4VRm^|I46biDH^l*SB0 z*C7?LQdWW|LS&Rr4aHb2wrNRw$Nx(nJ)i#f;MElDSi*D=EPyZ10fa|V0Jk7}v07N{ zB9|KEPF}PFm6~RSdy7#F *F-|*G z?;m0 &Z|=sk2r jV|LcIqJ`^x zZMDfED!9y3_?AHbQ{~O(a7X>*GWB&s3BANS@OrcZgNeO@h*|t9{Qd0u_Jqmy&>o$J zbF?K~t*%2r&%Xwv{oW!~^{oMlKg-X($?&>@$cQh(S*pZy;I7sm@mYxb9FVs)iW_}4 zM!m~+4!C>{pp*s$ wzLIJQ zevRa67*-dcfxy$X+gIyt@^Dh@zTvRWX0m2fQ~Cw94!KS eFR|S+7#l|R!V+=2 zhmCYLApJ*Th%m%0SVdm#M53B__xGddE8?tdLPUMJ9?B=TXKQCUf@X784t? 6FuMMSJ;F^o99&_(;5x8&gLt-Mi1!z-LyF3J8V0vRHHf(4Aq3)@chZ! zxa{RER_X&FU7uZ5t_L!1$_VoA?vS5Ps41ZC_Dj~58?dF*vNSZ B%9f+Cy%>W9e?Jt>2=L|EE%HCMVoN|;n1>58 zvyyprtj7c=d9#3`4`lX9BG~GB@g)kH+uPBS0g<-fluBt#u@k=eS}epba7i}CRbbW3 z&OFXxwiVm|*G$SILS Kx-po6=-y!=8+!8!pAx5L^ jKGi0VFXG-x` !GSd5l{@C1QsYSOnz z`VrNk2JtXnpg48}Xxrx>;3?MR===G(d37qnd3z&3OD4j@q8yaSnRw@e6qm|~?^kV3 zly&`W4sGs*!xEZ#ZBSe-qHrQX)FkwvsGQeQyAVfq0@ktZTqXt`7Lb~kH4`1UPhE>i zzwoX}5UCyD4N|o6753AnM?)yL7rYsQx! TA`jrS@lC2uI@OXTOXu23iW#luYngzu^$`qSrS}r^RchaHPVTeYZ z{>pXDKyKaKH1kimY6vRO1GQC3L8i-QT2;pr-tV}9A~7a$MM ;Hh`NPC*0&Oi&UE4 zKCg0FNGFt3Ap?EW9ez9Jn@5)2$dCgQiLd7{uCyE~)t4m`0C^A1zTdLw72@n4$4}d+ zII3Rv`O`UI= vi+MdmOse?l@hqgKWjLwTn{y*H$ 1$n&T}KVAY9r>H!DR(Qpfq#s zG9J}>M*$lyYuT 0y;=n&`HZ%LMIzJ;W;gxmv{ixt$MS*~Z z`}jFP!_L%fQlVVym-$p>WxcvSsI6@h!lQ52-Zlj>&<9Oc6tzzt7}%Ikx;9Tj!ko-I znwlmR!W=-&EtB O4xa5MAw?^EBGvuh#E99d|6(Rx`4s`409xVv$_ zo=kxj; 1v Y;u1NQaJVJ=Z> z`v7ms6<;2JTWAjAf}*>J1UGJK_vrNS0&lYDd%c)dqj4O(SN;wC)jMu3J_NB|U>ztl zO1&bi8C4imm#5v{Qx4rJ^A49-W_#17iU08zLHWSwLf(?dWF7 LrrT$ti+GcO-qm2QuTNIjoFRei`;1z|&JWSCl>(kEF8G01X; zwi#0Myy_YI-G);8o6uCID8!(orXh4K79ETuS_U_YvMh7#?wO%MInNYAwRtNK_$Wdt zMfQCC%3ZV0<*h4W0&llM5lTxR*7Ps|MMtoBg~+T6ilwOgtwW7O`Z~v_Gl9YxXFqq; z<@FuK*gH~B?-7n0>8Ep@)4Ea5lFNPyZ>eT_>2^F%wX)vB8q7Nq8Pa{6%tPF2&psXt zhl$mk1K4G^m8gknLi8_*TvSmW2`>4O-NYz1`pY|hO`^6V%yLLIl|M4+_-dlmyx# zEwc`g=kCNwASEL+B*Q#AW`A}9Yg}Jan$CX_J;N1d_*xnXY%h0yjPvO9ynu`B&Cj>< zd&yy~on-aPJ!OkNI(_r>NLSjUlphCkz^ e5A)=vsAGc%H kNQoTBx_4w+p)c)3&@a-{~PyYNd)(g8MIQvnP+GHE9byUOK 1dyDxxI7vaYZlyG0+(U0 ;Up1UHOwUWq3ucB@CeZ^ofN{HT`McOLyI?DEnqjtm1vI5}dufv;X zEvIrP<|I2cb-eb(sS2?cJNrT(FLnKI{52rc*9Q%3M4t$d6{oH^|38(7?TOa<^A~qb z=)P^EYT8=J2FoLtdI&`#c;AH?rnh(xe;iTvR|!s3H$a?Kq1VdH#9*wV+5O3ePA)5@ zCl!6tPq9!Ke^D&`2j%oYED zzNgA9Raksj1#3GsSNz$v%8}8n$L|>ac!=_OhLFJ5# 1SO=F9?nBr z+>7v&DewZ}R=u0?bp^`0$|E=RjFbI5By@pM-~tN Wzj%ZyW4uqUSaY#&99~b2m zepsKEm$>4(-f_xgL#8z$4v}d}iPJOqbS&4v>HQ_x4UL?lQwNe}@ura{4i>w>UI&w4 zfme9ROL4nBFTYKI!Od@cV@|p^vyzTY#tKI7wdrD^y!@SNf})~1dZBw=%eSw4d{G^r z8Wa`Ft>7$6B`TYto-gpxv&2Fi6OPFoc#k9X7lE5*_T8H@2r{qEIY3h8rbYVH#B>69 zMVxsKX^I^w7d{6RTWtHCIEB)kwP;)>SuFqR)MVDdGtfZefPA3wA2Zwb!UUt(@>B~e zf@^}j?$y?@HiP_mR*I0*lAyGspY4)0Yx&)0<&;wGepf#JQ|;C(4byaCK38h%XXWxS zfR!Sx6&_FGlvN(|&MO_l!aA3Woz_Js1;T iwVMlYV@*GgomQqEw>{x2eBiLn2eU&pL0=E-R?m(0K&CZGy^fk8^WAkqA?|ET1 z9BOk@N@i(bwfl=Qt`qf13CHa@s-uN&jn~wpu{)}pGrbF9jagQ06z;-AuI#j>IV;|9 zRhbeIueOJBGiOKC>LI=_?|3TLgA>j5-XlCFqXldsZu*zsAZ`6D+t~X2beJ7?;c6S8 zJjFYqlQXr*;DWoM*MeA6+R#Ii4w2SggTS?3%x2K>NPbwUM|$II8Va-M;E|P9P~xz# zIa`FHoC|Q+%&MUDmrUn2t=p?h7Uyg|INPf+wv@w)0>1zlEI{GY?p@el5Bv*L%MeeR z9clecB3M)@%mwMBmmq4k%xUIEX V5>< z->Iz*HT7}wBc|J$UqTLfNjoR7&>%{Oz7zKH_~N3c($JMs{7=@+hF`8-4s>eF-dVFJ z{q}d;>)ovBC0XO}-?YZcEAGsw`IL9g3dVSOmABoiga-AG?6qCF_p&spf6^CyWn%(O z&Emk~kS1$sEB>@F#|6b (vNn z*8x2?`-S?vJIRp>q7xBD`vwG^(;L4I-AO7ZKsR9RX!k4iyq*sFu-2X#AXojDai5!~ zf03d~?{nwgnhf<~dH=v$?QVr_U)uDGE2;E3=@E6LKvJms<>CpP^{I|HQD3EdZSRwx z5+)VTqiXJ70`e<}!kYea*QXv8O?cvlwXiNg5Dg|R(N jB^^_`R3sH;Yhb~z_8Lc~cLTC8PW#eOUk$ql(I#2M%(FVpl$DtD$Z)TBJ$ zv!y=@lAz=;rXaQhv>KNxua}TBo$!8I+fmg~`-iEu#88;p&YX9Y^N5m~zj(v$sLy{f zHDPdpH4_Sy^AOYh+b@J%+D8hOp;@Q43z4o@4S9= >N5U8>7(z7GJD8XeQRHpOO{0X<8XoPKlXZ*s+w?K`@r!J4_fCCv%4C zwbqV@E36+X#|5l>{Sx}1swUy@*@o2X;JdIJ-})O2g0$dBZ}Rv==n0MGz0*oOL-bUW zcio jH(QJ^vR?yLL6i*86(uk@VN$tAldG{uzVMR}=4E5dA_shSUU!QD~a)k >3n!mXMVtH54x^yA0(qi_0z=?p7@>3YnQU7S{0Lumj^ z{X^?n5H%s(vG;5A6yX @CW8TUUB7~Q>C5) z9`eD$T8x?$Gh2@lBeb&jy6CWSKRiI!rFqp;zH|oeVbbmBi$JR^?ZVO2Dx)WN`G*j5 z(#q_ODwBs4X {4_KDh70sJUBvr+ISm($VxWoW8cP#miS5DjFW;w+!gi0e z3)UKdP@`AtIi@KZ3TqS} ieL#x5Tv1A_w3(P^=PRFz#-1sX z qv~iBa0qh&C;H!mGqU}9PzYBgPkF@Zj|DY zn0aCa^W$&O7xeA+?q--_u>V`~dKhzi9Essd%^ &fx9lL-V;U*!^7TF3E=VtziJn%OTb)}CX5_g?b@EV{>il2!;VZhFdP&?dHQJjW z*Eh>5;IuV04d$tcROiR$h}6fOZw(Z4rnd-t_MU`wYQiW*6QHyaU(*^a4TL(Mc!&3d z(`Ui1jB23pl}R0dHnF0ifB>9+e5P~9Ot{#o&{!R*u&H_MM+JYLIVqa3r{9={zZNT{ z=Df6}Th9TRXXsgMCb03AY$odFOvC+Xr3=zm6-u;`GK>*sZcEiuE=PP|8c|DeFN>?i z{*E#MiJZ!z+Ejaqk~mJwO(V7l6O=W=L7Za4?Wiezx;Rx?uChQ uj~Tm!!_NU-3LQ%* zO^V3=TYBOVCkutjw59iGP!TVsaR&7P-^?by^HP%`YGM`3s*9ecl85u5KeZa>LV1>X zN)Nqaac5ygg#E)`oD3sGisYo6azR{BRAZ!0W#x6BmfAs;dC?q7faRw-7M$Y45@~@z zktd?{8F$?9WNegjR=bHk^QO@+R%19?OiS9rlffZyJ!Cwv nTO&_pd&;^Sgkp+&9}|=lZ50^}^*DME=%Ls{ z#H^=K{A-BlIMUNeKp5wnlB5u7cx}JzB*7P|#?KcOa6J4%V u@x@K+Gm3@Cc6Bo1y?I9`;cbt-SQiz zk;=joD7{qSzEoQI0!klPi+Q6@8vZ1lC%$*4-y)5VQAuU!ZzK}IH_q7Jpv%92oHjr` zjbk2hqVOUZg%=S|!b6`Eeo<% 2zZ& z%+-kE8s&f$N1rnFlWA56LS&j=0-pg%g%D)mdFFCfevlUjWLC)bG$I*jh>~Wut8L~< zDLv`WKR@7B9OKt@!i`@4QphP3x&KgnKa6*LdVE?q{RkG*lmzU_IC7Fn&_bG`Bf3rf z&7`5>?z$$0C=tbP2sF4{M|5gS59Ki>3dX1m9aBjStvgThX~X%rv}L?yjKN9Rs}Dk% z^MeL1hs}Lo0huD(4=VHSj>c&|qjc7>T@Llpud_rhqoc_}Zmj7*LL_w3=7(E+_(7Xy zWm$;xq?%IkqtdneO}6goTnZ8@ E-n1)BlYysyeU_|aU|MI>o+fv%KfU2UJ(CtB zdPy}Zvsg=GqR8VpcKg@#k@JnY`$4XrX90-SFXCI5b%5#9k)aCEfNtL-5p9$31J%0k zlb$BYYpA<|n_7V-qo=gX3m5U4*P%(WxNza2(v{2xtC&C;WseOFduI`oQoTAe_a_8s zi!p@2eoSNBN11ft7~M10w^WrIX8)GxLQ&JUcsZu_cS8P}tPD(Wec0YGSvPEC*qlr; zUK(}O{-f_1;tXN?DN(WWohv_6E@6~Ao+77FJTm_wr~Ma6o5)cqHDr1-?wd7k67p47 zk10Y3Xeu-!#~m--?UD(WV|vy9m_iRT9)WMR5xyW5#ja_(M^jAQ%oDjlmgsCmvBm3@ zXazlp$P5 %Ew_os70 4wIXsvCg>yx?Rtpd7=I=83FCCQkWQn3AagVoj6 z5xB^_5!(*S@~dPL%I%4r5KZlw%aYG5OR}Sy$R|%?g@c0v@jhSoc(=1M9Set}ELow_ zMAR}A%M@>18uihhvEC-ueKLT814VqosHB8Y1sXEPsWWL{`DYUFhQes9CWZPyI&OL< z3qMh!P#^0+$x(`lpH*_C#r{ gCWVOn@Av0u-!1&4OI+oP<~aMQqR z(MD-XAPlH5wd9G6g87FA(fW=M@LY95@#pUa>^ tNfMd||QJ!GeVBtBSwnnXaM4#%lA;eY5RQi(#K0xiki>lq(1BOUNHO+UP z8Y0tyqYO}(!G~#u@+ZgN4953|6~F%Yd2sEdad)Sztla77hhzotj&hedZKeDuOP$;> zD?8GFj)yh_ZeVFrpm!5PA!x!{H(s}`seItOS{k7H%LMKU3dBj-@DIpI-uhG(Y-k$M zvs_CTE||zf6VYv)Cd5RcJ^};3sZUp*IZ@#|#k>fDdvVs6n&n64$MzVLQIy=5cMhn! zl60c%HVtEJ=bN>p5CzVI5T<&a^dLvVVK FGt8oAZ%7I@!uNXcPLv+7pl&ORNBoUzC* zQu}|BV)gR|(kBtwc%s@KAIY%WGhv@R&au6I5zTM@Y`o3PDQh)W#N0QE=^7`6`p}x3 z-K!iL-FZwg@@f9(ToVp{zhSOkQSrAQ%Muey*4LLx#l)PgtaQQP%aj!z4YQCJIKd-} z!hMD)+-IA@eXbcSVbt7DyvaTGv}tQyGfJH4ab(TVON53Th2EJ+NpI)0bAUamnow|m z0-uK8(rV4MNif9&vBR r;$V>^`gMo}e%IZB`V251xl`|7|9`LOQO6 zJI27D Axi$r`sT7B3A%`oU;S`$1_^)a<5}hDz| &HBm@UGFN;^jGbU()2jsz zTwt1z+ZToKfnP<)_sjgt!g^9^zDRyV{%^U8NzWKzYi%GOE@Yixt8Pv4DyNE0{!6Z+ zkxYV=1Q{rCi0Mh{>Bips=P+64D$jh+Lq7Q3+L+}_Z@EL-zdugal4QR5a4St$AcVge zH7rJ1)uznuhAp(W$Ra6pMnq4K(d0D(iMZ1cVM=;ZvBUmVY-4HwF`$7xqnN%O&jE?& zfX20c*!9z^Bu3olVo%POZ@`pZl7gG$IRHmXa$d?0oNjEtOPty%?d7+AB>TY6Gk^X0 z4D0Mn!a-}WygL2T>u(M-ZFiT~GFPc@bx!p=Z8n)k;;x#m9QW}t4x+dvS# @DX+}NI*BvXoLKqwc0(IcDps-_AEzww z{$cVEww=4$GSKMSdX?4sv3hDbszv}N<;TY8L`ngQyU6OvFXgBS-po#~>cUN;gvSdD zR|J2j+p-l(x-~?3Oklu;Yf|fUt&|BwjyNqiAFpMfM4u>QT{ydzQYZokd#?1NQAH0T z1oA(bZlh}Z-8J}+oU2)%^cwD8Ui6B17kija-DR(EqPWbY^wS1=Op1RtL*|+kFyDJm zy`$>fH>_^}t$1=oo_V7@%F_+~);rQ}6@v_KeZ5w{gPxY&nK%8NbD#T`Ui(~vG6d=D zC5;QMZ(+ml`YuO4bgUVX8zdZbOfB3_<#qmP?LR07HTA{72dW{rsX axvW4YS^vsEvlvU|6xkg6@j-S1arOy*WEu zjlcac0MP;YN(y9;{()?P|A1_!e?d0$Ql-j>&% }t0&jtUKtDn>Om|}XLxc vpad!&+j3pB6m4fP0k=V``WI{y#*<45 zm!|X*5!}G)dVA}*9L>k!7iSiMS(gDS&rgpur4#Sp;PbERqKSOUg+KfDkXiTS%Wh$k zqIl+wEv|W;EVUg^f$PpA__B$tggH}#0PBr~KE#JS)x&f@Jp=7c(Et U^=deYZ>qUYEon&Kcc%4giv(X2)mnGcn=f)>c?| zyun?3J^cCMj6L^EsDhe~_JkVOVTd}g`&hj^E$7`=Y*DXI>yc+-wbeoBVZGWeZ?_qn z 9VC;J=FDtSiN|jQ51-rSwc zxr>v`^4zgcltqXvlil`PK73Z@1fy3035p~Y_)9iIat%%LeK zPf+3wl0|w*A*AYXnZjkM=Lo`2pB;|*?nq&~>1Z|22RKRt=yy9Y4=Qz4bCOO_DSiuk z4R7!e?*@g@wdAN~Hn?sdi8&FD%K0bH0aM}%7>iHDgL&nZDRQZ)R5{&r(M=XuM*r6j zZ8g10JdsULnwP40uB*-*T>Ox(&|F?#Z?WaWcdB24DXC{+QvsPK7j5zv;b4$q3FfT& z! s zavSnpCaRhD%KK+A Yz8M3XQAp$$kCZ_zmRhzZ-5$6E)4-|8J{k)O! zG$j7y%#`9E`B`sgWIE;ZQ^C&EbV@ &rkB7l*dy-s^HIxc;I^bbPAyLBLcuIIXEn-VF6=LFgK=EX0Fi z#jd3krsW8WNYMkS= wahU_XIe0)(Qgb(t~ 3a29$#qxz!-Xm(i30}3@RE5m)dEZmJ zq95$^oNPbh|1A7|Qfayib6Cy`Fv6J?t@lAh(gPaD!Uvto2eam=zC{!W*G<%FzUXpt zTP`k{w9huYKDUo{7NXg0ey~27uRLhBsbzxE^G6GhIDjk=kVuLNe%E0Ka$*OiI($T+ zB1QKYur@BM|28gO_ p>p2lKz`00P zw0u)n{5=Zrj3^yj7h{N>-=IbVeq1g;y%M~~UWwnAOuyV6C#q9&C-@R{#5&Xh&le>P zRA*ewbF`MlT7heW0J8*laMC O#De< zV~>r@sB6v89F|&JFM(4}uU2KfTJv6&-yGVjCA*J>GI@?EN~95RHOF3wpnZN1yI9+B zMPXgrIY=cW&B%)6>WzJP*UCr9=fB>fX928Lu*ISC7lpt{$AOj=mHRz&JYQD4#@d+W z35jr?F$m6Qm+GMgVL(}T9{B4NJ$|Q8u7uQJUwi~9YvoJgy&kxS#dgG8-FbEIp5H*v zREt;6l*ZiDkAsfKf&!-r6?C}msLu5z2K2yiBIMzQuko$1?=!#hkSpI5{N<}&Up9VN zCMqdo6F}6lFa*_&&h!E*%MD(GQc(^_o;L_HfP#cxL^F+M*DYyYlVZXK>*H(E0&`t7 z! ?6_p!f4L0-H{&wK_bKimgjc}Zx9Yr!Cdkex&9618xpc$|>Iyour+!)m{ zJdil*nHQ@WkM#D1Lm#ro!Oz!?x|gpN)Mlc}?XV?n&9@o^BG5Fnb#-IPr$2|jQW{4O zd0ljLWoag@(ciC~A;B?Jso#x3A+<^KiFS9~2ctSHgVFLMW_h{mnwkUocF=$f+3}~z zj#ie`Mk5(ndXf~8X2U&9MhvpWWM|5Gr=_-S;&8Q3+wvY%eUVj;7(pC7%i3FC-*3m@ z*27}(mEPJhIIQ<{Od-}gyR^7bt~W<_b`_SdH;fKG z!oW>IbF(9Ts~h|0l#?zbS;EYtU`cLg%SN97DRlH7g;11iAYjM4jnY m zaRPyaiApJ%t5aH9L8W`yP#fQ#4Vi163hurfIhMf%&Jm=AYSHe D8 |<;2Of<%+{wvZvP{;RvP fPh0g2Mv<8XC3soDlRuBF$L{5}feWxQ&9rV|MreD`lqz `~X8VGq z8zCwt7z4AcHl_}vt^iaC6PjOqJhj`$92<*;MTZ3eA`T#}035bNa~OWAG$-lx*y~wG zTR>Y$iTt$$3xvUo*#u#Q2+45OmK;(f`=Oy?9y KA+uuM2pRv4yk)-=iupEpDmm&w`6AY+$@nI~ zvO`8%t2OVS6(8tW^JcbG-VodgSL)>6P8ce(_gUXDLu;oDp}D>nI$;-BzcNt9!HXaF z3j1SG-Y_6kSMcH64w24(5Ol~&MR#xgEg~07%0EXFS2)FpTHef;pnAsNrHM2w4)T9a z6Rr0Wksk8fo%YuDT{l=79gOX3NYlXmj~T|zc*+Ki(C<7p!}w%p{xZXGyN<1mMdiBl zOYw@20$Xrd`gjY%9iZ%4`5AkbO%lB_`tvGDz#cTiK+Q2wSX7bLBSUASiSVogr-(^7 zJK)|OjNPJ$a;W%3s7)+4*8_gR%BnPBV^VX5G|TKPVPF~_^Sy)CTe)HJmuo{@CH_t0 zm{dJJYoihT^xHYbK3(8{NA$PYfBpNw7G!+5?)31klaJ;SmDY-uBRkf)zyc0g)=qtY zvSfOq(c-`Xw9?;*R*#WPJxh%i`b&B_xWb)J%7Qz-41mqGHLNXwWpG&&unfMb%at~d z5fhDnx1=okzM%`A)DEV9qn&i5r_-by(;aA%4HscH;o2VQr9`o|n8!;8XFQ#h{v17t zs;#wF8I(ykACo9BZ_Ly`YVWDJOSlB65HCH1U8s#A)5&AyV_1-PW0J{FG1~o$_e`cT zUO+aF&)~w(H5d-8K`u>T4T`AzT7wj &9B*3jHui=CfAOnba6o$2}Cp7$iyERFjr z!f!fCg7GVvH+?DjAw D2+0z+fbp1ERP;Y4uO(aaaeXx78)NX{Sd? zt>4+xb<6*#se_a2a1HzbJnsQYHoC(sAE0DAU8OdubVqfObE+iQYR*=#;iN1L1qjL$ z@XK?=7alB7caP3)xkn!8dV{HX$S1y{JC7%Qm&U)_DO7!G1R_8An&Iztq_ET1FY8z4 zFy@^J6&LL`{p_G|t}*>Q@VCLDeCrkz0sr4(lkYx>nv*44E(uvHejVvF!$?Ccxj~i8 z8Y>v?qY<=BPdCG1NonfKQxe@IA(hi3G-!)kRhx^pcfY2S;&Vsqq?rZ!`A1s_2xHb? z?uw5=yezxH5nXw8^#bt*%XQIMFlRd-eH6g0$socBz=?391#lk4AA;{qWXBYQG;L%y zwA3{^-iR!PcL1`>x;1fga^h3i1B=dJAOC}VtL(+v3T`3dZ}!~O@ufi~BF^Zi?2@sv zCp-MF>|zPXF2v>blN8f)jj77VvP&SpvoDGwBFY_IRYh4)D;qDdmsP7q-g1)Grn5!} zKY|O#TI6!G>H}R5EN^X!!(FOhA)ne)>j5g~DRDY)C$-XRcVap-s~pjwVz;oiyA$*v zv!UL`JYsu({Y+1a)$El)uHivnwVmyyc%oV7Fea7+i}nV&Xj{pQrCvL(d(eck8+9NQ zz1qp<8B%AVx>d$`AH%^fmQ?Se`QzWZQ8lyq`XrT@oRN2bdSmiMIb%rIA;@qOen u{TVK$6@Ub ;~DcR7_ozM5H$vosGWw#PKl-=cRty?~wvhiIan?xg7Bl0nU({xbcp2kkJl z3@l?p=@j6M8Cx$X ?)V-8Sw^>10az!c *OTNl>$ zabL_U)?x;`7tYJ;_Fxh4+N$SR`>zRdUUPnGz`Iht(o=TUOPhP7*JZJV$(3f4;QBmA ze+YQ5j?p &^HQ z8I6ZhQi_%fYWOfREIlZ9b!z~+9aQ7tZa(nn!NG+H5zLs7d z4>zSG!&s^p2{G*~4z)g!%LL(t8MPpX&-L4#fj&i_cP4rE2a`c%qSuG=r!_(S_lffc z_wyE2gibj|=whR#HK1H;jSM%=IKT@Gc>793{g$K$N{l*6goI_f@W_E+qDwX0oEWYJ z*Cy$~q9!;@N=H|a@p`jEQPWup(oPS T#UB*{5En|IOThtNT@o-LCSL^x z!#jrBy8Th{#QtC>I3cVPUq&nFH05Vx3O>Z0_t{ma8D!S0uU(iz1{1Ayp@~a*0xTwS zklH8nbNT?YF3Pb?PZy#gdxBt3c96FuG?ZMT3j0 qxvtTPWgZ(b5?hnTgjM9+T>%2 ;!U4i{J7Ocr|n z9uSV!t%ipm+ZbBQetviCo^hA(d23Rd?W^`Rdro)5*9JFxzo29Hkhk{etb)|v#WeJn zrUMSTqC@H*j)P@%-14Wla2CYq>>N^W{UgHv$3g;uxz6}(N&W`0!J=OmO*^x+lXJ4) z?Wz-!_-+UHNV2Jrp1uqHeR-immS6I|^M|-HGoVmmT ??4U#wh)PnC z6n{rR8Z2r_x)J)*?3@OuM>7AHdc=RM9@QqAbeVgb2PNK;RskCV*e;R^TSzIOnkc2H zFI+g{Gu4RKIY}{2)+R1Xy=!6IoJRUnB4fU&|C!3t_s B(}_uTxS9^SFyhDnEm zC|cswmCUSm;k~coORZ-vEQ)aQwV~|2gWkEh9rGNce!kN(W0g@@_0{-}uB~i~PM4w| zS@tO!n{;@BoV(TMr437f*gg{0PWMmC6>37U8g?p`f7943H_S%L^o&JG8CZFVe(ors zS`SY7bb@P?o&rTVglJuW2#8u=py&ggYIM=1jZen3Fy!*hIbRBx8954VgxAqwrhj-N z6bh26{9R^TseB5j=2rWQIgdEgk)e4XbFVu{R&qxbKFN^v;Lq@NU@>J;Z$sUZUu(bR z >SYvqiDH2A;WN7Q_)^P&d<$K4+;@(WFQafj>fp4tV?qQ-2%_-Jw0lLU+(9i5tYy zHSN19S3exH3$<*je~xk|Oif{qVU^jK$Rh)aozFh*m&cE|`U_Tt7W)CYX*=<9O5iV{ zI}4BXv+a~sv(i>hk~sRE9#2u?rWbZT!12*id@R%lRqeV!Sm}Wn7N#eKT7#$3SLiYW z@Tn#qr$ME%MSoemH@VdFC8QkzDE(QPHf7;5m4#mR-SPNGSFaDL&3u8|j&D`g&8glR z^(}5Cj45r2Z&%)}sB{H%?yutHPUbI5cQTDGHN;Tv5IN!sqly4jg@q;yT*-p~CO^Gg znRkH6Z~j>ncT@4`oW2ayEQ$eW?R~YIL>APlt&TNVIGJSD;c}%>o0H@ks9AAq_6q$j z=&ZeMlGOtPNb*^HUm+lvn-k4Qt^vTpNIm4-Jz_d2ju^-`DIg8T-?O%F=xTHBz0wih z(#syJ&inkSBC42d;ocpmC`JnV*dl-t7rqKT|AF{2GE?U|?Q{dj1SWG8FJ&xIWMSvS z{4!~ALBYOy9(b`FuwI)_owmv-C&RK1Qooz+OS{jXcmLq0yv(X7C`eJi$`+ 41 zl~TGaw8DnDC&4n*-MGITJP6d)!@?V-wBQm)uCb7hMwClOGf(KF$7&y1d(ZiD;Qm)8 z$c$GtpU0K}G8@sNAp&{RCP21a86)JhKaVto3esj`g*;i+?Yf$rMQQ+p4NBzBSf%+Y zKMB!j)wx1%ewL~u28w$-X-WGaP#0+FqPizb&!P5q6`T$CtfI4YPXjG9ttLwc6SZS% z2Zb&CvlEln4YF9aqnt?QoMa7HgQ!P)LvQzLKX+%V=p?_j&y!zDAC&~ObB*7$jLDjU zswDc3VxT4lmHhT0#UtTED4?N)dAaHjz+J7Gs XEkn2U#1II2K2l0V#iXwf~2lW<7q(x;qtG 4|-eCGU|I1LwdSytzPr(bjCvr9Dh*PWFSgKu>nZWhJ94<@gxy(+vR*XPr}Z5F*y z+gm*@ymuu5mNl+y18f_N26ICPG=jLDudyV6*ZOw1Di2aOns+P0!AKj0&`t~W@z8Pt zVt;96s}3r?7b{bDt2LpwhE3c6{iNRDM$8|c;=)xePg=Sb XMS5Nh_L(bZgcK1T4tg(K3!PeeB_{1ed?Oo#21zF7x?aJnUIWV-4S?y3G1zMZy^ z@
VUoX`Zc<92ol^+z;H+zsxpCww zx845NxOVuDPiGAoHvrl)4-P=O$3P%^>#=};2YZyjL+^--W6AZ^G*e1`wXe61JI%Dq zbwci8UL?G!Awj(UP5sS@ZeoOBjcuJj>*qK22z#fJwzlTW0P+3{&WXx9E0n>a*A}T6 zvu!aKVpY>hASywACOrd{cH~eWt31d!TDvxPm2oo5{X=*IJ2TfM?%uQS07Izb8# 2GI%HyP*TK6jVi6pTqbu1Qp0|d_enXD#%rWA1c&`mUR&*Z=x1en;N=2)! zDJq8ZhX%@L?JsnKqwTqqV5Xr O7Kx!xC#Lu5LQb^)Cyz0mVs7<| zn_YSK0d?+mh)(aa3H(;{{fL3mw098e$+IhEkwPb)l&!yClMgiEdVVvlQ;oB$=A91b zUxQpb-$-j}oE=-N)MjIrR=;F;_3oS &efc%-mnW&}M9i I9xm1q!8a=#@neY>-Yi5(^ k^={`VN zG85h6PrQQ(FR_^|^)L((7JeOOXyxjsq~v#h-aBjCIP$@zdkKP(#cyvT7QLDSP>^(t zmKDyqqG`ibZjg@OfbNfBd_%lBb}# Os9Y^APYJ*k7Ov0o)du|8a6Q |EZ8uAps`kc~`@ZfbfM_f^K5v(ojS&2c01kf*-ZiZ$K zu*v HXtyB+}DFa**Ol>b UY71&3MQ1o?1pX5SHdr**dFosKFnY!poLI6BLeV6JR59$1Jfb zUgp?&p(E^U#k|*#l Zm zD7|QD!wB$>vC;~j=M&m>g=x&B3qJpxI0x@5cRHDqr?^rD6kb_$^(xI!;R0?*Q-SPY z9Eu_(T6KG`a`i_NSS7 e$02}o)Z=Qn{ h5Xn3^7B|>yVITLk>g!Cmr600(6b#g%qHMODPt7u1@C1 z;Q5VoWvgBy8j&5^a(Q7R`|B(2QRAVI>dSb>0rvgC@P`k?;4;VSYzD{ekwOHHmmZpN zOH?|t%HxY{I_BzuEMJ(t_J{I`h_2)>?y`7yNRp>2mRNAE5lcFfCu`n_s!7 qJZ(A|APWcceD>GK|_${o{1DT{N0-jXbj6JwSKe=N} zW&SK8YKtcb`aE}VMDBKH@nCt1C<+GR5_dPp6MIULp2qjn6a)>D$$9|aSKLU*r%>Q4 zx>uH2iHWuB6|PD*PR0hOz_Ysij=O0X>nP#0%0!$y(9T}GG7`n*)p0)d;NbhCeO)U0 zZ$~)>3MK84Yo6T|?WaH?*NGW(=en-xAIJlyJ(QjM)M@*5tCEaWwe D=aY<&yHUDkRxz!vk+Xc7MPlR>+Hc@ca6 zw}Ib?ly9=g%{GHfQSP`r$I;)>n{Hp$WuUg3Tw~&8gS=LH{CVeWn91;It_RBB%jNyQ zb{}$LkC?`f^9F5HDJcC0HP%$SAPx_%0j$5=y0Shz^+jEg+&XvapF7NMO#kR_H7}{k z^L}0#8`BDMee=1UZ}>KTqz-GVuxN>ULQ%ZjO_S8MZFMs%`2*PNA+zncr-l78vX&XS z#Ve~aP&|Tcg`LRQu+A+X2+o>yp9*EIkMbIh;smW$DtvU(YVvBySD#o6Mb^OP2_uju| zF^nEN5+f|W5vAS`og%!8tn&n1NM7(5!uju!Q3WizvK!MAc$&k`{h{S;9rK42>6d_c z#GcN-Le4zm8ftYZbL49c7G42=IN})d_n;ZZNQ~l>Aq+plD Sd^wV|v*Po;nXC3w3IOD=0D+v5Kc<%28{h`}LQB2&e3o!b9BmM4H&+R{kF9)KRDf zOTwUX2F*Bz?#!0}#Mr%Gcq#cvTpv(5>grveB#w73`veX37Qxi=;BL;jUdUU4YubLO z*GNyIwq({4{0!aZ)>A!dLDY9?-QL&8;J@(Q&(UktCH~%;#N-=hjck$Fg}SWC5!E_# z`=gnxMVZ^u%^|bp(4bJC$?`p{s~qI;2i9bc9OOF{u149r Ad8lEx^4ieaz zalc4oQ7QyHb$>}Dp8Dw~qtM1-SSOMuMmlfm!!qOQ6B#{aWekjYL*FakUeSD#B%8{6 zxnM8suU=D=%q<@UTvR|NQL8PSOkmdrz2Wy0@ixsd@+eoysv&)=6k)ffes`Vqn}bwY zIb;x~!LQIybWWnE_Z%psV4;=-Bmq>UZ8;8Nezu6Pj0Pq5Fft0%G~??qA3yA8tc|TC z%S)c)3Q)szz-z|oJx6hXGv#MH_l2XWe@;|;J|Xxho%zF~mbyS<{66inxFz`LWr1t; zH_<<7tN~mF=F~5n2^BQC#7gxQiK85OjkEU|4W|Q3HKJ$@j&z`m@ipy*qK|i-ZPHcL zvMHyDhR3AD%_bO@zr$CwAE@ER6&0}2w}`VSU0en&-%hVMNaXU4O4>?B5$u71zkmwb zfj7goTu&uKOp|iOHtu*q4R@MHm5fBZFQ}3Fn{a0#mPU`f&asiNK2eA-=x*0(s=tXB zJ->dwP{z%p;3^`5e`Masb-2!};Evbe{d>C_ySy4IOF#ohDrNQ^MGhz#DU|8{EvxIe z8Dy1ag;BWHu2=(f!(T|q$ToWtEv=XP2>i2}xAl*uo2zziDZZWjdGa8r9A*0ZB}4uk zN?Dh4@0qCL5{m yKMv&A >vL9uX9lP e-J%4lm@@1-=J_IJ&2Hlh6G$0wr1JPjXuis7V`` z4A{5Z$_$7`BX^e -kA5W<`1U6Z9T7Ic?gP0pjK z#J1ViTRqCk(Zo?#1h*|m!!Fy9*i#ATLAbboPFw(?#}BVpXePt1P}jl?vRIRLv`;^F z-M*>zh23N}qSv g4l9Rg zijTwrIYCxAxopI|gc}HPg4-pKsLp)ZKxfa+LzkDmsspMr2DzWk4y3T0c~S_oy7Cfg z{xtU^RZIOeEWN`;LdLq%p+LD$cA`%X6ua&HBX#fz@aNv|-GMMFBQ8de^TB}PEtG9y z(=m;maX?PKyRRXsph0TBqky;LC){_z+5TnwdHVfHve+i?(Y+jLANFUIqw~FVtWu^Z zhp2l(af!T$3P+nG$Pha$H&zHU-u 5vzl^JSY~y!#75z$DT4XXn|h{u;e^*e zCpg-LHaOOsMX(}0i{bj1&&|1j`~S7iJ&B}xfvN%-Wk(0d=>W;-d|pG`%B>BFJ{8uE zdZ!n|A=r`oNm*@xB`xw?8`BpUN`FhwsBLp|)bQZP(qP=I9HoGH_0Xw9D=M(bPhAIJ zMjoRzd8p^V$gNc&0`=_5A0SyJ_Q`+cO{)1b>F3uv8|_{{E@}O@tMynB`;WsX%qR9t zcWN1xx}ry9QT7#}$@fmG4b7t}i>Lh*6@>2#^v;(W7caxO86M&Hwel|&cMKTojUY0Rz%;^U(qF$!>bF~GKl z6FCf9GX1R)YBv!q{n7Epi>@{io+WS4+Hlt=+v$MT_3_-QH(I(tWn=BFk@6i|d!8ij zpC2q?TY*-)>J_Jn^3oMA_rTD*^7dECcw5usVb!(4r!P4aQKb zz~?$IL4OH+uC(LN75;KirN5TnfbE?n;7z-Yfm(F9+d ?q7c2?W-ktY5w;fMrMPXnYlajTPNs=jGM4M@ebhhG`~z zCqN>u0OFS75$I5c&R!U9|Lqb@gy4e!1RtrlVW3*4bzSCB@fn4Zx#aOigpPsQXI0>t z3cLQ>Lw;jNg!1gvbU!QM<(+w%m8Po5II^$2u1IjQe2cKXV+IY+1H_m$dSiCd{y7m6 zgeo@}#z{jV#gTZO>-+4;_;(`QtKp&jrl9`MF6+z4mKc?FCC=ofS)ngF*Sz@>&hIo0 zj+ZSQL~xanOL@?s@bWxI5Yr+}^u^s0J}fK8(|4?AU7GCdjOB{3rwtA6Jm=HCP{9^a zoQ+WXT9EE=Rc&-!Z4{PR_j*#!s5EtHy6%&KU!Fcf?y9p2R3#xpiocW0Ei?8s83JU0 zE!RhLfex2K>G!^>svAmEay#|(#|JX>eNhf%kR3oEtk*a=AV=TXXc#D{lCtmK;&9^c zNg%>UDGH3#ily*ZrwbxKB1YssgvatyW8j@gCNq*gtaAjG&(6;`UnNj9{-qMaE#c`L zvmM!1 5sBRIWc*U?m9 zq!_?{By5{D|DUIn{`2&Q6{7+G8Yd!vd9n)3laf?xnscRaf5YQecHdwjdvrYhX1c_R zl$7aE$hNhwkKN1gMB~W0F3Hy8%V6ynmgFOOJpOB(RMYpalQVhJ!=y|6ed5a*WIklO zcl!ADJ80mcQa4Vd(c*0iXMSidnLqWs$9bT?(~qa*xW98k9i@vH$7;dXwRBI@u-P9` zZkquaA&@G`5@yaLeO>kNyY$BZwM#jN2eK&YwUU)p>)Uq|#MyaXzFsrGx#jZ8%la0{ z)+;-xeFk|&s`*7vnYR*Xvu`*Oe&>NCoCv744T?pZUgkFAXjtV>eTviGiZoI4sepKP zI+>W_!(V-eFLvFQ2r=Jv{mzGy_VU!IS|7Y2i|nQv8s zLKTDCnp&FDS7NxkzEo|inHj3l+??Bl6a-KkW6`pfg|aAF%WO3CX8^Nq%;i+Y%FeLF zF)stW88r{LM5B6DfH?x3d7^mTrhae~5kXd1i*Y6t)DoB8eDJ;p9Ds;JE$c (Yt$kwS#XHL~P%PVD> zbl?1Z_SXghwiieJqqpt_UNt?erSQgq8dK`F`)Z2U?$miA`kiyX$vz9SEW-y9^=yFh ziI#s(@L*9!!J@#m5Af%LEc%VViGgVTr2zT@NA-t?OK(O3I$e-gS>mG}m2xuKJlAW# z@63FqKlrAB+?=gEbK#RBKGPq{!{P=h`Zmeq&xbQqYTitf$=8olmezlqzj^VAeuQm~ ziP|(`4%GZ;Vj2f;JX*|f( |AjqyOCJyZoU+?Gvn-*WUPF$L|4;mhr;w*&x^;tzt5hl z+AP@TC8lyP2yfo2uCLiRW&njaH8muOuSB+ZjATY2vy36TvuS2|LXo{uxfLQZZQ&Ms z;bGADOMpd+agahaI0%ytl^>UA4a#7&$n|1cJ=8iESyWItUIJE+Kn+KgFM30(JTp5W zwzE8P)ane(J3dW|cYkmLQ`7cBRZ4}hFq5Hyjh 8Q9Nq6 zhk)g-8Flz%8l$Gy(ER$)3rT<|&;fMWL5n41LoZ2>p|$kPbJJd)nTV`0{o=d4HD%mQ zL#7gIday_v>$K^au6E|8CS^ps_~_+J);Fgb^j&ak!#vvRZ@9w=bn2MKT2(57b_nm_ zJ16&6=Nhuywpljl-LjbAIB`6zjR!An8QER9Lr65<{qKoMGku(EVXD^aJtzoF%-BMb z5ZIX@$)fSjqV96;C!~w2j>Of9TGh1G;-&!psFEyg(0@ =2|n`%lgHFDdpYx z#}emg+b)RtN1LaK*PL66z8=39{(T?(=QR&7Ct >+AoC2!$oHSEY9!pjUT}aErMu Muf`Ot9Z z#jw572QOBeoJG%c3JDL|2*>A}XEx&z9*1K34R(|ONKj=qwe!qoaCXR2a<7cV;FX~m z#NpyI%f-drKRj7gY{V|vhLbI^<;^N}L6_fGB{C#-OY^cHcK$H!M~s(NEX(L)j1Pk{ ze1Q_KjzEiOh%Y(sCgcz=(^J-@XN_=H2L9zNFwCKt0glmOH76UAeSIE4N pP?qeUT0Bo}PFtN3Bq&Pav%3})d=)ry $j`A6wgOQGdckQl#?pL;ZBFDEjua-3R84dlG~ zji)F9t->*#fccFn3gc }I%Q^~5uuif*`8(z|=DP6@J%Sv#QSWbo&uK-5m$5}y zv7$0}cNaVg_1M}PW(E8_^T+u>6ZLEaP}u7ea!`qPCIpo54X8v>j!S3^j5NtA)`4mF zkB1{xZFB2hq)q|V97mUuL;B(gso>v2Z2Z}39oO5zfHC~;2sqZ+@L}QtC*76IH*cgS z %}?iI2I( zB|y03`b+GjhjE}@Xfe14UV3I|&z&%>D~)u?>OBzKrO!((C9yGCW}0ZHq3%Kh)0Yqi zR ;}v*sF-vz3a!$OSivn@pSSSFp9~Ndr=|om!1!0gvfrjvlU>h zs*R5}AJjJ^&>q>|{UBtUaJJ{34{#l9R=iTeH#@B^4bUp|8wWTO0U7SJ`rLQtCyuG!>z4(%(Yp*c-pr!)7G*@UZgRFvguj5stT7B z+h|93R<`~+ZzOaoyjK|~z!%08Y&30=u~JZ5WPlpQ#aB?q1!S-X 8Eo-xam`It z2g=C=-XQGgF*3q09Y}?%nV$8^3fjn*o|M3kw?3fsL_GtJJYBCMB81CX1W$@TM`TN8 z{m!CrnXJ=S-}A}YH2A*SH^TNAUYdteqN+wxrHKo*&T`G2u8CK|4rR;%%JYT9%D74` zZPa?Np;pjnl1$h6SjI`@Z;RG97Qv?tW48~jZp~clL*EeY)M8j^M^3*4Xcc({ZKS+l z?%L`e6dqf`Dq{DfbXC)3t-P`ML$3Q>gUh(_AKRW^3;*YN8u_1>L|8rk2D3yizK*1t zU|KDL%fmw$d97DJ(i67Va6mDT>hNN`AiS~Zx&C0{p`e$@S+U8ynE@Kl-!<3$t6tZm z-B&i4RMtgX=%EzHfHx)210^LtgoQ9N9kQ)6vJ1~D1)ogGPFkU^DLP8Zj^uL?+{`UI z8XvN6@2)-gu8PTXRi2kdf-_`3+V)vi=X$=~3zZGb2Xn+_?+ qV@jVJSZ9<9-+4 zFI%Hncdk50VgKNnQTh!Y35 hI8v;xkx z-JEN5L6EoDuQxnA@NONyGYf|AOENgAx*V%?C}{bvBriWjSA$(Dx?`TNLg|c)@)i6o zbsk*p3ND|5+3r^FTQ0s}7jbgxwpaJ`5oU7LaF_3Awh}ZfaSW9Vtql#$$Yi3AoZRmf z6~$QK=ee^NDgP%^y#xWqHB%=9cv&T-1!ml-SQh_!JWQ++Q}dgL9E^s@#Z3FbI30gC z03+lWj7H; 68@HG+H5vi=*0HjMvnla%#;(ScwsAJEg!gt_; zYe|GQDmWn{pi%b4Imt?k940+j$1+OG8WWE;5cw)l!3nj+a}~y5m4a$kOX73g_^f01 zJiou#XZPTr b_UWCTe9RDJ z(jRSAV97#zyraQM!F~UOOA+s#PXY%_{%977d*c(KJ$VtI)t!GebNF`KB<}XDZuA 7P1E5dKvuy{?mcc|^lhU3EQtfR(9M|c