From f8431bbfeed009ae003c0ce80285f7c6f7a459bf Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 6 Mar 2012 20:14:03 -0800 Subject: [PATCH] Make Aphlict client somewhat more approachable Summary: Provide a reasonable JS API for the Aphlict client. Provide an example behavior to invoke it. Test Plan: Ran "aphlict_server.js" with: $ sudo node aphlict_server.js Loaded /aphlict/. Opened console. Got "hello" from the server every second. Got reasonable errors with the server not present ("Security exception", but this is because it can't connect to port 843 to access the policy server). Reviewers: ddfisher, keebuhm, allenjohnashton, btrahan Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T944 Differential Revision: https://secure.phabricator.com/D1800 --- src/__celerity_resource_map__.php | 46 ++++++++--- src/__phutil_library_map__.php | 4 + ...AphrontDefaultApplicationConfiguration.php | 2 + .../PhabricatorNotificationsController.php | 37 +++++++++ .../controller/base/__init__.php | 15 ++++ .../PhabricatorAphlictTestPageController.php | 56 +++++++++++++ .../controller/test/__init__.php | 15 ++++ .../aphlict/client/build_aphlict_client.sh | 3 +- support/aphlict/client/src/Aphlict.as | 49 +++++++++--- support/aphlict/server/aphlict_server.js | 2 +- .../rsrc/js/application/aphlict/Aphlict.js | 74 ++++++++++++++++++ .../aphlict/behavior-aphlict-listen.js | 30 +++++++ webroot/rsrc/swf/aphlict.swf | Bin 8031 -> 8423 bytes 13 files changed, 310 insertions(+), 23 deletions(-) create mode 100644 src/applications/notifications/controller/base/PhabricatorNotificationsController.php create mode 100644 src/applications/notifications/controller/base/__init__.php create mode 100644 src/applications/notifications/controller/test/PhabricatorAphlictTestPageController.php create mode 100644 src/applications/notifications/controller/test/__init__.php create mode 100644 webroot/rsrc/js/application/aphlict/Aphlict.js create mode 100644 webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 9dff76b900..c128030f52 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -321,6 +321,17 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/herald/herald-test.css', ), + 'javelin-aphlict' => + array( + 'uri' => '/res/50cae715/rsrc/js/application/aphlict/Aphlict.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-install', + 1 => 'javelin-util', + ), + 'disk' => '/rsrc/js/application/aphlict/Aphlict.js', + ), 'javelin-behavior' => array( 'uri' => '/res/0017f840/rsrc/js/javelin/lib/behavior.js', @@ -331,6 +342,19 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/js/javelin/lib/behavior.js', ), + 'javelin-behavior-aphlict-listen' => + array( + 'uri' => '/res/6388e057/rsrc/js/application/aphlict/behavior-aphlict-listen.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-behavior', + 1 => 'javelin-aphlict', + 2 => 'javelin-util', + 3 => 'javelin-stratcom', + ), + 'disk' => '/rsrc/js/application/aphlict/behavior-aphlict-listen.js', + ), 'javelin-behavior-aphront-basic-tokenizer' => array( 'uri' => '/res/9be30797/rsrc/js/application/core/behavior-tokenizer.js', @@ -1658,17 +1682,6 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/application/slowvote/slowvote.css', ), - 0 => - array( - 'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js', - 'type' => 'js', - 'requires' => - array( - 0 => 'javelin-uri', - 1 => 'javelin-php-serializer', - ), - 'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js', - ), 'phabricator-standard-page-view' => array( 'uri' => '/res/7e09bbfc/rsrc/css/application/base/standard-page-view.css', @@ -1892,6 +1905,17 @@ celerity_register_resource_map(array( ), 'disk' => '/rsrc/css/core/syntax.css', ), + 0 => + array( + 'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js', + 'type' => 'js', + 'requires' => + array( + 0 => 'javelin-uri', + 1 => 'javelin-php-serializer', + ), + 'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js', + ), ), array( 'packages' => array( diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 4a9bf0cbcb..cd3ab4b682 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -445,6 +445,7 @@ phutil_register_library_map(array( 'MetaMTAConstants' => 'applications/metamta/constants/base', 'MetaMTANotificationType' => 'applications/metamta/constants/notificationtype', 'Phabricator404Controller' => 'applications/base/controller/404', + 'PhabricatorAphlictTestPageController' => 'applications/notifications/controller/test', 'PhabricatorAuditActionConstants' => 'applications/audit/constants/action', 'PhabricatorAuditAddCommentController' => 'applications/audit/controller/addcomment', 'PhabricatorAuditComment' => 'applications/audit/storage/auditcomment', @@ -621,6 +622,7 @@ phutil_register_library_map(array( 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorMetaMTAWorker' => 'applications/metamta/worker', 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql', + 'PhabricatorNotificationsController' => 'applications/notifications/controller/base', 'PhabricatorOAuthClientAuthorization' => 'applications/oauthserver/storage/clientauthorization', 'PhabricatorOAuthClientAuthorizationBaseController' => 'applications/oauthserver/controller/clientauthorization/base', 'PhabricatorOAuthClientAuthorizationDeleteController' => 'applications/oauthserver/controller/clientauthorization/delete', @@ -1247,6 +1249,7 @@ phutil_register_library_map(array( 'ManiphestView' => 'AphrontView', 'MetaMTANotificationType' => 'MetaMTAConstants', 'Phabricator404Controller' => 'PhabricatorController', + 'PhabricatorAphlictTestPageController' => 'PhabricatorNotificationsController', 'PhabricatorAuditAddCommentController' => 'PhabricatorAuditController', 'PhabricatorAuditComment' => 'PhabricatorAuditDAO', 'PhabricatorAuditCommitListView' => 'AphrontView', @@ -1390,6 +1393,7 @@ phutil_register_library_map(array( 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine', + 'PhabricatorNotificationsController' => 'PhabricatorController', 'PhabricatorOAuthClientAuthorization' => 'PhabricatorOAuthServerDAO', 'PhabricatorOAuthClientAuthorizationBaseController' => 'PhabricatorOAuthServerController', 'PhabricatorOAuthClientAuthorizationDeleteController' => 'PhabricatorOAuthClientAuthorizationBaseController', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index ff0f1cb83e..6e29a1abe9 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -423,6 +423,8 @@ class AphrontDefaultApplicationConfiguration 'channel/(?P[^/]+)/' => 'PhabricatorChatLogChannelLogController', ), + + '/aphlict/' => 'PhabricatorAphlictTestPageController', ); } diff --git a/src/applications/notifications/controller/base/PhabricatorNotificationsController.php b/src/applications/notifications/controller/base/PhabricatorNotificationsController.php new file mode 100644 index 0000000000..00a3a2eca3 --- /dev/null +++ b/src/applications/notifications/controller/base/PhabricatorNotificationsController.php @@ -0,0 +1,37 @@ +buildStandardPageView(); + + $page->setApplicationName('Notifications'); + $page->setBaseURI('/notifications/'); + $page->setTitle(idx($data, 'title')); + $page->setGlyph('!'); + $page->appendChild($view); + + $response = new AphrontWebpageResponse(); + return $response->setContent($page->render()); + + } + +} diff --git a/src/applications/notifications/controller/base/__init__.php b/src/applications/notifications/controller/base/__init__.php new file mode 100644 index 0000000000..60b2ffbc09 --- /dev/null +++ b/src/applications/notifications/controller/base/__init__.php @@ -0,0 +1,15 @@ +Check Your Javascript Console!'; + + $object_id = 'aphlictswfobject'; + + $content = phutil_render_tag( + 'object', + array( + 'classid' => 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000', + ), + ''. + ''. + ''); + + Javelin::initBehavior( + 'aphlict-listen', + array( + 'id' => $object_id, + 'server' => '127.0.0.1', + 'port' => 2600, + )); + + return $this->buildStandardPageResponse( + array( + $instructions, + $content, + ), + array( + 'title' => 'Aphlict Test Page', + )); + } + + +} diff --git a/src/applications/notifications/controller/test/__init__.php b/src/applications/notifications/controller/test/__init__.php new file mode 100644 index 0000000000..67a09abfa6 --- /dev/null +++ b/src/applications/notifications/controller/test/__init__.php @@ -0,0 +1,15 @@ += msg_len + 8) { var bytes:String = b.readUTFBytes(msg_len); - var data:Object = JSON.deserialize(bytes); + var data:Object = vegas.strings.JSON.deserialize(bytes); var t:ByteArray = new ByteArray(); t.writeBytes(b, msg_len + 8); this.readBuffer = t; @@ -150,11 +175,15 @@ package { } public function receiveMessage(msg:Object):void { - this.log("Received message!"); + this.externalInvoke('receive', msg); } - public function log(msg:String):void { - ExternalInterface.call('console.log', msg); + public function externalInvoke(type:String, object:Object = null):void { + ExternalInterface.call('JX.Aphlict.didReceiveEvent', type, object); + } + + public function log(message:String):void { + ExternalInterface.call('console.log', message); } } diff --git a/support/aphlict/server/aphlict_server.js b/support/aphlict/server/aphlict_server.js index eedc35c581..f50fa855ba 100755 --- a/support/aphlict/server/aphlict_server.js +++ b/support/aphlict/server/aphlict_server.js @@ -6,7 +6,7 @@ function getFlashPolicy() { '', '', - '', + '', '' ].join("\n"); } diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js new file mode 100644 index 0000000000..8ddecac31a --- /dev/null +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -0,0 +1,74 @@ +/** + * @provides javelin-aphlict + * @requires javelin-install + * javelin-util + */ + +/** + * Simple JS API for the Flash Aphlict client. Example usage: + * + * var aphlict = new JX.Aphlict('aphlict_swf', '127.0.0.1', 2600) + * .setHandler(function(type, message) { + * JX.log("Got " + type + " event!") + * }) + * .start(); + * + * Your handler will receive these events: + * + * - `connect` The client initiated a connection to the server. + * - `connected` The client completed a connection to the server. + * - `close` The client disconnected from the server. + * - `error` There was an error. + * - `receive` Received a message from the server. + * + * You do not have to handle any of them in any specific way. + */ +JX.install('Aphlict', { + + construct : function(id, server, port) { + if (__DEV__) { + if (JX.Aphlict._instance) { + JX.$E('Aphlict object is sort of a singleton..!'); + } + } + + JX.Aphlict._instance = this; + + this._server = server; + this._port = port; + + // Flash puts its "objects" into global scope in an inconsistent way, + // because it was written in like 1816 when globals were awesome and IE4 + // didn't support other scopes since global scope is the best anyway. + var container = document[id] || window[id]; + + this._flashContainer = container; + }, + + members : { + _server : null, + _port : null, + start : function() { + this._flashContainer.connect(this._server, this._port); + } + }, + + properties : { + handler : null + }, + + statics : { + _instance : null, + didReceiveEvent : function(type, message) { + if (!JX.Aphlict._instance) { + return; + } + + var handler = JX.Aphlict._instance.getHandler(); + if (handler) { + handler(type, message); + } + } + } + +}); diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js new file mode 100644 index 0000000000..9a6ab06f30 --- /dev/null +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -0,0 +1,30 @@ +/** + * @provides javelin-behavior-aphlict-listen + * @requires javelin-behavior + * javelin-aphlict + * javelin-util + * javelin-stratcom + */ + +JX.behavior('aphlict-listen', function(config) { + function onready() { + JX.log("The flash component is ready!"); + + var client = new JX.Aphlict(config.id, config.server, config.port) + .setHandler(function(type, message) { + if (message) { + JX.log("Got aphlict event '" + type + "':"); + JX.log(message); + } else { + JX.log("Got aphlict event '" + type + "'."); + } + }) + .start(); + } + + + // Wait for the element to load, and don't do anything if it never loads. + // If we just go crazy and start making calls to it before it loads, its + // interfaces won't be registered yet. + JX.Stratcom.listen('aphlict-component-ready', null, onready); +}); diff --git a/webroot/rsrc/swf/aphlict.swf b/webroot/rsrc/swf/aphlict.swf index 51c64d12681237d81d1e0652fdbc008eea57bd75..3bd89060899879cbef92f371114848be70e22a51 100644 GIT binary patch literal 8423 zcmVRXysycP*oa#M__A5mxKcFZzMCmJ?qbSPt zO=LoQRwvSnm#vWm`l9JbDn62lClmF8U?`j%&78ArnMHLVa*XK6Xlh839EdbU zhoZyLL?+$T+}I341Cg#+GBq5^tO<>b48hxeTa@Fq8Safx4D3l&NyJ;Fx z)XBs%L(w%o1Ici-J}?yBQ}3_unXWIfO%#DVFwN+i8Qlzt+KrLqaMMUCIWQUlPce{^ zm1kBFBt}NVL-F)rG___laaAI@J0Y>Ap#mq8iiR@D*%$?wC?5(Xc8!L1Mc1s`ENdV8 zmOO?s(KVYwsroigz0ce1Gr17Jvzvr)X9(MZVEoo7#m4BI&0AJq(6epZ#ap)aDcKSX z@~po;<3oD3(yR9M^y25U?#=!#iIUE4-d@l3Q~Hn7jEw?#?<+-5@Y zL^QQvHjTm(-PzSIA#{)qW7M^ zW!t)9fj5*)M@_7v3sT8lsc1SaQC-`%^q#kF`_8_e?LC5^H4RNv_l7NUzDP{_hAlhS zZQZ(Mt3a~~Oti9X(a0zUYwt92v0U04wqI&isX~=mNM=RH+P#@*PbwAKi**qiSUVbv zMN`Kn<#7_!qhf3v7Bo5leJ%A3l&>i;(M?n6CUrd`Iz9hnXw%Zrqhdj58LWVl5uDY} zr6(bEl3v-&Nk_sC*(r1}&4$yvc4Ge6N0RBd*r4nqp;S7Gi3;ry8n|$KKu993hefIl zgfbzL(Kc?|vbkt1x+|1!Ov|O1c41eErsAQY_~)a}-C}vjYQ>g{*n~mF_HeR(%o^76 zLO*8AZf0o+ZDce(cno32q^0Nrvl^B*iNXKdy zw2rAK*|5pJ3nQFP4n-S>lDk}mhVWj!bLXz)&P;OWKr|LlNGa|dj1G-Z$dvjzeYC~r zr!8%M>i7Dn-``CA9WAZ{@i#YXEv?O7t);C6Nqd`5^S5?-HGjLeUGsN%{hGhi+d;vQ*6M9*vzcnDMD}c2 zaTLwcF{LMHq^eovn6{=On_K8(1Y6(<9c}mfsIS#qsgo+bO0BA_nxikNR_#lxmQ^jU zT2ZyKhSZYURkfbl)wPYaO;r)Kj8ILtF}uU*au*es7^P+98SEMjq?C~| zM#`C1!AKP&)l9Epq?VC7M&~kWV+84ZMi($r&*&mX7c<&WM;NVTw3!hfqbO66vSR4)G9_!gwwMC!Rnq)it~$gc4%O64Fhs;(N3lghTl z_z-Hhr4A6cJ2?XI=y+O?ts5h|_u$!!=ks{3!E-I1>+oEUXPh9pK_vS`a-&FY63Kp% z+$@q?L~=kRw~FL8k=!nlJ4AA)NbVBJ-6FY1B=?br3EBOKczu+R-abc>LgtiqD^;+s z?A_=nA;mINi)2Qf-HJmYT3xqNO(@Y_-O9NNu`R@>3X%D{l|>4%w{$B$g*a@Ij+3EK zAuhE?QHi^vN1>!>evhJ&;w3$bPD+;dC^lj=_9&Pt%{_{pl&$Jf9He|fkK!a1-93tn zRQB{JZcNG^rHE9o?NN$J4Gh2%@JkeRPOSkh?GBptt_M^n4yW&9$`@w6d{K6T!W)7l zMm)A`Obt+V1z#dR9UdoNUe8xts01`^1z(ApD%BnC*9oe6vfhtKk>dG?6cZ#RY8PLf z_1FSBS;3pKxsZ)}vy&mlecAeuowsCzj_gJU2|2kxdyx`y@wRNxz0s|Nig<_hR?IuI zvEppZ87kpj4a(>d!>1??el~!fc~cVs1C-Cniq}vnUt_?$&3Z3WRz|dy-c`e7o?YWz zQ}>P@nQEx7{6Rj?d$Y092g_h9^L6Wae>PB|tl%5gldEnZw~(@Og=(6^{aewK^RnLM zhw8`Ohxz&0n0>5lqoSZc$|k-EOdK*+3nn&aW3F7R1|%zBdGjqsDbmknV?`$1;|gjdPX2JjFdENY|0=e7o$tX!F8Jv&R=tdRk={ zFM-(X{L(Dix`;qfmuCa>!FtdrwmN*o>M!LX@cUp{HChRS6;G?D9hCxe=n5Xm1{R=F zELfk7Rb*oe1NC5U*Lre*e2Kguy01~y;k;FJKQ0FHEaggmWpL5PMauZ1LwqO~TpU{* zYskiy1eT)Wu=%|#8(S{F6S?4u*oxT7Y;2YM9?1ngF;8rDHr6P=ug>vQuqoCQ^YU~y z)+}M8kPGG)3m71{I~(+E^eLehz6Vpq&-Y?#=_)v?21C~fhPEiz@N2W43*>yd4#hT6 zJO{4$_eiKl(=VGk^9gXf6SoQ+eEf&@-gddoR zb*sQ~Xi(QLs~~ne(3T&m-yhNH$J-)${kSh;TOMJ8!Q074HqfrFFmU*C@OyxCx%v>ni-mMAnQTx(9sGU*#0Gm2!|;897;hkZhy*()8|;BD z(%=kqs-s8v!vbll!9G!Px0pIR=_8=`H`=2}L;69aCAKFBe}qgj9~PfdjC z`11nKC5Ce@@V+X?A+CIt@E6dyD;HRUb^fBjaf`7MI4?__A?0PlzlO0I&dKroy1*&Y zsKdDwIIl{ar1C0+GD2P@{52w#*wwkofQA+E4S`)OC@ui@Hzjsj`6l77n|PUAK!a5N zL7kQfFcg6t!>AjrcZxxU3EHEW*E)#oLsP+XP)p*OJs&&ZJo1) zeSMrEFI-DAT>-}rOjF8q4Or&)sSISby`469woqR??BsUe@lJ8cz+LId!CRF7kOWOB@X7q+8av3mb(vgpHy)g^ePnx=vie z&VZ#0JA-PLouL9CbT$(jSyLEZU~Jg4xk17mSSO&P>19B0L0P!7F%qEIwTe(eMb#RV zl!&TDN(nbmjD-*7P>OP`P+l&|OO>xX3f(iu z@stx8F$x_2z3rXFW|hJSXg*)FPxG~S{q};8G@suO+*V)5Oh1h99dtO3F{=66J6dff zL(XXywdRiY4y}sV%c>Tsgi3`;7$Z=bZl;zqwT7v6Or6hE9I_WX7893L572K|Rkm?m zp?17cC*FkmRJEdUohVw#crkRF0jL5!ibdyu(kv*_Ie4$dBNZv~1$Y+XX&_ETR~&YI zE%c?L_lostTPswmtyid2wid@ejrHQ#r$gZ~VuQl969)QD;_O3a$03_gDm0l>EN%mf zTji^QuKG)qK__pJOZ)%T3Wko?a6+|6H8(9Axzxq6{bjQ7L_O3sTT>|){}t+BmR4*M zEJ)Qj%>vYe_q0tP$F^fxJQ*3P{0w60M@8B(uC23Mb+ff*WyL`a+rku({jnPkj#4O&k9%Gr}l z*pAIU?@4yW)7y{Js(^UNJ4pw-aVwz0Vd6GAKe_<{FyXFfmd^uk*i3Of^9*_jDnmI5p%|p3>noC8;hnK zGVR4=pN^NrQpsTf5!0W9NlG+jF0eCd%3Kb*oPpZzWNP4yrk=D2NfTLFJ}TnQ!|?q`*+0u>LbwBQ z5PwH|%kg$ms~16&R$tp}BdE0<=1rToy+v>Kw)(tC+gg1{+dJFz=H~YHP96Hxi+7*T z54aEQ>CG)2&3N~FTRZgT)(#(V+P&Tm9f-abr0vZ}eI20YYiV!6drL=4t5%Ocymq#9 z9(RVZd;+ID%4&X4#o@*0cZ$<&4bkhX7FI8`FRCR~w-N2wftEOk-o=P6kFZWgTud!u zq=c!Z!dRmfj8=&Vim=xfAQqx7b}U2M#8@+9olHH8sb@2`jM08ZH!!x5>E|)^d`34h z{Q?o>V2Ew>F?A=SgG`Mx^-87=Gd00zim7R)?q%%rOudfLwTxcRC;}(rpnemc{lsxA zQJH!d-Vp>*5&eLJkLiDp{Hw(A_k_YDD@-X38IEG`IgQ&D9bp2h7vqh=6;D~&xE+VG z>hW@-Dgi*uWJ#7CIAeY^(grQ>km{|#t_Kr4wSgXeGY+yIM?-Ym?z10qqCxwjMe zA@3wIyW7m}F|+&3?0z$Qz|0;rvxm*>5i@(#%nq8_V`lcanf;xaJz-`OX7;3+J!NK3 zo7ppF_C+&$*36zWvoD$1m(A>XIHJMzi{{r$gtT`!DDjb6gj9r^qGheHiCcPwP3&JQ zY+`?}u!&pO3Y)mKSJ=dD(k3nup%A;HTWLVF#ev|9T1kj=wFr&4=8DjW8=(|8DOw~# zBgM-^Xr!dNM=6m36@!#|dX!R9R^OwPk#bj$QcfyrdKB2`O+88_snWX9Dkaqj)W8W+ zBb*>#A%x%_7O5ibu*B8cI)rYfogTucQ4AkL0zQqT@ONAdQyg8zZu9Hls_38L z<1jteb_E{m{X{xPZWd;}R-h!D(9*fAp%(n)rf1}S>BF=u_Y?krsHO1-MJ-#Q)Ge2wr|P>xUpJgW`LpaTxotfvwu zi*Jx@z@>nu!{*lXcNr>u3zh2kA8t^NG$;{m;l79s9&`Khh~pz-SH1<0Yz=u6=^XVf zq_yMvvi{0cm_=zCb^jLfMhu(M$d_k_+2o#iGG~f`=WIo_}y`RH9(RkipG($o zz~sNgo5kI)@R|+Vf#O*;0X8L{9}u6*S$bDKZ+F?99uNOF$>-{ceorI+kn}tF{~^KB zTmUl|)Pia*ppyLtJUX7n{V3!R75rYNs61Z&v6S)eOp$#aXI1_m7RFSdjO;(m|3D@^ zl_kw!?2mZ!J&&H0!g07_(&K%8OfpO6 zXyJeW`6cL9)%ZkU-6=q=5_Fep{JQ|%CqQiybiZmG7NCa&s9k~{R*km-`iOMcA5-~b z>SUlCQ40ruT%dFX)nG-eBJ5}|o(@!!1|v{KAchG6ct(CnQ5rXZ`#hP9I6dd^8`Q8< zctn>d#^)fcHT>oRfEe8XoXc-7092z1fNp+I0YHuU0QB$&3INS0Qj}$7f}CzZ^)J!w z&bsaSy#9xyd3!!zm(Mrm^F8@IK>T-Ps)0fp-F9^{i?RU%vxG`xUj{ zNPyq(A@~A@*YY2bV+;%adiie(03!M8CI?bOZ`P0DP!F*nO`jh+* zKF$B@r}=yGd3+030`$PQL|pJ6RiOyhD{rX$4fP^r+oh} z_B+UHkiRXj# zfpfROxmN-zi(vQ|PE>q|Lg3B`ki8vbtCg|RcQ9a&&ZNW-QtUn@6uaXJNaINa4*1g) z!Ed|hOc}v`^Nje!uuWhO`l4t7!(j;dJwj3AdDK|A@9;+?th_+6p^z7mM%0&(4$zk= zwv`v?zC*}fqWg~^dzp$b>DMHu(TXapq$qy{82nY56D6-nPFL{%Au+aA+=hOxv9gqW=I5r*Vy!YTiV zUZ{Y1F0#Y?W67(Yux;^1H3N ze-bT?mF|Un?$gYLv0Un(`!!7GGHTcK_ciqWVNJ}=Be00HWw<@CKVpLwq&%vLTObFK zj;fC#9ixvU-L3tdh6(zpCa3E`4YGMca)ON)tG6&NJ22sXL12{Z(Vx`#lL7-|jkm;3 z`;7Ps+oZgnwOD>mbm_~Ikgwt%Y|~uG?nQ0VbIt^scu6yMi>j{xutor0)r^<`d=r3k z1>kkfxKIGz0H9j{-qZ}l`W*ai0D1)AyPDA=0PkoG3VO_iYQiLv!z6Lo@X<@xg z)H$Rr6Lk)2^3KEu+N3aWaN}Vmun+T}YQ}S--GA1^#Tr12hf(KwDU1m7T*JSrg_*VS zV}f`t;a@8Nh_nR4{2K*;DlLI)`9Br_RPMB+{M!Y9CU@E~{`~?#pWbP0a;LS~SzS7x zx7~L%|Mb!PD@XI+JDNXyH2>?Pc|fI>{Fef!{3*%hSg9Xo!LO}pwMtH_4>hr^m8(#b zkBT+*Tg)rYt;zVY99&NYW(fa1U>>EP|3Si*NZ20%TMgKMN!W?AN!0r@aO#2csl(EZ(`8QD>DyE6se1_iAdFS?(+}MQdQkBsY|CYDGut! z55#CcVUgV~KAsYE$I5W&kUt}vxRF#W#K*G|$%6)QcZ`3@tir#n8;h}_2qC;+Q4zE3 z>$^-a7@9RtZjR^oIC8D9+<=JI7KzIaxYdGyEp@;L=y1Se zwg85w3b&i4Yc}Tr;gZ3{Yej>R2e4jEYUbdn#WS~_)GS24h&WviO*r1*CW0)+COoCWXx36Y7DTAG zq6pjn`NHnH+(RlQt!SZ8o`n?87~GwFj@O&@$EqYIh3$gxmd0K>mh zfd@{zDpFm8XAYiPJau^H;+cnMKAr`57F+8Men-+x9B_0hYPafu>yprJ>agQFgVwD% zocPqcapN4*+172tr8YzZx*6;QVzYPKalH-qoK)*N1ci^4YL|;8r7B)5JuL*+qE1?O zs2*DgCu*j4?Vuie2*(fyJ$HxZafV=ZyR^O?y2l;Tam!5Ku*2pl4#7q*v2EVLJVpo? zeM{N49d=Jy$c~i@{xsd0^=3CHAD8os#9A)wMCG`8;hwq5u*k~gPUJ2ZyO4FCQ|ZS0 J{|DHQOD2HFa)mtK(UCZXNCZEyGP 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