From 71724ae33f4e1d853d0ce9e3f01bb5011c1257f0 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 4 Aug 2023 09:00:03 +0300 Subject: [PATCH] refactor manifest crawling --- config/app.php.example | 41 --------- crontab/crawler.php | 184 +++++++++++++++++------------------------ database/yggo.mwb | Bin 19912 -> 19103 bytes library/mysql.php | 117 ++++++++++---------------- 4 files changed, 121 insertions(+), 221 deletions(-) diff --git a/config/app.php.example b/config/app.php.example index 48fc761..3a24f37 100644 --- a/config/app.php.example +++ b/config/app.php.example @@ -181,23 +181,6 @@ define('CRAWL_STOP_DISK_QUOTA_MB_LEFT', 500); */ define('CRAWL_PAGE_LIMIT', 20); -/* - * Manifest (URI) processing limit in the crawler.php queue - * - * Used to collect distributed data index - * that match CRAWL_URL_REGEXP & CRAWL_MANIFEST_API_VERSION - * - * This option related to CRAWL_MANIFEST_SECONDS_OFFSET value - * and the crontab task frequency (https://github.com/YGGverse/YGGo#crontab) - * - * Usually up to 20 pages per minute, - * to prevent websites overload by sending GET crawling requests - * - * Set 0 to disable - * - */ -define('CRAWL_MANIFEST_LIMIT', 10); - /* * Renew page index by timing offset provided * @@ -234,19 +217,6 @@ define('CRAWL_PAGE_HOME_SECONDS_OFFSET', 60*60*24*7*30); */ define('CRAWL_PAGE_MIME_INDEX', 'text/html,application/xhtml+xml,text/plain,image/webp,image/png,image/gif,image/jpeg,image/ico,image/svg+xml,video/mp4,video/ogg,video/webm,audio/mpeg,audio/ogg,audio/wav,audio/mp4,audio/aac,audio/aacp,audio/webm,audio/x-caf,audio/x-mpegurl,audio/flac'); -/* - * Renew manifests index by timing offset provided - * - * This option works with CRAWL_MANIFEST_LIMIT step queue - * - * Pay attention, that CRAWL_MANIFEST_LIMIT + CRAWL_MANIFEST_SECONDS_OFFSET pair - * must have enough value to crawl all manifests collected in the DB index - * - * or the crawler can stuck in queue - * - */ -define('CRAWL_MANIFEST_SECONDS_OFFSET', 60*60*24*30); - /* * Only URL addresses match this rule will be auto-crawled * @@ -386,17 +356,6 @@ define('CRAWL_MANIFEST', true); */ define('CRAWL_MANIFEST_API_VERSION', 0.12); -/* - * Set default auto-crawl status for new manifest added - * - * true - crawler autostart manifest indexer - * false - requires manual validation by the moderator in the DB `manifest`.`status` field - * - * This option applying on CRAWL_MANIFEST enabled - * - */ -define('CRAWL_MANIFEST_DEFAULT_STATUS', true); - // Cleaner settings /* diff --git a/crontab/crawler.php b/crontab/crawler.php index 4eb7d11..20eb7b1 100644 --- a/crontab/crawler.php +++ b/crontab/crawler.php @@ -46,7 +46,6 @@ $httpRequestsTimeTotal = 0; $hostPagesProcessed = 0; $manifestsProcessed = 0; -$manifestsAdded = 0; $hostPagesAdded = 0; $hostsAdded = 0; $hostPagesBanned = 0; @@ -65,14 +64,76 @@ try { exit; } -// Process manifests crawl queue -foreach ($db->getManifestCrawlQueue(CRAWL_MANIFEST_LIMIT, time() - CRAWL_MANIFEST_SECONDS_OFFSET) as $queueManifest) { +// Process robots crawl queue +foreach ($db->getHostRobotsCrawlQueue(CRAWL_ROBOTS_LIMIT, time() - CRAWL_ROBOTS_SECONDS_OFFSET) as $host) { - $db->beginTransaction(); + // Update robots + $curl = new Curl($host->url . '/robots.txt', CRAWL_CURLOPT_USERAGENT); - try { + // Update curl stats + $httpRequestsTotal++; + $httpRequestsSizeTotal += $curl->getSizeRequest(); + $httpDownloadSizeTotal += $curl->getSizeDownload(); + $httpRequestsTimeTotal += $curl->getTotalTime(); + + // Sitemap provided in robots.txt + if (200 == $curl->getCode()) { + + $hostRobots = $curl->getContent(); + + } else { + + $hostRobots = $host->robots; + } + + // Update host index + $db->updateHostRobots($host->hostId, $hostRobots, time()); + + // Process sitemaps when enabled + if (CRAWL_SITEMAPS) { + + // Look for custom sitemap URL served in robots.txt + $robots = new Robots(($hostRobots ? (string) $hostRobots : (string) CRAWL_ROBOTS_DEFAULT_RULES) . PHP_EOL . ($host->robotsPostfix ? (string) $host->robotsPostfix : (string) CRAWL_ROBOTS_POSTFIX_RULES)); + + if ($hostSitemapPath = $robots->getSitemap()) { + + // Replace relative paths + $hostSitemapPath = trim($hostSitemapPath, '/'); + $hostSitemapPath = str_replace($host->url, '', $hostSitemapPath); + $hostSitemapPath = sprintf('%s%s', $host->url, $hostSitemapPath); + + // Set default path when not exists + } else { + + $hostSitemapPath = sprintf('%s/sitemap.xml', $host->url); + } - $curl = new Curl($queueManifest->url, CRAWL_CURLOPT_USERAGENT); + // Init sitemap data + $sitemap = new Sitemap($hostSitemapPath); + + // Process collected sitemap links + foreach ($sitemap->getLinks() as $link => $attributes) { + + // Parse formatted link + $linkURI = Parser::uri($link); + $linkHostURL = Parser::hostURL($link); + + // Add host page + if (filter_var($link, FILTER_VALIDATE_URL) && preg_match(CRAWL_URL_REGEXP, $link) && // validate link format + $linkHostURL->string == $host->url && // this host links only + $robots->uriAllowed($linkURI->string) && // page allowed by robots.txt rules + $host->crawlPageLimit > $db->getTotalHostPages($host->hostId) && // pages quantity not reached host limit + !$db->findHostPageByCRC32URI($host->hostId, crc32($linkURI->string))) { // page does not exists + + $hostPagesAdded += $db->addHostPage($host->hostId, crc32($linkURI->string), $linkURI->string, time()); + } + } + } + + // Update manifest if available for this host + if ($manifestURL = $db->getHostSetting($host->hostId, 'MANIFEST_URL')) { + + $curl = new Curl($manifestURL, CRAWL_CURLOPT_USERAGENT); // Update curl stats $httpRequestsTotal++; @@ -80,9 +141,6 @@ foreach ($db->getManifestCrawlQueue(CRAWL_MANIFEST_LIMIT, time() - CRAWL_MANIFES $httpDownloadSizeTotal += $curl->getSizeDownload(); $httpRequestsTimeTotal += $curl->getTotalTime(); - // Update manifest index anyway, with the current time and http code - $manifestsProcessed += $db->updateManifestCrawlQueue($queueManifest->manifestId, time(), $curl->getCode()); - // Skip processing non 200 code if (200 != $curl->getCode()) { @@ -203,7 +261,7 @@ foreach ($db->getManifestCrawlQueue(CRAWL_MANIFEST_LIMIT, time() - CRAWL_MANIFES } $hostURL = $remoteManifestHost->scheme . '://' . - $remoteManifestHost->name . + $remoteManifestHost->name . (!empty($remoteManifestHost->port) ? ':' . $remoteManifestHost->port : false); // Validate formatted link @@ -256,87 +314,6 @@ foreach ($db->getManifestCrawlQueue(CRAWL_MANIFEST_LIMIT, time() - CRAWL_MANIFES } } } - - // Apply changes - $db->commit(); - - // Process update errors - } catch (Exception $e) { - - // Debug std - var_dump($e); - - // Skip item - $db->rollBack(); - - continue; - } -} - -// Process robots crawl queue -foreach ($db->getHostRobotsCrawlQueue(CRAWL_ROBOTS_LIMIT, time() - CRAWL_ROBOTS_SECONDS_OFFSET) as $host) { - - // Get robots.txt - $curl = new Curl($host->url . '/robots.txt', CRAWL_CURLOPT_USERAGENT); - - // Update curl stats - $httpRequestsTotal++; - $httpRequestsSizeTotal += $curl->getSizeRequest(); - $httpDownloadSizeTotal += $curl->getSizeDownload(); - $httpRequestsTimeTotal += $curl->getTotalTime(); - - // Sitemap provided in robots.txt - if (200 == $curl->getCode()) { - - $hostRobots = $curl->getContent(); - - } else { - - $hostRobots = $host->robots; - } - - // Update host index - $db->updateHostRobots($host->hostId, $hostRobots, time()); - - // Process sitemaps when enabled - if (CRAWL_SITEMAPS) { - - // Look for custom sitemap URL served in robots.txt - $robots = new Robots(($hostRobots ? (string) $hostRobots : (string) CRAWL_ROBOTS_DEFAULT_RULES) . PHP_EOL . ($host->robotsPostfix ? (string) $host->robotsPostfix : (string) CRAWL_ROBOTS_POSTFIX_RULES)); - - if ($hostSitemapPath = $robots->getSitemap()) { - - // Replace relative paths - $hostSitemapPath = trim($hostSitemapPath, '/'); - $hostSitemapPath = str_replace($host->url, '', $hostSitemapPath); - $hostSitemapPath = sprintf('%s%s', $host->url, $hostSitemapPath); - - // Set default path when not exists - } else { - - $hostSitemapPath = sprintf('%s/sitemap.xml', $host->url); - } - - // Init sitemap data - $sitemap = new Sitemap($hostSitemapPath); - - // Process collected sitemap links - foreach ($sitemap->getLinks() as $link => $attributes) { - - // Parse formatted link - $linkURI = Parser::uri($link); - $linkHostURL = Parser::hostURL($link); - - // Add host page - if (filter_var($link, FILTER_VALIDATE_URL) && preg_match(CRAWL_URL_REGEXP, $link) && // validate link format - $linkHostURL->string == $host->url && // this host links only - $robots->uriAllowed($linkURI->string) && // page allowed by robots.txt rules - $host->crawlPageLimit > $db->getTotalHostPages($host->hostId) && // pages quantity not reached host limit - !$db->findHostPageByCRC32URI($host->hostId, crc32($linkURI->string))) { // page does not exists - - $hostPagesAdded += $db->addHostPage($host->hostId, crc32($linkURI->string), $linkURI->string, time()); - } - } } } @@ -720,9 +697,9 @@ foreach ($db->getHostPageCrawlQueue(CRAWL_PAGE_LIMIT, time() - CRAWL_PAGE_SECOND if (false !== stripos(Filter::mime($contentType), 'text/html')) { // Define variables - $metaDescription = null; - $metaKeywords = null; - $metaYggoManifest = null; + $metaDescription = null; + $metaKeywords = null; + $metaYggoManifestURL = null; // Parse page content $dom = new DomDocument(); @@ -782,7 +759,7 @@ foreach ($db->getHostPageCrawlQueue(CRAWL_PAGE_LIMIT, time() - CRAWL_PAGE_SECOND // Grab meta yggo:manifest link when available if (@$meta->getAttribute('name') == 'yggo:manifest') { - $metaYggoManifest = Filter::url(@$meta->getAttribute('content')); + $metaYggoManifestURL = Filter::url(@$meta->getAttribute('content')); } } @@ -835,18 +812,12 @@ foreach ($db->getHostPageCrawlQueue(CRAWL_PAGE_LIMIT, time() - CRAWL_PAGE_SECOND } // Update manifest registry - if (CRAWL_MANIFEST && !empty($metaYggoManifest) && filter_var($metaYggoManifest, FILTER_VALIDATE_URL) && preg_match(CRAWL_URL_REGEXP, $metaYggoManifest)) { - - $metaYggoManifestCRC32 = crc32($metaYggoManifest); + if (CRAWL_MANIFEST && + !empty($metaYggoManifestURL) && + filter_var($metaYggoManifestURL, FILTER_VALIDATE_URL) && + preg_match(CRAWL_URL_REGEXP, $metaYggoManifestURL)) { - if (!$db->getManifest($metaYggoManifestCRC32)) { - $db->addManifest($metaYggoManifestCRC32, - $metaYggoManifest, - (string) CRAWL_MANIFEST_DEFAULT_STATUS, - time()); - - $manifestsAdded++; - } + $manifestsProcessed += $db->setHostSetting($queueHostPage->hostId, 'MANIFEST_URL', $metaYggoManifestURL); } // Begin page links collection @@ -1236,7 +1207,6 @@ echo 'Pages snaps added: ' . $hostPagesSnapAdded . PHP_EOL; echo 'Pages banned: ' . $hostPagesBanned . PHP_EOL; echo 'Manifests processed: ' . $manifestsProcessed . PHP_EOL; -echo 'Manifests added: ' . $manifestsAdded . PHP_EOL; echo 'HTTP Requests total: ' . $httpRequestsTotal . PHP_EOL; echo 'HTTP Requests total size: ' . $httpRequestsSizeTotal . PHP_EOL; diff --git a/database/yggo.mwb b/database/yggo.mwb index eb968ee71c6ee0734f86e5892eb729cd4f86c19d..2c6cab814bcc0453c6238aa6630631456ee6c13a 100644 GIT binary patch literal 19103 zcmb@tV{|29w=Epo?wB3hHaoU$c5FN8bZi?twr$(CZQk^I&UeoE{@!tSjhaeJ?b&Gjg^uv2~)eaW$lKv$4MLwsBo+O}u=1LDy{|@VF|ch6EBs z8MkC+C!gdT0!YeQl4qV2)3hs?l7!QV9u#(c2B4UX)rk5N8i(I!OTFHjT6W!(_t#{Qu8PC zS3c-vb-dZXG&PZbWn$_DWsLyhU0Y`3&C&;tDZL+W`DJB4-xK99UX33bwBMD+ZdXRy zX)tPWC!XiMo^5On?#q3K>*A$94m4;CX&3}aL@ii))d9$;_6&x=rnn?OW|Y@c;?y*K zQt6AnH`F=A(M>E9cW}4XpdS~_JVIXl@Mg^t*k~&z1uZCOo$)04^*mO$dOvzwX2Nds z=B0y?4Y&$I7CeNe5SsV0TiBOmV~0-HieTO zYbX|fUG#$rfZxnQh_Va}_-P;0Nx(oe);Nb`W>64y1*j4{y!q-RlN0s=+ zp-~fjygV~O-##Lst z32*MztPSUm%bK^UFM%w!_q>RV%|>C@>DB17YUIR<*=oGLeJ-TL83${JDo3F{E2d#) z4Z|iIv*_hg$N3K1?(U{)MdV9$Kk{r1$n|b!@ZHk#mobRWQ=>Nal(hRs#LF!vYARdy zR$1ucp)EHBl%^|^AaQP7Om^DLHcXP+?*Y+Hin^6dTS_***HSI)wRl3`i%oKDIni8|;LHW~CEC`PKJhyPfQ$RhRI~=)TTKz<-GW_( zW-f32E(vCQgYf9-(M!k3oF)F!W0{5Zpn{)rzm}X4wpaqnIDxq&s^hla5azdud9cw{ zT{4DM{H$Gp?H-8y>7i6yw>{_*4poWG?WSFqo zpblUzf=<#(t6Qk>QaC(f);KnayFp=pZfx;=csdiB0w@>PxZ1pVgbJ#_^PHBQUj-TASw&-a_4~F1CMh?=GK}W$WRD=wiGdLEG~aYqYELrCvwe zHn{^J&r7%ryIy>FM~qc{w#U%Q0tGW~2MXRjA6%Cd>Mi!XK7ANd)e(!|@R~rcuUPmp zuW!80?meFy^n5&g-9Ojf*v;CokM|?2Kl$j@@Vjj>emXbfbNXxrfu&X$1^~iaMilBN zmR!klO^J0(=gz`EQ_X4k%OwWul9{p>1zY3|xvG41qwkbrqh$KnlxAJ#Y+GS!C-00B zz4Z?6ZnZgaM#xXSVY&adbij9*I~W_)Y4;??fQkM|WlR0?@cJzZRQcJX0*Te#5WQUn z3cL-o?P0u41^XjLl<@?isxb)ytU3Zry40z~1r~@K$xj7u3Fi%>`s+>ZSnlg-U+!7% z%Zx&PZ2a5`wqXhw0TDLTqcH^^8}6uxHqzadqP~uqkPuCC2hAQvfTKVfRj_DfN*a}M zm<3qVF6RMicAP2wR87hPajbc<2qLJ8sX)C34Y(5lQcr`BblZwxZcIQ7v_}o>nG4tJ z!>OVS>Il++HVU3HG)e@eKLB(P0m?`L325*U*+?PhwJzvaFOuIh(2km}GV{~4zWRr& zD_VUG6X`Q-*&r$0ww=JW8OR*!+E_c)*qJ4F-RstiUn7fTN^jCnYm3IM*>b8Co}Apw zOro7!0a9e_*&d=D16&3P$TuioMzUxq-)b7J7?YW^c8WHm?5IQ{8@4jA5dqcoD3BOC zis52pLOuFeIY)ucEF!(qE&|-w2<`pC(7JLL7YH!lj4-f#sdH)=V!tkf?(7$Szb?LB zf?tF;pwEuMU5GF3(sfsz^3${He%%ba7E&OZ=q?g4`W$c!EPZfPxNmTg$ho+Upa$I2 zh3nbA>Fsl5Uq);%JNbuQ!7ac&h7yLONO6g^2;IAc`i#Ux=rJUZ$Z&ml#Th_w29CdR zFnkz#j%?T_q~!F?ZR`Zhhgq29qV^@M(kfT`opZ#A{}}ZqFv&y$!@q@uSO7^HPT4c6 z_(jH$HdbY)6mRj&%c-k#@*&luTYin))TLS!{0K^R2Cj-v^|+m<#_^h6q} zirTc?W1iK-+47<`2v?q3ozms|aa5FDRW#X7WVq?Iw)+ZetW(J>34=PlOO> zwc#To)C6=1OCm zARVg3-&J^OlV0Fov1}oPVPn=?{U>slmJ~NOzmZcLri->u2rK|Jary{p694q5~ zlOJiM=2))8jZt^52YcZamVb#}@u}{lsT32;R@y!SwC;FCaoFfe*OQu(yX%P z^Ei9UHE7v1)GLZHQ%ziGNr2Whf?AlR>&wQH(}&hM;$SUSUd7r3Z;J&`RzRoUh`$fP z8B~6X!6^cU2{T?>4x{mxs1Ze>f-mjvh_V!5_Bi(Ymc@Hq1Gd>@P=={hFXN9y&UAeWEGA~MQBHI z+u3y6DSkTgeY-#0?w>vYse7A8ddk9PlJ>)X48+!iU*V1ElQ$&h6e7ti@gVwD&4CRL zJ!gR}#$)^nHIn<+LjplKlL}X9adk-m0Z|UYrgDwL(!rd0&L0y2mF0z%epskk7#o?K_ym%TDHnr? zei*1(n9VPp5D9ox;9_`BA$V5=wXH-MSCZ2BaLO;ASl{$EA1zEHZ$9Rks&m7gIcqf} zJBFAEuJ&$eUwXZY(?mR4ZO0cAjlF&OcISymCIHa6bLX-ORmTI@$@CD&d8qTRNvE`l zB>s=}!L=@yB}d-l{MA7mZmQ-nox!at+dGu~WxIwN^li@7l&P)*QD)TIow}!Ai}k#NEM3Uc_zcFFBDd zseWx?>y1oDqy1!6QnA#D5$=)p9R|ikxQ3&`^z{ZMYXuwi`$n6#t7i88?#(oq zy%y)`Ri6xXcBjt=(IHTW1C#ZIsgiW?+N1BQrAcdiXVqgIP{ch@GsqFL^0xts$*b2K z_9J}f%+V@1eaKR>4Br0go>dyAL~RZe054q^!J8tmpBRi798g}M%__IEHY+{E7Z&&H zkNnH@lRo7ES~?fP3Ta}{HnbPwePN0LFLf^TBVB)GTxxV!eR^M#@53}aGm=^4qCp#maRA)=+#rAii0`;t7(AK3plZsIp#3DjHK<*5+k z5cp>q4WN|uKWaPcnKv{YG=G^2GvjLwDHMVb{Y(O<{e3qfAfP%3BhVO4NUZ@!2utW0 zM0f_PpN-axf)R`?YkX#@0JO9Rt|m$tctO@o1@eQ&@2tk(N(U>&otkkx3#r?%$53XD ztUCm#8WdO^dc}$#SXJ;52FO$S=|kCBr7B@|R4zOzu@ra*@RLi5OskO+!V3?g^ArnZ zS$7xkH#DxhH}ed%S1r`?%~o`}uUEauy?`uIOmi69NEBr-pJJO)(wIhw6xhf!q=qsC zw=o$jJr0WjgW++R;SrNB{Ut!1TMJZ39iKG3TM!J;FD#@EtzpfNtSW>73+1T{<*Aai zROML6i)&w7&qC-FMR8<5{tQK?sA4lqQmMjYXn+N2ks*d`;8*z}v;-s?)Afj~1Bxd6 z33KeBe9SfVc>%0pQw|5EBHSN7ul%>{u83OMgmjT)1ZrqL=O-+o*#%CSAsnX_y#l(? zum-S7G>i%?-`gyn%6}dTObEtSVRwQOG^lbWETJM)yC5$cU2HkM3nU_d7WU%<_11G& zm{=F?+hR%gkf8d0=A=YHNMt=;L41QR9?25Jb5B7j&9u z4Rs;Q(-C=wg={sqqE+UpjxqZpJn{S3ZO`N>EL`rkr(jPLT*>?C=+`MX--2JV>=k}-FYc5@m zxRS0#^M)GG^A1uMnO=o@K2;{fzII8Ejr#_)=g!1pPX^iYdYBZOgT>O8h~{1PoF)~( z6IxaR1h)>Ogkih*y0RK?nhlSv+OqD?W0W@iv(c{(Qt7D($x6Kk37%~kw|xUIZEBcb z$6mZ-m%|KP4Mq&?$`FDnd=EHO9S78&ow>87{g*qt4(TG#&&@=If`}~!7gj5)OWp8@ zQ}At7OL!?bE_$~hwovQUpzJ(GPUplR}?s(%eT z3?dD3Jcco>^g_QQi1p32^~~XHM80Vx8*>MV);Sw53UOQzy^0EYzzOik{B#i{sZxxz zja(652mjYvF+1c`Q7em4rqIr2vAhA25X?d}+NfdoC6QfTZXQw8j0CyV5?MUWUopX6 zr?e1c#8ke{cFNuQP%{T0@ot^O92_0y08MyDTfFw*90;*@?nov)$`|6P( zK^Xc$7}}WcLI4-b9F#rjH0nrjTuRd; z>+jw^N1^m||L8Q-)p4iopl;~w?!OLiu-c)+aHxa+KOW75R}80LRnOXSR5GQdU!Ef0 z*VZ4_TCvXVQp1BxFk!Pz+dw=5{jC>wz)fF0Un~pTF3fk*#^|XxCLaR|mf9)mV8(Ti zB6Z@nv1zt8GV8W$RXMi?i3<-jZo&M`ifRY}#w`gWJ|k-ak&+#0CF;sN!h( zXXi(~yld&Q)De5e&dYc=pP^@|5?A6aWHqvPZKry8H>5a+6R@)#;=wr`2M~WfTIou+ zSiPqvw`Zs!Zwvk6i`6OjnjTn}?$#-=P5VIbOAE#c#lr*j|G!Xl|KZ@58`!YyM`803L5VGLur8Ia?dMIpL)tp0CCG z(jeItNhAO{j|44q`x$%?0W^^MFfMTUhycJgNYX3I;O_tXJER>Fb5E7dN5JaDv{NRA zi~a#e0AbJF`D-EFWCE2=g4)*J^nv&Y+t?uWP&Q4)SWXc`QjwtP&WucqOXklOs2apF zeG~Yrf#nYcSqi_^Q{Ma8vhj~I70u969rm8^Jtg5!o}eQP>XXW+Ys>M{LCXTxUuqq_ z|C50W^M+8zzdD!ga1a3!o=>~Q@gSYiyf_5xG8sZ8x{x&19ax0>Xb zcx0P)v;2f2h8!;ic$|k6cnlL%?du5o0Mp0>83q}~pEG${Fx{svtiP?RvM{j}c*dUN z*gx#?s{-v^N)bd^`{flCkFVp2h~>ue^qHi=Il~7u-8&xBO%Gj-6a@MV5B07NG-5*E z6Ae!3N8qPtbq$E%h_H5-?&Jn*WMCsDoR>s13<{7%#UQ3ZxJ!W1mg5qTo%S3WjeHrnB$JAgGj(*i`;*X**`ze8*{{?tol820Ya0Sxg%D%jzU}p#*qwU^ z>%ZgomVcpl_@00>BRZGTW2kI(eeLZpoJAk`wmsn5)E4!55tdgO54wrShq{9C}SWLbmu zV8>bbDBERQb;sML^XtpowIh{`-jRJPoQ+oirhtB?YV9~t^1>h18m5eBR6UI@+VzAD zfE|jaoSzZujiyXOgn%mY?cu-{5a>%xmCSyx2Uub-Go9LvkHE@<@4+7^XfN`P5%{xCo)F;&~60J zDFhtM=*fkrVp(ia6i|zENQ-lp1i>`S0(ahDcWLhV0%=brh32Yp*4NvJ00r-T9RM#VpfZ- zD>Frx?$j_6_GYX!foutY)x^<&FoB-TfuBg|2V3uHX(XG^Gj;^&<5*F53$$3MC$|RKq9mc+g}@ z7d=NmCQ%9{T=~>p@4wZ2gV+03=yOrvr{tP|W7;G4dQH)#n|8<>*mUK$V|9}-r*(1F zmRo0-^4jR~+PjGDTn&>WQC9uLZYs!0Dm5!PxlEN1iNbEG&~7T5!8<;6vOVY-3$nXJ zhz^59^*z2?DdYi5%I3A%rEV&Nidt?!eaZ=*Mq%;^a`(MofmO^LXdInHYa_W%_Gb3i z#aw!znT}3cjV$2z)f|p&H+$i+>)R;g$Rqx5@`Pcr~wKT z3W?tdVLf4xA;9DvNQi$(F)7LB??+#KxWyRpX5^KhM0Z8ToU%2=uCG`Q zRts=b))Xt+12g~OpEMI|`9o*&`Jv^u*x1-?N@Zv`z?P@+cTP?X3~FLykpLmCTf06^ z9$0r{w7wMxm>;d*#S9GOYXY2?6yX?Fp9Ko=FaoqOxN-frs|5Z*>vv8&j1Szqx&eE2 z6Y-MYuZFbuKtG5B!E=&{W!FKGt94hnc?J@qK!{iYYhFiMkEqklkOzDWQrTW533bbb4W6=Xn z!Ao@g{sGHX-e8UgqxM4NHkc^A&@K}L@hest<_(1ac}hpl^L82Lf+qd3j#CAzH%%dI zZVOVbVSYl%>$E(v5YKD?QqrIxR*(aS6ehH+XRZ@bigclI!D(W%OqJj4z|A5!nUt`O zJi%E2%0vzw)M>z9^^@K4>wL+{M7sf4&$i0q4w~a3Bg})4xL_vakzp1|)$ck+%#fGK zZM-IzCk|XbVU7LzfCl9Hg5>!Y4EP!38DB}^@~6-u(Ioq|W;!0yWf?^*bcD%SAyoIq z-hhrE;mHNrWdu;1Zuf@KFAozU&^R4eNUdMOpaE)l5|or;Kvlv4YTy<&{5vYbny}X} zno8oKRXMB;kQJptA{}#SWlg~9XEuIAvuTQo;zWgG5D~QSPVo(~I#Rp&~=y< zls_1!)5<#Nx!k_OEoGcVi$eT=Ou~tIe3prH-ZJ)9 zdZ5faAQg&{RKCxA+)j6PXh8jhxEQ@D;JzWLzD5QPW$N(Av$M3o#vwD4koH3 z&jS7rY#@+fB8vJ{Sm2W*ipCA}8KMZpXA-JWaXWH)6e!XvDfuYUTMK?w@SJGuuqi6F!bO2AiZOjeg_yfXHb4@jItB(OZ?O;k^ttx>{c?1PWo~l#41`p`|MAOLEG(oW z{oa(<2cVR zI2v7&w9*r{|J;eZYh$hl#XNE)=akPt9C!hxn%v3(mk%RQD9G|4>TEJToPoK~g4^MR z;wDP#52LnyM<43g`Aa0_IgF=(5}AVfuOP%joWir2l*VIq{YNX}`Ew&=aJOcp{2*3? zQ-IU1A<&+&91b8vg<^8YFpiKf5r{b{0Z$b`f(DEI4`6?%3;`0L=pqqfq~-k~65C7+ zNFH#41u8i4m}q#~NGCMe0SJni=m9?=J(BoqTgbdPZp9VQ?u9;w4?p+4lg9NaQg8H_ zP*Fk1n7wII!^6Q$ASi}7f0~29#LpM-f!9PH-Hv(Uzr%$Rq*5fF#Uo;AMRJo_8YNrebBS4K>qOz1~t!{G#1N+09TDe3N1chgd}+nRA6!K2LE*JOyTR4n+Gr6PQui%mxg$$4z$rTW?D5 zHSuQ^uL#3+(1GFw#`XeUJPz{(z9$Eij~c+^e?=CRx6rE9CeA94d6l6xz*A7EPmALy&fCtWI6PE^pO)?uETmuOSSH();k@!4N{am-Rb4IIX9;)dj0po z8B((WL5snWN{6sx-z2QjKUUdgJ7g#i;#I&o?9E`As8&o+SU>7RKT1dZ4j-#5A%5#|FzX*Xpx7UAWi+*pH{3x7|z8a=zlR?BQVL1rvyY~zFmxiFwNi;W- zYyVFgqWv!oDf@jDPxYf9M)`rHv=*y6CmZ&)rSrj&L`;DHspP@20+HV?>$p{S$K08t zZSMtc`U?C*_aAH?qL#wldqS3N;Y33L?mYvSP4r`~8Sg!1aN<$_=uU5Mzjoko6X)!m z#X&aM7nt$t{cs?)7#9a#w7&M}#IDgjexbts@i=O6rVGrwwYOj}%>;oLf+0F0p5K>) z4uJfT+njKj)W{4APQHI*LH2KVi1tdTsVFdAzs?AJ|Ja8vd7FV{0rBhC^i`!A&E2R6hQCqavnc1HZA0#77@iae4G>D79YXH@D*7)Cb$X zqKt~))SW&c4e8I0Pgs`2PZk|^@Xki}M5xgtnmgVK6RV4DOL zU_dqSEkhLCK-vDOrnus>oc(TAAbR9R{IRDpMDDrw%dq@5S_i7((ruwc-2w&wq8PCU zlBfWH@-`LvTi-K&rQ!@}2tYZ+J5j2s5A$~-jCLW2T4}cm)x?ktx^^McG(dS73Y?t7 z4Y|1*mh8nyyU=h|LJr9jSp>2}s&Mn&k6O9iluzrk`eF}KoY*uLiZ`gF%j|V+Vm12n zQQ)^j37d8TrIioPnPuPT{E^DQ`+nW{>TAhfLxf%DX}U!rAlvn&*yjASUaIz?f~1i| z?4r5sNLF|=4anKxRV>N<;Y8f_cCIzs_s>wvoW3n8q)a)2UQG9(J^-tk`jfRo`zc2} z?@_QyxZ4rY1;gJKP>lb4og3W16;k6WS<@sZZi z@wA*v<}nH;P(oLptQObmkFI!O$9t>kKVlktPtkm5PWSZSwM(k3r?_Y5ZydF@6$&QM z@*wI$`wV&OjV`U}h65i?W<=4K+w;~o{}@o~?GmkfUQe2 z!g&M1rLb>tdrZ)))mEe}bupnFEB7)^AcY!D!urU8UL&p*E3TEbsbI{k`+kjP>%>Q& z$bOgv){m3&ap%GHg!#KDz=Ju2#I6YcRxL7!+FGseSUhAj@I4{1GOc}9$!E#oBXy5y z--X^KzN~fF4n2K5`e~Laog{-AXQ6b$qrsoGV=b_l4U}?^L1aI5qf7}cRPgw$<3NQ? z0dp+b*Y?Y81R;@paD#>l(vsQuOZbNt5PEW6zHUShUW9Ho>4MrG`*g4#l$=g6Z0 zqajV?>m|GR>*Oc2>(H`g+s*C6mEC5CWmhsvC{N>3rS*BFO_a~e>4nyo*&i2tXJ_7B zOJSD`vDX%eIQe7s9R2q`%2{Fjhm8>Bfk@$6#JwU)`9w_c7g=28-0& zsDZ$p(N#X~Z-c;3#34^np2>Ebo$enscGE-I#P^~U_(=D(t_`wL?8>(1KoIz#5ToMV z-G4pfbr`Gjr6!Dway)LautImYv6>G&kFMyqv^l)4_6A<;x;k2pagImHp|b|> z55=4Q`gdM|67I4l20B*S0E+?U{Kh2zM5a5i_IgDoY;npDov{Qtzpn z?vwdFqmK9!AC6g5UZU*64`n!+yy`5FE~@>egfM1y+zvw3dX&-94w~W*w0K~ol1w> zYfgEx%`X8u-y)6IQ7fmFQgaI8dL_DNi^|Asrk;U8@kVTT5hvS$!MP*&MA@|@v`^jA zje|Pw^uP9J=p!kPvJIMTyH+;!20gDX!J;%w@%K2oinPXmt}h!dCw|p@rDC2{)v9id zn>KBU*mP=`Gm{*8eCTA^kG^c3s-uMR>v{vDN+BS7GCQ*x5Q0=Cv2D)@O=-e%y zrn6f~!0TNeR#ZndUGjF0DAldNfNmX;=~3p55r47C-LV2e`#mUJ|G4atoJ9WHOzg}EAtX;Y-88=1v!huTz z>OUB

>|e1ulrQGWH!STtxgmBX={LILceK2W>Cs2SqitTy6Ob+zSn;kPP#B2}7xCe^S%FT6_SzCSUbq9rOyX2+#^7DCAfZwAPn=aa` z%lKkfm$3p0Av$#Ngi@$D{YR~jo78vDRZN!YZi}x95LI=+CexSS~XUx=8DnmkBdJK|jH0e?h=Zw8`8tB-|=L1TP4j z^$;QU&%+&mY>UGA)17dn9SUeCk!C219HZ_WUPB~dp)d(XW>_T$B)HaTN_|BAseD5@ z-c!lB63Fu(9FL?d;lh&fmPp%;fk4$mppT@;W~CxS{=8t-kkvLuI zLN*Yndq{jY{y2t^uUC-5)^-`))pmK~ZcvU7{QIr$`~Baau=wxI^rtCN0WyXRkboYY zhO(3gDBwg{h$A9so7%4UZ(3;X484zSK7MQ?ZBH9*%kzTD&%puVuJR-B3Srmcg(l1^ zNLcK$KmmpF;QGYVB!tVWKn@4)OsAeCNlq>ZZ-cpkx!qGh?x-^Rvrd3@=YQ!KqF%^FcTfwytmKc>DU-Vx}k=X+su-?b0v%qSFu)*CX$EXKX z1PX)MroU=HtwTbsI}b?|_Z}KR+M)z?z^s!BeMGDaj%Sd#qAC$s?{CHka*7}|Q9=X| z`uGv*y23e@&0@PC4IzWwbU@scjHN+)2ME3!eZB#QH%}O_h3$lf?EwA~Wm0d3VD;tm z_Fn^qQtXB)lWX)gl53jq=FW5qep54Yu}h4>P^PzcHW@ot4%ksoe4tsY@m zwn`E}jPF2j6A0+Eq04lNciBF=_x6^HCVb!ey_BG9u#MASX?r_Nn{f3 zR?xE@;k%_krHySgytOHAh(buJDN?cn%*ode)UW7YuYgii~hn}kk=#njYt!pB+ zKl(7ZPXrpT3|&ZTvKWTc2`>5OKDw=nhmg-mcCpX)o+*Q0#kty>kxy)ra3K3qf)gO4 z?sdQr2^yD0YflX#Go4B_UBh7hlF&g8bJCIf_gUTTg^gixX3PIMQKCXClTFH2o_Q&_ z_%V<~%m^6jaCfv<^eo<>g6}A-rHZ$L+#mCEw{uf>nQJ@kx^hwcCt-$Ob5T5c$XwW# z(E2sDp0jYu8o@)aW{PujPjp_}v$Gh}Kr27Yu?efs+hphG^9N|dWWYhqPt3-9vR52t zGAj2+gV)N#ssxJN-fC$0XeHx{WC_5eTDsNcp5TFB^ReT~hTuN`0`oKTNa-$fE;Q-qmABJZV8upfgwEAnT$RuXydsYp*FmIY%fzA_ zUi8E}#wFgDmV&D1xn^oodEe>yyPKKMn)STw(A@34Q22=#ywq5>-l&%c*9YU*(!SG6 z&*@S2(c+?7U;oE~bUt0Od;|XUnBHaQ^5W4?MC9m~=+t-leG9f1%3rH8V2k*z?W_uz zRw7jT2EEc0yS4C;NcI^Jk^}9@HlAuP4_^i-*~E7Q@5E5@_g^R;5v4Eib4o%tutK`+ z&y4s-Q{}`kN-6yN!O+>szL}{K9*<+5ceZyJ1)wveV%jR0q60UL!V~mrvE=lcp^75^ zO5=r)U$qy*gJTCvYikJh{YZu+nIaFP0V4ze){zvXaT5HP{gpAwY?IPWI**189wiD4 zkFEqk>L!EeS`hbEI-jE7`zs zcmMC-OdN=slqUA9TKRk_SfMZv18GtNo(K^9c#%P3k)ji&y{E+@oB9CF@I)3K$(fkJ zg?^P*A7~8md_n^*nZB@4=_-h+Vlr+UelK&BG~~ETDEQ01c!wedv`e=@KyAB^4TXp2 zorU>3t@+)#gK$5RJuzz{M6EwMG2zK6nwk ztFsfQ1%KG?KO#8aRM|x~aF4WG2x)WOX~EyMf=xOg$9`|uq+xz~vtCLD0+(K8Bfg>t zPtr~sUW`Ve&TO3jBMEbK;Qew|AF$eAA*U|^g-of4r%jAO^rk9u3ITN|O4M)`73U{EKhpz7OwQ2#4l6B^q8AP>uN)huIrU?ejWNz-I- z@~FU{thQ=UBRj1gykIE4SKiU)cYsTL8d~T=)JvpTg`X2Xw$?0A8}?5M&h9GHFtNGG zVgy$#IsZ;|8Akcta5A7;`25y(`W_RFFedvThmi_hUC%k|)|=G{)lRdhv3G@1bF2*$ zH=>$axh3>|>xx#yFY)MvBhZ;wmSx{60lI9bkglgyYxG7E-^{U0iH)|M9urWp2DwUr zF(EZMbR($PTDIJP0ycS3sLjV7!`!JE%T~oX4;~scoQz~Xp8S3zNKu~1Pl}`o#|h`5 z)kBC)R+jS6qboW9{zG8EM#uSV2lbx9TGMY}_5n%jU1XaqtG{FHY?f*j_b!e6l;}{5 zHPYCP%zIk0+lwcFP|^uR`-}TT`2TTF(yfv5a-HGsGzVI2*H*t`l^p6KwP3DfN_^a;Jp96lb2nrw|w1I9+cse_Tix zuh4ilYg(OgD`S)y&6@puRatc<#Uf%8>VZbk+ddXesV^6#P98f3xK)^$Bz>y`!ZzG3 zip6zQ0buVRwUtI^_$9)E!@>kD1U$(hfv|%bdq3+RdhR6YUJBAMeu+17n`ZTQMyEnb z4ntRA^yZtt0r%IocTRt49=mgXnNMFE>E4daWWvgoq)yvAs|JQ>sZngNKk`l5IF^GG zmpgd3IuhCVw3Yc5k*@|fKYZkHAYd8%ytB=4er-#Ngh5^}*1nP}$Y+Qx<1#}E*7~T= z!f<3K0B3>vA-eF%9PV-{F~R=#aY85b%13T>2zVf|I@BI}1T!&M(5hPYq540xPd9sfyBz1~Tprnm-V+lvrqW5(3g%hu6?uxpD`&av8=1 z9kS9J$|>!4I|=@V!LTd?8g}^}k9Nq#2CC;LZ1x2GJ6|}Dk3WqF6aM(sq>x%}Tvnf3CAxQdfP9f+r%&76PR0DsR zS0k*-t_C=^iBp&82A;D1gNFPn-AEbvLXQ!Ty$z1H+~LBk zDLf(Wgb~41-2Z`$Vx|@ekr7Mq4UiVxP?Y&yL0bHOu&Gos5UKRzr-d_+QAgKf_wJaN z^XQX}^ZIpo@|%5y261X)*X9YMuMl`}1X!trIy2i=Mx*@{Rj6`{M2c$K6;_*U6p`@1*qnLDYhvY!I2qyL`3Q$1)Ba5IMH^KXLsq9Z)2KJw9dE<-J(b0l;tvLxfoaVTqJv+ zgn}ZLj!rdixzW0qVLuherVA{KuD(@DeKG*&i`do}QUVLU+N` zA&>pcu*~UpbFFz@dRpN7jYUUr3LsK9Tu+Z)ds$h^Kl+%Q}z9@!zi9aCb7( zy1weCQS);0jy*ST&#?~ap#{57zxE}=f&XNbb;b#`_S7gND0feUjUSVdaocv)?3$3k zcDi1^joP}|jXXJDm@6LBJ9LRYL~P*i>cm9W5)b~nhrZ%El_|dUVX|f=-t*VCt)}B; zb)~$WGQIPCZH@VcPR+En^I0Z6DZTryjHV?V{M5m{U_^xQK3=W7{DX1gG)AYPZ>0Yq z`TTXMD{Cs@v+@YfT6;foHmHzHLe+b2K|Zs#30jJ^2x=WX8m@PNpao=jy`^R$Vn zpYyFzXbU&VN2RES`P@-9yo4{CoofdLzuLhIVCDLqD*N;(b-0V4f9{Un#OLGvbpHKU za#Hl!%tMQhzN#}IAJf*ll(_5Ck|ce?S;3{DcKu;T7n`MPw9ET*Ff#R|)?Ljgvc-jk zag5)0VT(^su6bVVMWh9Bbv-DcA@H+dJ3j3P*GgJC=RM~HY-W^M_Qcka&NW}()7dOYIee>pAAqc5V}$Xpt8g)CPk>$rFLa7Ayz z$D65I`f|c<-}>HO>pXfQSKu@Tn1DZHQ0-01|2!V_aKy3mdB3}}q&z+FefRy}x!|Dh zxnM6ER7C;!?}6X~ARyT9i-55qy|ICl0iChoMdnz%whG>mohx}N14D?JVOaE|;JCaK z#}y>C5tV#`Iy4cXbw-9+Y#GmQQ>f~BI0y<_^bZ0WwMzBj!6+Pz)*30NTv&EAl_V89 zClXlSqetoEEZL`b2)MZMm%<}ckJr04@2QrfjIArr%ab)Z-k1KCh54!EB>LtBK06oI zOxe2!pDv5ErX91Im+4O!{*KtAOj9e*HqKV3FWYAmy{n2_ubQXq%`VTc^N)@v-7C9} zuKUk<&*G=h%`4BYi;towm#Yd;IsT28o<6{2ww#!TY@S0PMG&@p+HSYo1Z0TP9tE_N zakPO89>4eQaw_Tv^HAqafB2`ypRtwC*t%b4Z^;`DZkx3H^ZZuff3i+l)=q>s??5z_ z+yZwJo3uSc4|xq1$$7GgC;Fjj2nup$i~|lU%zDRyb}_rYE+*Igg+sZjE^X!R>xz?P zGx^-TKDsWKPc*-bd<}dUj$Zd>kWXGL9!63NYEc{~quqttYNk6rf8 zx-Wkns_FWij*!3NusupiyO%zsyB*BJ0+e*2lj*c2Fx9H+nv{wOz7}idrXIT7cUwLO zfrZ7nyy&evz1Xc^U3(85-}D`~c&rgAlG7qZeuBAqX{24%I^7O{-KKe_pgVlaNSL!Q@$)h3(c*^Y z*=mAU5@TZ-0MUVW33uEK!B$KK{Et5HD*?-a9$Ft@&s(Lyu3o8fMc9AS^iem6%k z(_YcwjhwVk+oZT@1jp+uS}G-@hUb?M;|{1x`*r~75LA|HPpdRq5@yyrD-5SNG8@SyFrNe8qfyed;k!`&Z#7hY4n;jR13@^2Y^z=wt`ZC?vp|wpjaAAFv{m59J8r*Ky){kx7M5Q~Joa+_Kc%N7A z?r%s1gVnKW1hWma`N|Ot057uY7QSx5~|9}lwM4H@M~tDG2it5>M>w=*7(atFkFS1?LNCDuD|@2 zCdDRDDMN0{fWnyGJ^2n=0;1b?fzKi!L}+;OqkC*Zk6q3ZU2q=PA^L!S78Q7DN_C6M zVbB9^5ec&)8yLXDg?nDYUyig;UNiu)rc`IBn5;>3M5Zcn<{K`WT*N#!1?7h$?ECJ+ z*JKO^B{E5SHs0d3bz^%9eoB1TCA+g_tUAkk-JtpXRVp`-szeI<>N*a*ycb7yr$h5w zH!Lg=X)W5AgMG4z9_zV(tbGCzJ58=(Ci|%so%YLm?K}$k5_P6hPkE65lxfw9s}I-H zcXv_z_5iABX3w>)f(M*eG?aa(+5BVVAd)C7AZKT)M{RwA zNgv!c&<^@|QS?2w`O@0Gk4?}kZU(Fy?6dkSz=l_Ie9z(=!jauo?W`3_MJbtVxI3Jt z3;*;FpL;V&-QhKdX!J^sCzkMfZ_Cm`f@s3Hfhz~5dtQm2V1ud~zU7KVQtoE!krn&# z)IhE#9)siEd^A#%^w+qVT%kK{ENQ2b6^chTVN$W8jn0pTFK8nt}9Uz#Y?m&(f=jt}Q zBXOIzG4?mUP;||$=gNsCD|Iv}z71TS1MqmH zkMQ+|ls};Ie^S@6ULp;p#Do?1*+l;wPZZprR?TES3!DDhFx(tT(+WAc!x9- ziNbx5#A1;d>SKvlg^r`_ggB@-Brw;Je?mN26Q&i zdf0!%hW87-neBN7W4csO&vqsDT*doomR3&8CZ91r-uAf@LFZbAB!B;NQkJC}cfy(9 z)k>jiY19bwgfiotRahkXmbA(sXSdRC#=`0Ds7^>QN04(wqe62Sy{|r1y42AeRRaox z3*hbA0|ls`tRux{HLtM$*Flgy)w!le?+EnTze45b+DY{u7ICEOD68ZnFBwHTM4)F- zYCB7}R4V`k{=VCsbmKy4(bJ~38p1g}aX*#L>Zp6h^xIvR z+Ltg-VzEkTZsixUfN4Z(3;!Dfmu8^x>TbF+7< zJg+(hb6)&3>4C>IrWE-hWiX8=ggzEnLVcx`wL6o_fLg32I?MNS$*NW9DD}rSA@cls z#)rI7{_{u_81X87t7xy8y!YmN6HoTx!ejuE93z0=H*!q@f$lBBUMLR<6J^Hl>O$hX zefo_VW0Tq ztV_%19QA3!j&vzWOrAQC2Xbl^&T~j#o(A?0zJ@clO#B_NgULIR+x0k;RHpM3-UN-2 zXA@|JT4s#}B>pf!MBh#IStK*XH5e{N$=B6h(pBIory6)V{1mj7p1q{m`;SzNl`@)t zur5Dksw)G6%F5Ofs?yCz6bK~;Pt`BLD63|NRT{Q3F~Qt5RiB)y;V<9NIZNd3po}|_ zxaS2O^`n$T@|5J2OkGY9E^bU&LIUF`nkO|{Rnyh^ANvoeSs0ESL;vjJMSnZaI%;dl z$z{O(_gYQtZ0oObAAMjBl$-vA_&d?2f3D}?s6i{UyZM;YZgwqZ+Y)mQ4jm3_l-(68kV8PQp9kWOr#Hwtzzg9If(Ltg`-CAt8fs8}|2x3G4@(;Z3noT-Mn)ze+5apA0fGdA zaR*hozj9rr;ROPMO8o(Z@V(H`#=yzS$l8(C%0-{n)yne1)5>9uJ?Y`;1GS0^-_6D1 zz=wd(uQcntXwR|PBF+&YZS2BRc>O08S?pd^F=L|In=iEPU?mSI#*NU^qS?*zcN-X; zkNeaQTX?U}E1SL9Q5Eh?qnCZUX+MSg^KPBbwdpYQcSCj>p5hnBiSTZ&{zG^&%H+G+ zER^i_q&VHufusd0h#^9(RR&~*?K2~{RA%e7aKA%UhjkaRvtAk8uYK3 zI#*{#sv-w97*Y>Qo(?s&-`wSRx921Z-ha`;*%5&MfC`H;LxNO-OAhS#1Oo2?$03Rh zR#N;eJV;*kcuzJt*1!qy6^%E%@n}YixYZ+lIc{*vZZt+XGA;l!Kp7hPt8@R<%~dW* zM?4&RlDA=2B7Db3qU z8>{-y#j61u9CGOz2B~+--#3wYT%Y4FRl`WIAJDb_K#o+L1 z<7vE$W}5j|k#>u_G>&PDv^o-b;C8D}DUAenT<`1;2gg^y1Qrv`RU@IjHE6$PAW4`q zavh`Fa^9p?q2lhAQgihUEBgu=3$02RTe8CnRw!Lg)?r?)VlQeJEJ$+_;;474LEDDGYBs zzLlXK+cXmvtr?*VM=_Rd0#6z}&sBAvIG-Xc7NR(iSPOO3_!X8c@^t)*ATsPQ}zOzxMT zD(6$!c7z$u2<+tQu`&~mw7vPE!ORY_f zAL%OgqP2O7$XOP)V$-D+&6tXc|4bBUt}9#Y!9)3}{JG(RQn|*S-I*QzXQ}@rHmZF3 z*Ch)N?#+(n-o5*KqmGxG_utR8H#U>WDI1?(6q1C@4maAut_e zrucl*^lBRkfk~mZiJ@iU58_!>57oE`6FMWFD!*IB(3^_azbsQS!mP}&8Zu%VMH{Y| zhMh;ljBj0ICWEcFtZ~r`?hu?=t(~x)B@TvG^&Y*bdtky5sjR7A=lGh4J{E7=Siwj= zjp5r^@d!PrJr5&2O43FlNAq$Hu8oZg#6C*whT?_cjT%5JT|m^{>OgN-yt&z*XZyw5 z#b3wp3t=Ou=GmY)cxZ4BhCDpX7*moOhxLcDrY6NAeB=#3G zfG|q=ENPf>`!Uy#4811Gw*?w$S}1V$sfdZ?sD=UQxCc}x^ov`Q-6V!}RqPY>EPrmF z6mqanp4fTUlAqIqh{B&jHYKr!lG{q$G{c?Wd72WqyWkV?yL4tpIR66*L&GI zBsVoEl(|MviWI}>(TU!U&fgEcFw#aHbZX61`@E_U(MaQh)RRDNV%}(G9v5#yo0*xC z&c2o<$bf`B(XGCwM~s1UM*4i_ndetUyBMuIcH*H)Gsuoi_t5W}g$c@|Ig5hmu0Yen$c5ze)y~(Lw#D>2 z6IepHCA{6G&vI&A^OELYSuMPCrko*C%i|yi#ZV_Y0in4AheuG1AhtsW741Ey5rben z8amhlYvW5^|4omq&-+E+(6mg1Uw{Kr0rMJ(BBmtz5xe-DGHP;y?MuRkg`H;AP6 ztfhodEP!OT*h@cYvq6?uPw(KFZ$`P~17cW)v?$gXL0kDphqiH9>lffxfK9V8E*smQ z0>rORm5&eaVzu0lh2#E)$Z}+_$zl#{X-NfT!m~$zSYHbVcR&l{jz`{ovl325iR3BT zzztQt3*Z@s(_$~(<`;GBY3kegLB5{#>x=pMG`0{R$p*Iy|N7>(-yJgLO^OdON`OQ_ zZN6$!g^L=irj1A+d#oAx&4TPz@-);OfCM3J1zq+8-ltgXroOq6R~D@U&?J(JE@drQ z8EUueVMz#4zAbI>;r%fzQ5%6qa=a6nA(S?XBk> z!1J*=P&@v6sRZ{<>~XsFk!L{eAxznO0=tr8q?EO^V-yH$f0L-G(_vQc^IA=ehD@-y z>EpO?&?(};Iyx|%I!{kbe36#iE0gRech{$-DW5-uNBY4^#7fiB`cjKtg}kt0hpo&U zrgaMAEWdS1P)WyD#rnAfvu(e%ogjATWBy?fD-&)Dp>2PrUH{#aiOpv<1Cx+@^5G>h zgQObdKn?c5jDF9h4?1Z?JlKy*6))U4bkdL^ElnoPR}Q;Jmg_!ZRi=g(?5mb#7#d@a zYk}=R&b`3w&Frt2lgZxD-02_2@TGb)PUa~DaUd+`;4BSTb({pjir}jD0pJ{Rivmfy zcFd5-Me_-NG8jEQ_UU`{AVvunOMmM3Wq88wUtjOf9JWQ~lLmkP$`Qht{wz8yx6QDb z{=|L~NU)iH>lwi{xpvT?5yW^&({s&HJJq0)cGvwF1FRZF;i)e0AXnEQQ-xc{1(;dm zB2YoFpcRLpWA)QH3G*rkC0R`(G3rtvj>WM4(5GY#XY?O;=QOv%Q7GU?qtu z$22K4-jDAqsC!}Af%`OnYzUi^vSm_hXsi127HNOgCb9O{_Fq8Cr1h;CH*(!hJ-ozH zRWgQp(mV?_-|G51#q`2i`AdP=drKpOg>DW%y5wED&fQ7M+feo`!AwO25W4zptfl^e zf7a9l*!rJ+*2Le*q_lt4!ZgbiCvb)y(qdb$LnxvczxT$l~A6(Y-mtTeoUxlEK3(-ftj7V$k^Z_n5KaVD-w=8r~;zd=IoOY&>=WHr5T~s(JXh2nFu4JR7>dUBSH{C_BR2qQ~ot;I@gVT zTyV3#!#_ti;Z5qTEPEs*m&y-dmv}=o*T;c-W@=8}*9j(VSFIdzlRPPd;#tr7RhVFU zS94xteo4V#)CM-IDiVyO3B#vf<`h~=BFO+rtB%PadJW)ENadN?j~MK|s3 z{&iR}NCS>g2}m_f90?N1IZ~|)V65djY{f zE&z1I8PO1m45$vMP6V>f3iv!8L}h{q>qn?#71|G5Zf)B_Fvb;WKdN6by&x@k*e%L| zzG@b~f>x11R`_aE;~jx(P=K1qfXWHoFGzceVmd*3@D@FJjkFf23LDIeg~rDeqpy_= za0}yT04ShckDxmFU`Q*{yA}!ezkCBZm%r%^Z}D+5Eaxq-R`O0%0u8ny26ZSbV91|* zpF!G?nmo>@7Po(eJpiXE9s?U01Opnw-4f#sI$IL(Ep0To1yu1!Lm~Shl|$bGYOv~7 ztcfausL>D}OAsE*$g6aY#G7pgM(5n@5qMvawAF##s_-r63oNTWR-?r%4v9z0CK5#zlG^*A7D*@?u`hv+(!+=FOQ8u& z0az5J1V+?2L(9Y!4X29H$H+Xq3)M$2q|4; zl=JXn-0Fz~zrx&m9?aVx;$eLbt+oUf%ngZWM`8U^9k8^kX15G@{PD@|Ur-l+J)F8u z{LU{XfqkrUYy&aQmxhZ=xni6Yj41Xbt6CZ z_K$cx4+#E3+sAYtDrCY+&>SlDw0-3KP#bYNvnW%VP-~?8lTkQVfGg!U!u>IWPYbQ? z1=u`5f^U-`ScJL6wP|-dj!tGx)6>wNf@cusQ*Ui7_>i!k;93amN=f$o`izcOspif4 zy6O7Sp^ko~qgmi^<~iH*hD{RvGO(?wZLZ`_)L1*SJx;Uvrq9*P!>u7u)aFaEmY%@7 z$#uxVos7;o(8hm$bK2AH+A9+lrEHCX}g(_S3A_CCKWW6rp! z{~*Ek_rVlx@w=_v=jGzy7PgKICbJ<%Bw#ihn^KrM`xQ2^@_>r7D|gO#;BtG{K3(Yf zxg|G37NS+}z~bcGVmm1KC}?u|3~mB8o$fC%8<_Qq4>sQL;K9>!*wkWk@f6}OqaMWe zUc`-|OSlz%Q6#^CIk^fZOF6ks{wCFzG>$i{mEE1yyvagTkx4WwV>iL36-ze~RT3zj zk}^uLdC<7hE-84?B7#&vhNP#P_vcYD8^m-`8nZ#7z>iFsT$I5W0Vm?XI*c05k#tC< zfH(|T$2>#z$5XM}HxwH?Va||KiI0m0h zup#B72f>%{F@x}RMEEHduZRfDZ3WiNl*Q1c2euTZgdl+(x(YWA+uks3k>9$+m6E6^ z(-U_6`g(olPwI6^Ywn~Zgf--P&C&XdQgT&rM+|Y554y8Ix;1VU=XL;;2}cjuZyi@R zS!s;x2LBBDSWR+FTN52nGA=H%hE~kylHdP6KpHaR8tR<9U1u90VvKQ(k)r3qnm_|? zwPv3AlwXz{%JAhJyEWm_U0E#X$-@v^W2(P1Z>Ygn%@8An)zKs5O{Goa)XYtz>F{Dy zx^_Y<>NZ&{tn9aX=Fuf}>z>byIY=XW0bib!al)KV?e$t4>EgXgP^o$JA4*gIA4;=F z`7fnC|1V0TszVpkS`Kd4-Zi7x1;}2QIouUe?htn$rbE$z&9qY`!(b=NM3^i|6d6M-Uo%tt^Fb`is55}{?3q~+b_A@2&^iZbQmfjmtf{{VtMXr@s4ysudWxcVRl1lKO1 zG@e%3FJ_{F_iz(yy0LCJsia!$g5ua9+q*~WC>})LkjDa$xo_^R#<<5${=mGj% zV1#~a1i^6Nc2MmQM;|qUaS74<5~hxr`I>NxAPF6X@9IIv#YNXJ%kuk@MKC-St^>h! zXUcV_`02>o%gw=7|CpHPCI$;Uvp6UNGAC#wCrDY)8DgKl4>Ax-?+E)8UL5X+94<*P z;*MOJ>oeBK&dzAARO!B4ykL4N+&;p{4sy%Jcz#}GG*z&lY)~Lr6o6?XS+oe|_$F>ke zP^x(nt$W~^EIe4G!9)V1AUAktsr8AJ7@FT^!uRbqm(GX_;nMAwv{ZcFiiGMCv0fjI(Es1`(8B8uxX|6**qa&Xl?{2<4l*co>m7~46E*X*eK;l9 zki*%0{V&5Z%oJcV8~M+Yz&#G`NlqfHvy%UZ93q@K_$P(a~yd|sj&bTSxK5+<{ao&EfF366u{2lg5@bpl)B`k zagl!tR!Au~aI4nQ-ZrK9Uw&o zHhtlLnVS81sC{@>x?nYS2vED{_BP`G{ir>kCX`Vq0JQxGVT(#c0b~sUtU{Ouv<j&ZRG*j1EYGV{rmkV~J@r7X6p&(hO;?!(RG?qvg((x_ z@}XKCe3bY;Wt*Z}DHxVvr$8yr^3+lSqF+vn7r&Ys8RxDsttwTyfHn@wet7%kU?Vu$ z&NQkr0ice-6M&ADH?36B3mg!pgzJ@MQHYMGi zS1QlCa9#1e_mM90@F<5So)eXSdlx^HSA~^VU4^e_D(Y_XuxiA06S++i#~X{urYUQQ zdMMv|D6NL>XjOmhK`oh4+$9EO>LuoewT(~;qm&13SLIjxD)p%wIVhVk%wyM$DHn-A zeoNIVZE1MXHi8pChU5R1s%`(Is`9r~Ef**ytUUdrZp5I=&x5cJx|RelDF<`wOMnmg zKcJNezfcPD(z7`R0e%ctiw=B)iIH(7$YZ-W6oaeDb8RgHvH+T10;&33Yjwki6+lVo z8UtP+z7PO8(1WlbnnMqjCv^XZOLmszGQd@rq*WA(v`9tyX^jCX1~}KxR@z@^+%Z}G z53ng_W3>^=EM!B(xi~_!#5j2vf+?nN(rm0brk3>cL#w&W=*U!7byVz+jg@BG9D-Ux za7w2lJ^~!q0o`@>lOLdjRkUZ~C^22TP-* zb8S)6t<&+j1vwU%$E>bHl=F68?;$fp7(9L)L# zRUjzR@jZb2gFG*ECBIW)ZzZ}uWR%XF6%*YVL;N!I8v-FRV0V5$|8ngFi_RRE*@h)Y z9dUF42f`A4f0Xjm8CfDz`WX;|q#;7s((~xKKuA%t1X{58(gc{~PNeqxpX6FNHd-;V zgbP2MYH3Bue>m~WqB{cHsrr8WP8RE zVTKiq`cE7az=yOZ=E1Vli>i~24+F|j2;>J2)_rUC+W{UnfQg&2I$0)tFQ>8)ua`iq zG)D-*rFv(pW75>xj;`C>jTbzT+rVqBzO z>j4?0Q|a?>ai7eDJl}3142s^sOM|GEi(C;r&BwP6(M6xjP9sGr;HA2%2jBG0o6DAp zEw$DyBaZu&Bo;EjWeMRX_-O6ayrjmj_Oh(k99^<3kITY}&jF3gV$HpJ4!a}9ah98N zI5`{m=9~=g=iT>1n2Vmlmq{@=lp{equjsJ5Wm903-80jRwm35NkS%q_`ep%-V8RYi zJu;y<47$oofb0ni!tMd@E7pcN;WY>^pqmdmdeTlbA&9IiZ>|_~W2; z-7R(h6^4W8NxLP0Vl)97L!drlY@O6kf+lcg8tURFFUO@5 zG6R0{ua@5Om~DTMm7Oo3q@IEovEvyPi`vH`NlKo}wQ;zMfG`pm&-{l7Q4%t-ENCh& zI;*`G9t-cQqD@t9LI=$tP_mPTuaR6hY-h?mF@NAz0IEH3zdfq5M|hn^8wc%&lJZ9@ zV-Cd&!Nt$1vPWLcjBa(ioWa4pTDku%oNyd7b3n*JT}9G$)aF5r8N5iJvY9- z#qkqhD0C9dbmS_TtLd-z`@?Nam3ZTdxU!r(u|(3Jab?ydN@$>H_YVBL%~l_pNdz;~ z+~yyeXZ_P>WNlT+?CN+^)~-CD_wM}N=>MP@O-L2&wUc}CDiR_k@wHR$oQb~A7XP)g zC~{QHM?2Cd;lp==Z^I(@!lwuw` zw2>>hiVE%=fMY zE473GQQ`F~A<;=c5~5g+hPuj0IN`Ukro)X=vHR?fy8jW!C3WB87|_w_U(1DOwDvuA z0CK1OSqqV1b(UyVo_TRQ%sPP0Q+<&%jn)nJCSUz41)S^H$IZ&=c*1k`)7A}XfKQoh z(Sk|`+C)dN_k9QY&`R0_&!x9xP%_x>FL?^YgL-=0OVn9REXG|5*F)UP5@WJg*)pJD zV0Z{NQD5CD7Ms=+7nJRsh|RLY-o?~<#q|dIU-SpQeyzG-;jJ3Tp|X|5Dg|SdhcPo? zJu{%J!sRJOEYBtND{0Ck7fuulnU>;`zzha~5bW2t3g^S}Wo9LfWM%ru0cl*khtzX( zl*?JlOWQA%fgdR)CsFZ?kOCU}Vwe{eNTYUYl#HSgEBvwb2@)dB!IeMda%1 zdf9XyE*jvUY*XA} zji1;t02q#8zH7TW8{;hSZtV8(Zm=L(-Zk}*j@aA}Bf-(04SE<<8$omTg;`#U^UXt< zR9zl#z3}mW4LIjm0kz~0=`@fhSb|y}x@&Nw*y5(^gC`>U++OR--WBFob{oEPW0L5F z=Qy60`)wNX>UGZ4qZWVUIE$*PG|cCdxeVr=B@<UJkKY~oD#wJJ%^Hrx|I$nIoc!Zfa`l*z+&!K3 zR~zlof3XoyY~7tCHjKCory80{3HV3$$$|QUJ<`XKch+ZdHpx1-!T80{_;Ppm->0#U zv~H9iBiib9O?0d*uLtGLHfuZv4c~?R3Q;A!w9SmIpqKI-kyuzt~6S9{ic7>-FFQ8-IP?W4XoUEKFnM5-Z0csrvYIS>xSyP;9C*0qSasFvPPX^!W zIu7DVN9Z!|*@Qo(?dh@i*~xV0*SN(g9z`gM`2?p7u2UwEX>{1mOmV@B16 zS*BuWuu<1KZ59r$fo-$LD%JQHw&i3ITW7gkvAuWJbkX=&Czu)SG^>8!IkiU2bcpRX zTB~8Pe*9L`nA&!mF(HzFptWly-2PHB0&v{#(>>d)NLwwE5MIJ6pSp`Zv}%-)IoxZy z#nNJ?#F{Hv<=HMy&H9Xinp)3Go1KM zre3Le)5xe^uj|>|M|7Af_6kc!fzAlt``CE-AW{1jE2E-gB)v63>arz7yOm+iL~QK# zut8w=VqXf!o~_1x6B}4iR*m;%UEj$%oJd?8*3!0^V^yuW*zw!*&Kb7!aD1{uSgYe< z`^G6NO)?4NnTPIK9#KpBE?q6{Xpoc>J}+!xQh&{romN_Bi}iqi?J_VmGBO6%db@qJ zSSKKBqsXc^D zSGJvOuc-k?cmMZ<+Uq6YpclQEYxkI=Ea2+tHbwGS@OlA z>%u%Z(n|HM?JK+jbM=!F?bqsmgId`2Q>c>-2)go}snejhA*ps)8~UjPtP=@tY7xN~ z@V0BafLy%!yi&I1$QIa$Mh)7c_rqJa1k^}3easoJ$)LN^F6{cnE4u&CcAv%F!rm-& zcLiP+QbLiOd>q&x=ez4SQ`J~x?E5+_4{7vrSL?{NbC_2@xJBR5KeqHUX@wFZwz#Lh zId&1L@6JoKi&UqRK_UV;6cqxs!^d?zu4O!e{b%6yf@ zY4Eck`60wgh$nvh%8mM75W)`wMMyL_K`nX1Pk$^>?8EKD?LL@gzOdIO2fs4!a4sf_ zq#zb&j{sKX4^&h9<6MkrN_wzT(BP0@4nkl0wsJxR8 zf-go6hA>E=7{V{k&Fj{TYYR~iAdknNJmY%RjvF-)*C(9MGX#oIP@UkwJ~G&$GIW>D#zd~%I1mgVU_v1hsGSPU(umJ+lBlB>U;$Vn8 zB5Vzje*eW~yNt*8Fk~CYebXz9Eu^g}KD**1kRlr>P_T2fpu4Cb>t0M8M_vGlwm*<< zNnpQCoLnlTlCuvN23%&UHRgdPH@LKqh>wW-Z^-5hs0wRRAiN8_=tm%^^czuAS}>(i z0u~t^+<^REKIjNFtYGE>q-)b)*8-$LHS$#g80IxjJgw6_dhbkrR0N>u&oZARLA+(y zzd%`8D!^i6u_U$ESYR?(t~eHZi&~YFq^muVsEu`I!WW&NGU(k6iehiI3DVlBI+oUI z5r@ME`oeq-3A?FI!Q&px_%g=dbDDRdlvIhJ%O{@baLX0!5TTZNjHT%f{Vlo^#jD-- zYOL*eH&@uGq?Vvxc^b+;Q^T`W^n#k%_a>h4;2st>TzGAR<*bSNz9d%Yy0bd%oid#t zUaVHab2CXI{ogb`Ka}?k8=hZ&?NLZ5QbMQ;hg#T;2cbnfngb*RRp=t^Uj{8K zqhAcCM3Ps#(M|_0X=!gQ_CjY~+}TnotFk%=*sTFfxPIAjA6JA=e-Y}fn!pH+@d=Do za0NRa&E4_p^R)&nqgn*mjYy5pY8F$Zc0mU%_tER23cL6@A^pkF8pbRA1;MO^{6h#6 zU>uQV$B4}|ljev1n@|$i;uF}a;P#(#9EO5lBE)OOTo_r3Vh;xe8>Tx`+3o@Tg|Lpu zlHF$AoRas%7_uVv0P=kU@@+rGI4KmJz3Pb+1W*h`5-(Ja;0{f2+Aq`8-Y3~tpQzvx zg{&#s2_W$DAyjh&{Y_kck`!T zH$#7-j8Gi#tYG_&qSAMFL+IBmHHiau1qyamW}s>i>yO813us)7FVQIOwA-KTk-HVb zbS?aC8-ii201VFX4#d!Lim}_inHlKVw!+>@mWQyuoSR*CCLeFlTc zAyuMrK@Ok36vdN5PNNj1?2OKmkA{3D2l-QxkA!SDRp$8>^#kkubtwO=Ir-N5G7e0b za8u&$ZXdkeb-;~%vj>}f#^(9%s|d8}(=eRAA`3x*Ms1^022<7Yk^Q?R`3A$cOeRP=6+p zjmwN3d+fUXnU&kHcxhp?p_Qh*Vy~LJUh^$vY_;d>f=q!A3N~Wvr22C2j{i41*0azU zk5zr8iej>yzj(`1^jDjDa%uO(V8zJ4e=4StLbir}9a)zf{0D0uLbQzV(enPAk1y$2 z-kL5@YZoJwGbpgLmPCW(gko5SGdW(ZNUB&ZbCT7nMT7rs2=|wk)9`GKkJA2g;bNOI zlFe8=oPSA|>uF)s3Akh44d4lXkymXBpSw&bBYT-(DHHl|ISlvHqH)20gk z_3|~)``~58K5Dw<$IK03Qk!qkiOqZ- z7k+M{+2~Isvcq9_RlWLA9=xdVdFWq5^{Bb3@9*-5d-FF>tGpt`;qXbkU+m4SUFra! zbE$ke5DD~8 z(p`4g_`1ySrcSDlS6<*Knb?=^?Z8~I*)NiPW`e_JpA3DZj-Kbi@T-2k&k52z3_j&q z18s~OwVUP8&;pb04z?>+2ZD$Y5{BKfj!cFqHe6Bf)(Ebbh2IH8apn;%v~1&XQo~l- zv|X3hYXVN@?U$3rcapiPLIs+Qve@a`h6W$v6qV~2R9QuHHL>c;9{(Ndxg2u(j;+N` z(oOSi_7#I%d2n0PW%5%PNiHw1R>9+=LPnh*2H$u(s|x>VY5s zbIO;5!vSbe?6{Y5zOdGs8yFa+!R2Iv(dThs%VI2(E8Qa$YAm5Z!h+9>8D@{I%^6|# zw0ys`1pGM~6*Db56bq9@N=8;WIsK)I1KSsH!d!@0EJ+73UHpU737>}~*Epa+L%{#* znk2&vWtPUqRtaUf8dlhaEn9xihX2$g8J|dg0QQMwjt{K}o6u;COlMtcF9o9}u`zGd zSoJ1W+#-)J9oF*w>qctX_4<;4b$D6ZTU^D;Z+Z&?* z#GfpE?ddLvJ6uti_&Gw#a?$BN+`0)vdRWDB0ja=K=Q@{}NWzk7?A<@s-fsvPnQ?;) z3aIQQ_T7u2KZgzPH$E`q!V`2EMg5f^=)Y4YhTbp-EE4sir=zOj?J%i6Ap=Y+2`|{^Z7ClA`td!=@s3#VgD^8JpYETZZn=inX#T$30{WBmtqH$-z@K z!XsVCKe@y zfX~P;Z{9Y4nCng>Nf)tx(6B|I$W9mlojOaEstg*1{SbVOS=4O||xv?To6>##wQ(zNcw^mQiO&)}e< zf>|{3TMb|ZIU;0~@z>VFb^|Mii;tgJ%KdjP*-Fu;{5y}@k0+ajHzN-;9z9ZaIO(oJ zr$=c=w~>+ZsqxBlhh&8ygrsO<{l>-~!%*)Wo-Z*4pM+hRUCgwan?1$-EK72ftGis{ z9moMQ|H7^kudp`*&9E9t1JX>9#n!8OB*ic4El~@HAJ>l(qHO8KVPm4VnHgJRN)hC! z8e9(9fhr%@?(qk{r>(x8S8tAQw}ggkuyA`|$$wXh0dt-ze+F8yNO?S2fQG&ziFd$&+hK#9qwO!X@h;EX-!|)XR zgf`>E=Bjae2^o}YO%r+6r%F)5X?Y}EFzfTDxjDuYeDotUuZePKgIrr9dpt+yN!2>G z`&=wu@e7DBz*&S>{US7aO=rTSAqA^)#_)(qOb4J^zNIgLRr=?=PWT5__zo7 z+h2dRX#2%w#{w1}4E1R|A0s+EOCQ6Se}1C6aRVWOK3A zd;J)X60Lqw;-SV+)w2EvUKmj--0#Gaj1}`Yxyd?WuFixSp@IO_DC+6pk${>NAQw*+ z*Cg|chF5uW%6U<13c7XL#p5?!BVJ@~fy-OMpDdo7)dIbl%7H;J!6~&DqY+0jn|7-d ziuiyfaPSn&+o+~13peX8m+lTZCjXP5QMHzlH&i5?5XfXIS-XpyNi`?0c^7Xr=-k36 zVhGK9uY{isp+p0_nMo#w@AxD&qE=C~ti1QLCm^#z=3-K1#;;kH=WoIz4*=9tzQO_p zZUceA&r{5N;36{qd!p~8ZAqVd?}M=ePlfJhKbfB@9c%f6bQwBtbYolAZFSh%5(6ce zQ-tg=_9|6#$uL0 zZavPDr>FA7y%f+(W2{$)X?nUzKRDD4U&jwxBh-eexXiJNqE@T@7f_Bp3b+t9lV7_3z~$a3!c4|qOWw1&(2OB zT05N`=oT4n+b+g`*?7j?Oi$AAq@PN}%gi$JU_aPH9nO|1KIAWl*I`6S8(?3ggzn&ze^ZD{9*)g7&T1DqIUvxlv-9h=&THm)#6UU4Z z5%OajmCC|9qMp()`Qn<*09Uwpd-c}lE$vFS*U?mP#!P^w_ z*O$K^-oa)|OD0dIVWm$u6o-m^zLa;pcyRB^>l_dt@R~Xx|5S#1J<5otG(I-6Fg~0) zV$MFbWPh>zD%{l8A}|GI zAM6)hh<{>xeKY*E`(1NY@k<_)P7>6hSXLH_%4@;v3}8fs$pbAKR{Emh;)>JHCX_%44NFae?GJ=?|T#V|#3eF?^IoYh2QC zI3|Ph{1>)nYweHEf8CQ^YFhSY$o$|@VWon!7eOJmww1AJaIFBY$j5Bt!z=6N=kwj2 zJ;muk_lNiYy|o_F5>&<8-*|@<_Ipo#0T2-M_Y0q)KAoYSqaLlH{zc|^yrvTFu#F2@ zDm{IGiGE1*Bmab~Bl{Hu6)`HgctrpL0;}{?y{Hmi3ML3OOD|AiVcwVj!5FIW$xtLV zT3fAn12vm9c^P%N=J6l2l8Sc!>Pgo3+)H{jk$@}{wm$xj3>EGR3PwVr|*>1O? z^A@Gq(>m;DORpWPvyL2-%da0rW}89T2Zw@h(C_zuvRu+$9-SRr54tk1$Fnvy4qoi9 zcssqHK3Ct|uR1rk+`KP7N}r^!;5)aTyw={Nu9iESAGtpqkKW)be|wW+8vPC(gUv%Q z=Q9ff-DAPWkdDkJA&=z_TD=(w%@>izyU6CceISc^l+n|aet^$Z|Gn6KOdD{t5eV^C zOdUI`kZH{6IpVI(XYtd9zT-B@;%cUYY~nTGNTDE=-S@yBg*i=frsF}1#zt9S7!>ESt9zV59ZABVcbq@%TaYVNiau z&Qz?jyski(6f^85|d?}dG8%Nd_;lwWiL>)C%iwa#(PTQBC5(OxSl zlb-vnZ4z@wX_wJJu%D+V-<)}^}7 zV)R?IVJE3<{)G&U(n|H2JRF_&6!6gCi+%pHdrZVi)Ra1+c*KHAFq~l#PG1vd6e>>k zJ`fd93^w3{8Hwnt@kR3ETJh(~oj!C!38sD$@mia+hNLKSV|_>NzL7Pvc|CP-{42vH zXWQNb#_*hQzk0>U=Lau`c~Cse(QkdZ!m>D_x>ZnbiS3Mn)OVWIXWywa~y~f7r&koKk{RlZPp| z>6+EjV4Krp?hN#?i%^N3hsv?>b22ar9t1DFk@Fyp8DyD0ec{z+FsYiL(^gVn;gQ;*UJRntpcUYZx!^$;a4ipwmS@a|ReNa=pvEJe8iMEO*H-E$MQk=TRZh`mty0EEbdp6DM( z!F)jzWgsHWEwm=3k43qH7U^Fj7j7+w%1sa+Cc%Xy94+PZD>p&lCbOik2KthkqB;|@ zno=#;6GkIvV-s$Zdbfb;)Yr1)Cba{S)$2+Rhq(E{!gt zvXQBjgh)Fzbk8Eau&GEUGe@t{OHAL8Zi0e7if^_4ijH8 z@$sc6%;Oq%jppZ&>^oKu_3qQix@-}|DXjzHHFRBl?eA2zSnsJ!vTd0*OJjgiy@*#G z992R(BXVGxHICa3qP=HEzoflAByVEzG68n!uBPb6l&!`5ZMx$zw`p`p3Og^g;YTDc zZQTFI1LoQvJfB+cO`8Sm+-CRI>5FhLDC=Q?!_{H8bolZ?vF)9Te0JYNbGW%NB7NNc zy}QHfizY-(Ysj95-K*e!=8UYW5P5q@sZJcnFpkky%bITv(y_SpV^u%8e}+|&NUr9I z(G7&)2ye;__%g(13{v>|mkUa|x#j&Y^)OA)Sy^(4ywlLr(LjUy)COJu2)%w*vd0QR zZQ&PgDu`onzi-vi5s}~f2matr-lmq-?HQw)qwd+*-Vtr9<`aa^I%s5^6(us3G;WFPCO4h7nHfn!^A z<+Y?qWa$=tM1O_RdHc~KHuP(3zQ!eWZ0_`S>~(l~R}!z)uQ_yl7P~T9Ib}}2dM9pW zIxjd2pv?vPw6-L(H3u*CWG0U%0CF;sVZy6T@LdNA`=Z8(-p&|Ja8n$KZ|A_SERlPx z@TyUgn*ZE4X-vzhGFRpo{zUO43PEyp_89HO-d@Y4&$j?tlNW97l{BXP6f7c_=07(o zjd^j39+A7{OGky~+eg&DfpAZlZ5nEuL>BlE1$0-m2$^;pYi7ulkRmwdh;QD&gDo+R z6_tKaH}Gi-N0*o3F|dx0bYt z5bMhf@&sD#2xfTF-K4%~)kVAfpD8M_T=$hT@^DF@co_-YI!Dz+fxTUy{xR zH}g*t6gWfFKem70ftH876<*^vllJ7n)-Rt=zXQhSrUyEb$=UjP_-RXsfIk6UD(h-E zf*klmWB{R32*MkNqU8=7X}Rl%29HIl{5fPGA<%)m>7>1?ndIhdq3VcNy`1~_SfesEO%LUk9Q}m zveR<6u^@D`H|c7dnI5&zU@ObVW&qLn6Qm_EIXrXgHK)Kxxf1e>BygTFlZ~R|!r3Da zIjl9&kL=(Bktll=vlM#wnipj1kZa#N*YbkmmLv(gJ%R#4s*UVM&*O7-M(&ytSv@8A z83mA2Z;$1Jfai_oBH;0){orUXgM(nj&cPqO$0AO4nuJ$-`|73K7T-pM_Re_1PH`>YvLZs_O z35fJQpnP z5=aV$&kg8!dmPfcbsp0Ht-w&eKM|e}JJsgoRVQQND7PM7HHMQr*>n~~M=}fS)|2_Y zyPSo{NI8!jx)e+)zKqOmKSvJZug9&QJKmM^ZQy05xEDBeV>jzysOL~HW5V3N8{L zx51tyl3CjjB7+C#AdW-C;g=xokCzW_^Tdu29zjQo>vs6VhYhZxU|~i&RMs8m>0dfR z65e<6m}05RE^^^Eb}U?lc+<_LRFi)cNs!MLy>9h^|691fL+o-|UhO)tDZRbpQw-^C z38O}5&nTnZC&+Y0%^dBR#c^U{`x?`gK@!)AlNi(GlHejevOWnTlmbg|+t$A${* zJlYZ0{5s2g>X{RZ7}HmqB<}?@SyfxO;_+z)@M&Jrc}xBMw9R!qgw75&TM}d8_;-CX zu6pg1n+}g3jsDFd6HrG_Jgrk@1% zd@-u25lktSN`F%1qMG>5_QdC2R`1Q8VcJm8Mxw07ad)(ss@AG_uO>YI(VqwW9A9R% zUE8o-@UkO0g*2Z7uXENUldlv7+($;_;qAQu?b%1Sos2)TTW^gqg%awbc8wxIDPCjh zX0XFW=5hS5S}#0GdIg&R&H`A!4Xi`PtzblqDZav~T>r~iZr~HY=Ij>UzC3KaS1sG& zL&f>6JM(~PUAZnPi?Z6&kQnRD;=_Q6RC-1#Gh--2yih2TsUZgJbvv_bx6PjaFKV{K zss3pbvd!DSlGyHF@v%jPN7lHc^I(0`e?Qe9>?aW?*--~;8 zTFp)zcjDta%IA#nbaztniHr)nfVdQlQgV*GhzM7LM+KwMu?QtYeFLG0-!ppA!6-xo NJlaADgG5FO{R3Xb((C{L diff --git a/library/mysql.php b/library/mysql.php index c2cbbda..c6be019 100644 --- a/library/mysql.php +++ b/library/mysql.php @@ -28,52 +28,6 @@ class MySQL { $this->_db->rollBack(); } - // Manifest - public function getTotalManifests() { - - $query = $this->_db->prepare('SELECT COUNT(*) AS `total` FROM `manifest`'); - - $query->execute(); - - return $query->fetch()->total; - } - - public function getManifests() { - - $query = $this->_db->prepare('SELECT * FROM `manifest`'); - - $query->execute(); - - return $query->fetchAll(); - } - - public function getManifest(int $crc32url) { - - $query = $this->_db->prepare('SELECT * FROM `manifest` WHERE `crc32url` = ? LIMIT 1'); - - $query->execute([$crc32url]); - - return $query->fetch(); - } - - public function addManifest(int $crc32url, string $url, string $status, int $timeAdded, mixed $timeUpdated = null) { - - $query = $this->_db->prepare('INSERT INTO `manifest` (`crc32url`, `url`, `status`, `timeAdded`, `timeUpdated`) VALUES (?, ?, ?, ?, ?)'); - - $query->execute([$crc32url, $url, $status, $timeAdded, $timeUpdated]); - - return $this->_db->lastInsertId(); - } - - public function deleteManifest(int $manifestId) { - - $query = $this->_db->prepare('DELETE FROM `manifest` WHERE `manifestId` = ? LIMIT 1'); - - $query->execute([$manifestId]); - - return $query->rowCount(); - } - // Host public function getAPIHosts(string $apiHostFields) { @@ -175,7 +129,50 @@ class MySQL { return $query->rowCount(); } - // Pages + // Host settings + public function getHostSetting(int $hostId, mixed $key) { + + $query = $this->_db->prepare('SELECT * FROM `hostPage` WHERE `hostId` = ? AND `key` = ? LIMIT 1'); + + $query->execute([$hostId, $key]); + + return $query->rowCount() ? $query->fetch()->value : false; + } + + public function getHostSettings(int $hostId) { + + $query = $this->_db->prepare('SELECT * FROM `hostPage` WHERE `hostId` = ?'); + + $query->execute([$hostId]); + + return $query->fetchAll(); + } + + public function setHostSetting(int $hostId, mixed $key, mixed $value, int $timeAdded = 0, int $timeUpdated = 0) { + + $query = $this->_db->query('INSERT INTO `hostSetting` SET `hostId` = ? + `key` = ?, + `value` = ?, + `timeAdded = ? + + ON DUPLICATE KEY UPDATE `value` = ?, + `timeUpdated` = ?'); + + $query->execute([$hostId, $key, $value, ($timeAdded > 0 ? $timeAdded : time()), $value, ($timeUpdated > 0 ? $timeUpdated : time())]); + + return $query->rowCount(); + } + + public function deleteHostSetting(int $hostSettingId) { + + $query = $this->_db->query('DELETE FROM `hostSetting` WHERE `hostSettingId` = ?'); + + $query->execute([$hostSettingId]); + + return $query->rowCount(); + } + + // Host pages public function getTotalHostPages(int $hostId) { $query = $this->_db->prepare('SELECT COUNT(*) AS `total` FROM `hostPage` WHERE `hostId` = ?'); @@ -696,30 +693,6 @@ class MySQL { return (object) $result; } - public function getManifestCrawlQueue(int $limit, int $timeFrom) { - - $query = $this->_db->prepare('SELECT * FROM `manifest` - - WHERE (`timeUpdated` IS NULL OR `timeUpdated` < ? ) AND `status` <> ? - - ORDER BY RAND() - - LIMIT ' . (int) $limit); - - $query->execute([$timeFrom, 0]); - - return $query->fetchAll(); - } - - public function updateManifestCrawlQueue(int $manifestId, int $timeUpdated, int $httpCode) { - - $query = $this->_db->prepare('UPDATE `manifest` SET `timeUpdated` = ?, `httpCode` = ? WHERE `manifestId` = ? LIMIT 1'); - - $query->execute([$timeUpdated, $httpCode, $manifestId]); - - return $query->rowCount(); - } - public function optimize() { $this->_db->query('OPTIMIZE TABLE `host`'); @@ -730,7 +703,5 @@ class MySQL { $this->_db->query('OPTIMIZE TABLE `hostPageSnapStorage`'); $this->_db->query('OPTIMIZE TABLE `hostPageSnapDownload`'); $this->_db->query('OPTIMIZE TABLE `hostPageToHostPage`'); - - $this->_db->query('OPTIMIZE TABLE `manifest`'); } }