From cc0f9b75750fb222c0fe74bc6b1c23c3f0c6970b Mon Sep 17 00:00:00 2001 From: Gunther Schulz Date: Mon, 2 Mar 2020 09:07:28 +0100 Subject: [PATCH] change error handling to utilize MQL5 standard error handling --- Experts/JsonAPI.mq5 | 412 ++++++++++++++++++++++---------------- Include/controlerrors.mqh | Bin 0 -> 40742 bytes 2 files changed, 243 insertions(+), 169 deletions(-) create mode 100644 Include/controlerrors.mqh diff --git a/Experts/JsonAPI.mq5 b/Experts/JsonAPI.mq5 index 2aae593..2f0b465 100644 --- a/Experts/JsonAPI.mq5 +++ b/Experts/JsonAPI.mq5 @@ -31,6 +31,7 @@ #include #include #include +#include //#include //#include //#include @@ -70,14 +71,10 @@ struct SymbolSubscription { string chartTf; datetime lastBar; }; - SymbolSubscription symbolSubscriptions[]; -//string chartSymbols[]; int symbolSubscriptionCount = 0; -//string chartSymbolSettings[][3]; // Variables for controlling indicators - struct Indicator { long id; string indicatorId; @@ -87,12 +84,6 @@ struct Indicator { }; Indicator indicators[]; int indicatorCount = 0; -/* -double indicators[]; -string indicatorIds[]; -int indicatorParamCount[]; -int indicatorBufferCount[]; -*/ // Variables for controlling chart struct ChartWindow { @@ -108,6 +99,7 @@ int chartWindowCount = 0; int chartWindowTimerInterval = 100; int chartWindowTimerCounter = 0; +ControlErrors mControl; //string chartWindowObjects[]; @@ -120,28 +112,19 @@ int chartWindowTimerCounter = 0; bool BindSockets(){ bool result = false; result = sysSocket.bind(StringFormat("tcp://%s:%d", HOST,SYS_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'System' socket on port ", SYS_PORT);} result = dataSocket.bind(StringFormat("tcp://%s:%d", HOST,DATA_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Data' socket on port ", DATA_PORT);} result = liveSocket.bind(StringFormat("tcp://%s:%d", HOST,LIVE_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Live' socket on port ", LIVE_PORT);} result = streamSocket.bind(StringFormat("tcp://%s:%d", HOST,STR_PORT)); - if (result == false) return result; + if (result == false) { return result; } else {Print("Bound 'Streaming' socket on port ", STR_PORT);} result = indicatorDataSocket.bind(StringFormat("tcp://%s:%d", HOST,IND_DATA_PORT)); - if (result == false) return result; - + if (result == false) { return result; } else {Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT);} result = chartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,CHART_LIVE_PORT)); if (result == false) { return result; } else {Print("Bound 'Chart Live' socket on port ", CHART_LIVE_PORT);} - result = pubChartLiveSocket.bind(StringFormat("tcp://%s:%d", HOST,PUB_CHART_LIVE_PORT)); if (result == false) { return result; } else {Print("Bound 'PUB Chart Live' socket on port ", PUB_CHART_LIVE_PORT);} - - Print("Bound 'System' socket on port ", SYS_PORT); - Print("Bound 'Data' socket on port ", DATA_PORT); - Print("Bound 'Live' socket on port ", LIVE_PORT); - Print("Bound 'Streaming' socket on port ", STR_PORT); - Print("Bound 'Indicator Data' socket on port ", IND_DATA_PORT); - sysSocket.setLinger(1000); dataSocket.setLinger(1000); @@ -149,7 +132,7 @@ bool BindSockets(){ streamSocket.setLinger(1000); indicatorDataSocket.setLinger(1000); chartLiveSocket.setLinger(1000); - //pubChartLiveSocket.setLinger(1000); + pubChartLiveSocket.setLinger(1000); // Number of messages to buffer in RAM. sysSocket.setSendHighWaterMark(1); @@ -158,7 +141,7 @@ bool BindSockets(){ streamSocket.setSendHighWaterMark(50); indicatorDataSocket.setSendHighWaterMark(5); chartLiveSocket.setReceiveHighWaterMark(1); // TODO confirm settings - //pubChartLiveSocket.setReceiveHighWaterMark(1); + pubChartLiveSocket.setReceiveHighWaterMark(1); return result; } @@ -167,8 +150,15 @@ bool BindSockets(){ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ - /* Bindinig ZMQ ports on init */ + // Settimg up error reporting + mControl.SetAlert(true); + //mControl.SetPrint(true); + mControl.SetSound(false); + mControl.SetWriteFlag(false); + //mControl.SetPlaySoundFile("news.wav"); + + /* Bindinig ZMQ ports on init */ // Skip reloading of the EA script when the reason to reload is a chart timeframe change if (deInitReason != REASON_CHARTCHANGE){ @@ -232,9 +222,6 @@ void OnDeinit(const int reason){ ResetSubscriptionsAndIndicators(); EventKillTimer(); - for(int i=0;i= 0) { - if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) {/* Error handling */} + if(CopyBuffer(indicators[idx].indicatorHandle, i, fromDate, 1, values) < 0) { + if(mControl.mGetLastError()) + { + CJAVal message; + string desc = mControl.mGetDesc(); + int lastError = mControl.mGetLastError(); + mControl.Check(); + + message["error"]=(bool) true; + message["lastError"]=(string) lastError; + message["description"]=desc; + message["function"]=(string) __FUNCTION__; + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(indicatorDataSocket,t); + } + } results[i] = DoubleToString(values[0]); } - else { - // TODO error handling - } } - CJAVal message; + message["error"]=(bool) false; message["id"] = (string) id; message["data"].Set(results); @@ -621,11 +636,6 @@ void GetIndicatorResult(CJAVal &dataObject) { if(debug) Print(t); InformClientSocket(indicatorDataSocket,t); - // TODO more error handling - //if(SymbolInfoInteger(symbol, SYMBOL_EXIST)){ - // ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); - //} - //else ActionDoneOrError(ERR_MARKET_UNKNOWN_SYMBOL, __FUNCTION__); } //+------------------------------------------------------------------+ @@ -655,9 +665,6 @@ void OpenChart(CJAVal &dataObject){ chartWindowCount++; ArrayResize(chartWindows,chartWindowCount); - //ArrayResize(chartWindowIds,chartWindowCount); - //ArrayResize(chartWindowPeriods,chartWindowCount); - // ArrayResize(chartWindowObjects,chartWindowCount); int idx = chartWindowCount-1; @@ -665,7 +672,6 @@ void OpenChart(CJAVal &dataObject){ ENUM_TIMEFRAMES period = GetTimeframe(chartTF); chartWindows[idx].id = ChartOpen(symbol, period); - //chartWindowPeriods[idx] = period; CJAVal message; message["chartId"] = (string) chartId; @@ -694,17 +700,18 @@ void AddIndicatorChartToChart(CJAVal &dataObject){ double chartIndicatorHandle = iCustom(ChartSymbol(ChartId),ChartPeriod(ChartId),"JsonAPIIndicator",chartIndicatorId,shortname,colorstyle,linetype,linestyle,linewidth); - ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle); - - chartWindows[idx].indicatorId = chartIndicatorId; - chartWindows[idx].indicatorHandle = chartIndicatorHandle; - - CJAVal message; - message["chartId"] = (string) chartIdStr; - - string t=message.Serialize(); - if(debug) Print(t); - InformClientSocket(dataSocket,t); + if(ChartIndicatorAdd(ChartId, chartIndicatorSubWindow, chartIndicatorHandle)){ + chartWindows[idx].indicatorId = chartIndicatorId; + chartWindows[idx].indicatorHandle = chartIndicatorHandle; + } + if(!CheckError(__FUNCTION__)) { + CJAVal message; + message["chartId"] = (string) chartIdStr; + + string t=message.Serialize(); + if(debug) Print(t); + InformClientSocket(dataSocket,t); + } } //+------------------------------------------------------------------+ @@ -943,19 +950,22 @@ void HistoryInfo(CJAVal &dataObject){ ulong toDateM = StringToTime(toDate); tickCount=CopyTicksRange(symbol,tickArray,COPY_TICKS_ALL,1000*(ulong)fromDateM,1000*(ulong)toDateM); - if(tickCount){ - ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); + if(tickCount < 0) { + mControl.mSetUserError(65541, GetErrorID(65541)); } - else ActionDoneOrError(65541 , __FUNCTION__); + CheckError(__FUNCTION__); Print("Preparing data of ", tickCount, " ticks for ", symbol); int file_handle=FileOpen(outputFile, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE){ - msg["status"] = (string) "CONNECTED"; - msg["type"] = (string) "NORMAL"; - msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFile); - if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); - ActionDoneOrError(ERR_SUCCESS , __FUNCTION__); + msg["status"] = (string) "CONNECTED"; + msg["type"] = (string) "NORMAL"; + msg["data"] = (string) StringFormat("Writing to: %s\\%s", TerminalInfoString(TERMINAL_DATA_PATH), outputFile); + if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); + ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, "ERR_SUCCESS"); + //ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, dataSocket); + // Inform client that file is avalable for writing + PrintFormat("%s file is available for writing",fileName); PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file @@ -975,9 +985,12 @@ void HistoryInfo(CJAVal &dataObject){ if(liveStream) InformClientSocket(liveSocket, msg.Serialize()); } - else{ - PrintFormat("Failed to open %s file, Error code = %d",fileName,GetLastError()); - ActionDoneOrError(65542 , __FUNCTION__); + else{ + // File is not available for writing + mControl.mSetUserError(65542, GetErrorID(65542)); + CheckError(__FUNCTION__); + //PrintFormat("Failed to open %s file, Error code = %d",fileName,GetLastError()); + //ActionDoneOrError(65542 , __FUNCTION__); } connectedFlag=false; } @@ -1005,16 +1018,16 @@ void HistoryInfo(CJAVal &dataObject){ if(dataObject["toDate"].ToInt()!=NULL)Print("4) Date to:"+TimeToString(toDate)); barCount=CopyRates(symbol,period,fromDate,toDate,r); - if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*error processing */ } - - if(barCount){ - ActionDoneOrError(ERR_SUCCESS, __FUNCTION__); + if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { + mControl.mSetUserError(65541, GetErrorID(65541)); + //CheckError(__FUNCTION__); } - else ActionDoneOrError(65541, __FUNCTION__); Print("Preparing tick data of ", barCount, " ticks for ", symbol); int file_handle=FileOpen(outputFile, FILE_WRITE | FILE_CSV); if(file_handle!=INVALID_HANDLE){ + ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, "ERR_SUCCESS"); + //ActionDoneOrError(ERR_SUCCESS , __FUNCTION__, dataSocket); PrintFormat("%s file is available for writing",outputFile); PrintFormat("File path: %s\\Files\\",TerminalInfoString(TERMINAL_DATA_PATH)); //--- write the time and values of signals to the file @@ -1025,8 +1038,10 @@ void HistoryInfo(CJAVal &dataObject){ PrintFormat("Data is written, %s file is closed", outputFile); } else{ - PrintFormat("Failed to open %s file, Error code = %d",outputFile,GetLastError()); - ActionDoneOrError(65542 , __FUNCTION__); + //PrintFormat("Failed to open %s file, Error code = %d",outputFile,GetLastError()); + //ActionDoneOrError(65542 , __FUNCTION__); + mControl.mSetUserError(65542, GetErrorID(65542)); + CheckError(__FUNCTION__); } } @@ -1091,7 +1106,7 @@ void HistoryInfo(CJAVal &dataObject){ } barCount=CopyRates(symbol, period, fromDate, toDate, r); - if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*error processing */ } + if(CopySpread(symbol,period, fromDate, toDate, spread)!=1) { /*mControl.Check();*/ } if(barCount){ for(int i=0;iHW0H~yTcje6qyUCNa2Zmx#KoMEX+`#f<>iu` zUw`-T)N-o(-XUc;!C+?Y?XIr+&Z)Ckb@%`L_lMJm)4S9A{{TPlPX9dplcD)=`s=j! z6aQSBo=?xF@WN~HjaSr(60J$x-@Jv1dJsIH z9@$^C;Dvog0yz52zDrZk4A&y$c7Lh>u_4$5geH|Bk2waloeQSASEneD;Vwss&u4c}V7tZmCQ6Ag(nqs)a zsE6~IPwhW*uDNr)_};$LBOGI9vhe~{iA5TVRK$5{smnP3MgQK+HQI)xaaL5`FbZ6L z;cnfhdeg{GCoPVwPw!3rT;CjpA1{oeC)O7;BE5D=EqTye#?{h5Y)vjt@&SsnU^=V;BdnY-EOa2-uX{iF8A*|)1C3

5SAlg+;^8U@?&#EQw?c=_%zaYWmjG4CKg}zg zBi31NV z+%i5so#QX&Rk(u^*E-El#AHMv*f^-WF@L7T(S-a3ZM9}(UZ=HY;)WlU%t>Cco!{Ay zdpY+(@_%l1{c}2$hrs9(1M7aDsxUicc1J7q0CwUB;*GH~k z2poKRVf@EeGs}t^$`R2bv@`51e2yM;KFbeIo8&yVULQ{HRCXh-6-|nViNznC!wW}UZhXa>z>&P?9S0F z3)o%F)f%ocevurN_*}M-)KV_oMoGGYldt9(>t*wUncGApAba0_zqL=q%;cZw=+X3^ zeSS1cq@oL25!(_?6Y<0~Au;i3S!Z6&-1zpj&SddksXvlBD1JJ@`(w*L$vH}!eR`-h zTqiuleu}{4$=$a{R8_|rN2Xf2QrqIp@x@q3qI%pjn{mzL4SZzjd($uM_son{@i1N+ ztcNCba89^s^^whNpbkxbIx(L}=Wg;4sC6yY+-=TJD-k7j>1rSGou0XbpOIeZMwBrh$oRIjg=S~-Rm+51NYA8B&m&M<>FZirU}~`}$&p`+vF`8w)z>yXIxcuY1R+ zg<_55tCYb~>rrgZoM5B)8^}uPe{abDuvxd#HHwKpvg033=Z&hYH7(1S4+{Tec9ERy z&^TF2R(<`Mh0!i{qp9_1G%(;@u^@l6Un<)~>`UXHT^RpTH@;f0jn675YmlmsQ+M-- z#Ao8=BBpZ-m@api)VjrV+ql3nDjTx&;92Iy`Gs+>bmOXZJFd^X&xLCL*x08t4t(Mo zjtfH^XJ);sB}lEe!-2=aQjjlGk0egq4co;9Y-c(isr4E*vP{<3;2CzIU2|EI-%AU4 z&USdzdJWH5e%UW@XjrM_FQK};fa+X_O08Q|H;oUiWLs$ofWJTQ#tVUTdjvkR!(a9#y;V!=~d73 z+$)rEOX|1hC#${fzjBo4*&L(~Ut;jTB zSA5iosSay?iC|xvWrQl?$YsQ1pK&(v8R$ZW_zN2|Vhq(ss4wC#qPmwwlFR7E-sG&w zeXEt|M~1G{N~{Sv2i}ZbPi{jV+41Ld&iibIV`J}dF7Xa%Nrq|3%Bi-a-Gs=cuLwD> zA~#l^O||9e#uwImCZc7Xm7KMR2~=bkXoo5T=MDcyiHGsltsZ@85+;ir*{q@{$)N9w zLmpMXa0*m6YcXa1LzsH+fj(t?rH(>O0N<2PsjetQZH$MGXips;T3MG@Cp>ak)-FXH zL#uJq4M65(Rzsf|h0!;19HZQAcyE z8l$0FA;EuXkFKx!L|k%m3eeJ#?Dsj2w{B%$(Uq?*)@xX36}C9`zp=XPHJ_+|;eUN) zlA}Zj4{hCDHtL~qK~-^bC(fcp>|*KLs;3%-YHUX~xAQj7OQ(KNO=IS!dnP(Tx zeSUXt;Mcd2dS3~1O(i-a_tL!!?ybCjijId|lpN6+c3GiQ>DvKz@sXhliH;514bWtj z#&cUn8j&s476AW6{b-p#qqyNr5;vUNL>aB_*U_S4#E@e6)Ahx9JwNbyjNZql-nt$A+FS+Di0li*)2b%p>q19!W@1I;+jz$BCF}fxjVD_f-z(Eb>*b8$A7clU-|MF;%R{ra z59c*S^mm!6-1b9N9~Yh<*#2^kb%z-~V3#$M^BX&!&2ArGdFys|KQZoTKL#Gh^)RYX zUXheE5>2xUNxQ1$7d-YR=2(-*+$+GjJ~}!-@Cv1FW|><=M)Fh3miYX}u4i2A;}CDX z9T$noeCLvT%{4T+Uw}h>TycI2zfkIe>MG^kjO+F+_YHV)_6D@JH}?(E3Tbk#xU}mY z9(*@DhuNXER&8WoLT=-;=)zmKb5^1vf8s8e$MbwSGVB6*hOZ(7FPH4Yg`p3~;tFrQ zgzlbMdse@Hcx@p(T4xUrpO^9EISIV#y}*Ty&uH=7F)rLUe+_T&Nw3UW)cI@4HLtN# zDOE0uc*3bq-sF?6S)!6rM;^WlZ7Twneyl&cAGC_ z&$AM+_tC|LK~}PaJ#a{aw?pp~*Vse%6rq=E!Z(e7=y$vKF*HhAntMFzX_=Phl}_mW z)_$=|R5Z#srAFM2p8GdO&-5<$Z{$7^6=@~hBW2cvh%$W_m&d9lr@l5hJGK_zEZXsl zhAH!UVC}WgTetSC)IxToXf=pCKUo=K&BJR(H5Yf&z>5PP2-aQ$y>*NAz%Xg2iZHn* z#h!hlD%OwM{IZmV)3aQlhiZFd`#EcwXr=f!){@!_noWgy-^TELP0_ZNq>F=GOI4UQ zQu5X<#aYEecb*3tm#p4*X14p{zzc$_mvC?0;wq)4wB0@z1~1^z2TwTK3jfd1~$7c8>D;9du-Kl>67WOGn%{ z=yS~eB59*m8;J3kF~#%L%el|wFB{!iUK_YLSXU$(?(+(7-Ok(%!=vmKTZJWyHF|b$ zdtO^>d#ks(zXeGqL-}fA>>I~UVxJ;b-yTWs2|;uE-g8&Ps;uPq1jCp3($;w7UbqmYQ8muPR@Qh3WOGLbG?6&W!4A1kj|HzEd?CGCsR zTIGGiipOP@&SMJV;+7`b1x-tudTHQcp{v&qZ{5;WOWQIY#cK5Mmz9WNeeOOtPDTFG zJ~^Y-t4jl)4W+$adFv%gGoQ^pf{Jp``)qEOZ9yx)ijq_pE}z=d(w1ksXj&$nYQ03; zy*WDbaeHg9XU}n3uG(K3mFHo&xYbmU+>ilV(euqGj6OJJG{8363)IfTY+SA^c_1+&4%3E^Iz6L zROz_a=x1x~Zg#$G?2#`IJQwrO+ZS)$^5CR3j>`jY!sxv%@z(9=J-t+qSOIc9(epE- zzQ1NSE)TLdaP@Y>TerAwncY~6Q?#muCDa<1?t?`N>S$ys`nYWJnrRqld;8+8TeJrz zeWTiV#!avM6EDR299)NL`odD}N=fcpprp4{mj~6+@Kl0(-yG zd3oTWO6=v+BOHCdB1=pW);a70v|yV@va&?W6~jEz#(tgDTesSX?I7O4FO<7~e;UMj zR))vxD;`%iUb98)_Je9I&$`3%_4uWEk(ZTwrk-x1e&hyA$~!1CdSmw!QWaE{l)t$QwlTnU;~+aGwQH8AdGD965S8FW3i`{uB3nr->OHP3uy;F&?#M-1M&owt)A#OhID;O_qAyyMEi zBZIP!AG~#o(lshPg6{AjCq5YkRI+_f_c{%^FNTIbs<^U+FP8kXoBO`m2R$8?JWjS& zkq|s4dgfWt*Z}TqU_F0#9@N?&60f>`RLO3eo)D|B&ZU~rI9k4vdVOl)xqjqc|1*;z z=#(vxk6{IXik#}gSotjG^4(bSxF8OD5;bt>Slb1ACCy1iNv216w9wi=$IraUYt^&R zTAR%43qz(Qd5!kKxQVZ1oko(h8wz|}=COsNm-J}g>TAf9&y&kG@0@88*O|0>R`1UAJu9rd`Mu?F2ZNtgMnn{frNN?+FG+i~+jx!BSzS`p z_0ciZtyd<4%95fr99fc`vYnt6lTv z*r-k^&Da&I>yRXQ4XgY_T^|(IKI#H%?pJo@tM*&5A|x+NM9NAjc652)9vHH0QdYf~ z`?tg0?GTPURsNI}vMMNQ5q`3Xc*5)KYWrD%9`P#kL*Bkca>x9R-^obz)$iYFZBt*D z#p090;7Q3yJ+k9}*bVuQ+OkPXO6^;=6#l%E%u-~2&fB-N;^#sx17l08TVAsH;7w$% z{=8^Oc3Jl;+vr)-yHGjuclk45mY%>OuGc$Xv<9w7o4Gr-=%;MQd8)6}pk)O+- z3FGZs=`HJg{XA`#dw^!-!SW})c>5O3^-fZdp7zeD?dNXfukvTOjGvv-%e3n`GVd|z zNn{-<$}MjGbQf>ma?X8!HiudEi@c5MFKvEbTk~sXnb8~hmS|h&qj-jqS7Uc*uQ}en zrA*aZ)hzG=j9X^vVNI8P^Hoj4tmfz)=-X+ITRm8BES^-B|*QyczER{@CvMA8o zx0({wBqA8@slZmolgP21+GWO>K>MXV=l1q=%kJ#HYnAr>=|5H?e19%VhBu4kynT!J zrlBiWAQZ8&_mtXT$@#T8ANd{b@XqEu&y)6N?tE{k7+>o(Ev9;318YS&PvI_H?pIZo zckmMRjy%-4G#c~vEq60&GBnHh*XduL8-M;tFDvYn@8`zbx71uWsX#LP`64|fK4Yca zAmw_S#~R8}9>%@o8Oq#O^}DUuKgWHS+B19-KkCou$T*mJEN|a(kT;3OI+fdgm(!YS zShzcf-P)Zxv998?T(5{z3u^tJJRVe)6tCeQ zc|Pt!&+zA-ua)5v0n!`KD5~*9%%}CA+sUdAlv7{CFYTN4XLfi}QCu6b0js6hnUSZY z!jw^!d&+BuoHw_6?SucwDo?$^lC$3t*+KdO*9Z3fj-Aaib6-F5wRC!WVg0D~%&h9! z%=uiF@?eD3-$R5oyRjrv*p(t0)uL=ejr(B5@#aL?Vm8HXl-PVqu({!MzKb=D;L^Eu?AUH zjOyNtr~b3rR>zvlnEYugy@$y5=u&Y~gwcA4&t^-HH2AKDC5>6bG|EP^eY;@FYpfY2 z)%f2x3D;UN(yG;O?ePt+u3;Q`{cLwH7;~?Gu)#s~6w9LL%4AgldD69fz@ONX&1V*0 zd;9hI%w;aTC$%5FvvO?>#h<^DVa#G~Z~x{Pd86QS)10+RqCc4>Lzq=Hz5Sab^ws<| z=^Oc9?nmkU@94QLy@ssKGWLC)dm|6VUBwx?+=K0oF5Y&##=nu5;V$3|VeVyiN9en3 z9pB&PD2qsI+xPR_F&lAgewnxkuT{P=GoB-$cVzuo#Bg_uYi`;slAY<7-!+hdArei}@17w9gSbhl_+`*!(6{ZDuC#CwW(ev_Wy6mLC6D(V?5uhcq@koh%l zHhNhskebhe4QC*)Siw8EN zb!+!EhD^1wVln~cz3X=lx>HaJG;35%Vo2?iaWr5ZolN4Uzn_Zxye1j&ym~n(N8QF z{ld;1Y%GzUvhiah}J^NFjBQlElD$&(cyg$cKvudHR161_(pV!a^2-_huLd^eY9aY0{!*%zM|CdwIR1T`o`W*CHs!k-OFVni zFU=M*4|~(k><6nk!nK>#)Xa|TrdDnk&FA}xjjgwne{9EoZk8GO(t1}QIx-vfWO~8=ilh=frIe?~QNQf&!m*K0wn|leSYP$Mu_k-x)~Y#~WWPl*mAgmmxg4 z7b^4q+Ol%z3wsH)&grw!`eoO)Q8fU*k>T(*#8R(*EMgN;={?osVkLt2F<-i-@1^Za zIZsGBZ_ExW*!H*FtIn1jJ5|iz-t=EK!|x3|#Cvv5>pEo{_gJ8+qZf^(zrEb{M_jvG zibfnNsi--g;rN?j=O=m>0{!La_Hj>CXlh+x?SD6ki=J*4^{4-T5*KuyHYo~O{=X%0 zB^C8d=MwieWGyOgOA)nOp1W3S$|`l+OH_Y7ey%_2JA!M$im;AFXaY`+VkZcx99Mce!-5*7mEY6=Pr4vR$)K4o9mpTOMW|YA$i}@ANZKU%HQ%HHohbIADK@J zd5E_D>24Db>6(%Y+8e_x__JQ)lX~GAU%4dKBfir*f6LLDR<4fwFQPB9xYC#GP5)9v z5oB@Y*{iRSz5Cirw^sJufnV1ZM0TzhrB5%p5IhWf6BTl|2p;cj7c$^d&V6Bj_pQZWi(_Bg(VI3OH>#rtwiolDIXWBRROXD16)!*Zrel$mz;I5GR}NG{Xke!Y)t cGZ(ONbsyQ4vNl3jey*)ljaYProkEKL7v@~ literal 0 HcmV?d00001