From 8b2ebb91dac7359eaba3722428ba5457647c5ca6 Mon Sep 17 00:00:00 2001 From: Cosmic Horror Date: Sat, 30 Mar 2024 13:53:05 -0600 Subject: [PATCH] Omega meld of so much in-progress work rebase this test(image_cache): Add tests for image cache headers rebase this f Migrate over to `http-cache-semantics` f refactor(tests): Drop wiremock (#320) * docs: Describe what we use deps for * refactor(tests): Switch `wiremock` for a custom impl refactor(tests): simplify the file server (#321) Refactor local image caching Hashing out more of the impl needs _soooo_ much refactoring Dirty rebase artifacts. Fix later rebase me away slowly getting the final pieces together time to update with `main` Nail down most of the routing Dont pre-render SVGs before storage Get more of the custom test env setup Get the sanity test working refactor(cache): Switch db from redb to sqlite Use our shiny new test utils refactor: Delete dead test code rebase into commit that added it rename images.data -> images.image remove redb dep lalalalala f get local image fetching working Get more tests sorted out Shrank test data image rebase original file out of git history Test suite is getting better getting closer Add etag flow to test server test: Add support for dynamic image test servers rebase this: lru test mostly working rebase this some cleanup more image cache tests --- .github/workflows/coverage.yml | 8 +- Cargo.lock | 91 +++ Cargo.toml | 16 +- assets/test_data/cargo_public_api.webp | Bin 67482 -> 36614 bytes src/file_watcher/tests.rs | 8 +- src/history.rs | 7 +- src/image/cache/global/db.rs | 144 +++++ src/image/cache/global/db_schema.sql | 7 + src/image/cache/global/mod.rs | 225 ++++++++ src/image/cache/global/wrappers.rs | 194 +++++++ src/image/cache/mod.rs | 552 ++++++++++++++++++ src/image/cache/request.rs | 129 +++++ src/image/cache/session.rs | 68 +++ src/image/cache/tests.rs | 765 +++++++++++++++++++++++++ src/image/mod.rs | 34 +- src/interpreter/tests.rs | 51 +- src/main.rs | 13 +- src/opts/cli.rs | 11 + src/opts/mod.rs | 2 +- src/opts/tests/parse.rs | 10 +- src/test_utils/image.rs | 62 +- src/test_utils/log.rs | 5 + src/test_utils/mod.rs | 1 + src/test_utils/server.rs | 345 ++++++++++- src/test_utils/temp.rs | 21 + src/utils.rs | 4 + 26 files changed, 2647 insertions(+), 126 deletions(-) create mode 100644 src/image/cache/global/db.rs create mode 100644 src/image/cache/global/db_schema.sql create mode 100644 src/image/cache/global/mod.rs create mode 100644 src/image/cache/global/wrappers.rs create mode 100644 src/image/cache/mod.rs create mode 100644 src/image/cache/request.rs create mode 100644 src/image/cache/session.rs create mode 100644 src/image/cache/tests.rs create mode 100644 src/test_utils/temp.rs diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 85bf2788..c358314e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,7 +1,11 @@ -on: [push] - name: codecov +on: + pull_request: + push: + branches: + - main + jobs: coverage: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 568d4da4..fa03ebd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1271,6 +1271,18 @@ dependencies = [ "zune-inflate", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fancy-regex" version = "0.11.0" @@ -1679,6 +1691,15 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "hassle-rs" version = "0.10.0" @@ -1750,6 +1771,39 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-cache-semantics" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92baf25cf0b8c9246baecf3a444546360a97b569168fdf92563ee6a47829920c" +dependencies = [ + "http", + "http-serde", + "serde", + "time", +] + +[[package]] +name = "http-serde" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1133cafcce27ea69d35e56b3a8772e265633e04de73c5f4e1afdffc1d19b5419" +dependencies = [ + "http", + "serde", +] + [[package]] name = "httpdate" version = "1.0.3" @@ -1862,6 +1916,7 @@ dependencies = [ "anstyle", "anyhow", "base64 0.22.1", + "bincode", "bytemuck", "clap", "comrak", @@ -1875,6 +1930,8 @@ dependencies = [ "glyphon", "html-escape", "html5ever", + "http", + "http-cache-semantics", "human-panic", "image", "indexmap 2.2.6", @@ -1893,6 +1950,7 @@ dependencies = [ "pretty_assertions", "raw-window-handle", "resvg", + "rusqlite", "serde", "serde_yaml", "smart-debug", @@ -1907,6 +1965,7 @@ dependencies = [ "two-face", "twox-hash", "ureq", + "url", "wgpu", "winit", ] @@ -2168,6 +2227,17 @@ dependencies = [ "libc", ] +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "line-wrap" version = "0.2.0" @@ -3531,6 +3601,20 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.5.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust-ini" version = "0.18.0" @@ -4523,6 +4607,7 @@ checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ "base64 0.22.1", "flate2", + "http", "log", "once_cell", "rustls", @@ -4614,6 +4699,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 90721866..f7043df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ anstream = "0.6.14" anstyle = "1.0.7" # Easier error handling anyhow = "1.0.86" +bincode = "1.3.3" # System preferred color scheme detection dark-light = "1.1.1" # System specific directories @@ -49,6 +50,8 @@ glyphon = "0.3" html-escape = "0.2.13" # Parsing the HTML document that the markdown+html was converted into html5ever = "0.27.0" +http = "1.1.0" +http-cache-semantics = "2.1.0" # Provides some extra helpers that we use for our custom panic hook human-panic = "2.0.0" # Generic image decoding @@ -72,7 +75,9 @@ pollster = "0.3.0" raw-window-handle = "0.5.2" # SVG rendering resvg = "0.39.0" -# Parses the optional YAML frontmatter (replace with just a yaml parser) +# Sqlite DB for our image cache +rusqlite = { version = "0.31.0", features = ["bundled"] } +# Parses the optional YAML frontmatter (TODO: replace with just a yaml parser) serde_yaml = "0.9.34" # Easy `Debug` formatting changes used to keep snapshot tests more succinct smart-debug = "0.0.3" @@ -90,7 +95,8 @@ two-face = "0.4.0" # More text hashing... twox-hash = "1.6.3" # HTTP client for requesting images from urls -ureq = "2.9.7" +ureq = { version = "2.9.7", features = ["http-crate"] } +url = "2.5.0" # Cross platform GPU magic sauce wgpu = "0.16" @@ -197,3 +203,9 @@ ttf-parser.opt-level = 2 rustybuzz.opt-level = 2 cosmic-text.opt-level = 2 png.opt-level = 2 +gif.opt-level = 2 +image.opt-level = 2 +lz4_flex.opt-level = 2 +tiny-skia.opt-level = 2 +fontdb.opt-level = 2 +image-webp.opt-level = 2 diff --git a/assets/test_data/cargo_public_api.webp b/assets/test_data/cargo_public_api.webp index 23c0c10f163563349dfae6557ff7facf4a6723bc..820ae73a2633484adb0a31e621044516b987c409 100644 GIT binary patch literal 36614 zcmdqHbChIHw>Fq%S9O=wUAAqz%eHO1%Qm`Ocb=6e z<2gI_j*Pu`#ECe$LP0`Q^fMX&P!$oBQ;}m=g9QKp>|fUkH~)-4Ak5U99V@E>(00jR_gWu53!Rd?ZeQ{P-C%b=m$`?m9G}AZw;^kkQ+Tp8%Uwru= zxxs((^MAPMfAIfs3e`bLLFmgj)Gs-{>A!G;f8mB^4%S~f%wIahM%Ff8?SrcQCpY?s z|Ne(tTRDIA?LY93hw#QW%8Fkl@z;e55Ccd6qycgOe1HMK8DI{u0yqI^zDk=fi6cPa zt6uPbu*d!nd$}(wgD)#{fZ>;gFu)dI4bcA&d%!OhO;@K+E2osPjd@4nP|}r66hdj1*UG3d8;;_Kg=%M0?aIeM3GZE&=F7RaAvYmn zhnaDGdQ+)|NQz^jSORI z^RDVXFutjt1Im24KJ`|+nT)#?y|_plI`d8JzIa;Bz^w%QTp6_qxtwVpnHwC1swOu``kE*+3Vu` zM0(r0M0&ciyn5sta)&s{dIj!ujla>n`(EoxtIr0IytCUU5vUG~_(XYXd*Ax90Q&e0U1z_ueQdq!Z2L?BCx9zJ ziOZ&srcc}Ztdz|YpfFGx81kOgEqgb0v32V+;;rJweqVw+GkVwd|BjZ8^baUVmX&9< zc-7HFkBgcS>jQGT*Cw|x?Wh+X{kGD={b9w-qJt)Pb-&zveD+2vUfJPmQ@m94N{Jbf zvRT#g@qdF}7ZnpGy}i_}ZI2sYJ46Rnf}Y_@x6H_p%_LWv>uhqqB>#8$1-KqvdFOq4 zo2_y_J}`JKzv`k!F_f8f`eM#I#iDy-6vmR4U!BEzs+wrc^vv8{G^Xm|BgHjY(UFr! zBIs2TI(1C{2J~bP!EQzc!qPn zJEqH~+;o7Z`-J`85I4L75zewQik}59Z`8%5NSZvGf0TVx3Dw?Ojg`ZByW`KBS@II7 zb#fDgG*7joryqdpbafH(hn~=`jOHNCUu|+(P2#zs3Zx?!j%MKh#g7XJkY}*P|NQHK zWI6kLw&yRA-t6^to&y4jul~qL^%)$l1%zIsbD9FTTK6M2^%!c-2cTI=^cd+>rr?E2 zCY(kmzQg1{fvD8cG;Q6gM$Nuf=le=E0x6vsg8^NImF9A1g+dO>R@8bRYdGh*jaKx5P87Om>etLFr~xM zXcau{3hBidU%Nk$Mn1PO5}_F)o{z-*|CrY|J48DI{56a&L45S=KV|ok7k-=&cBMxC zJopAwi}HJr8&q@ZQ&3YZ!c-WYO;3;BzfJmcvngc<#rWTW6r*MZwdfqeN>ktR0>L zuC~|jbSNp4C@N>BLSp(c7g9Q_tB^f5>T%FxK1F`u{$F$DpJGbI1|agDGbfePDXi1x z?cK@MoXiI5!wl;gaBz1)(1xFJ z^@$25wLzMIQ1qIe#XQofHTZ_Q^Tv6nW$i#%*}O>Cg}2Wo=g#H(*xqtS*1=uI?8(%1 zykg^0(QNA%zH@_^f1a3F?Hw=76h$D?14gO-;qMH*DC9IsZTVjV+ua&-jxeTPRI;Js zlO2LAzf|s9>)~l(XT9K+mSMiom%!5vN4UL*aW`{ZESuoRijT@O4HAP z?A8Xuj*e&?`n||GJ8k7fi!5zEn-JVq5i%Ivp4CCSEhWpX_imfMF8I>u)^p=Nc;tF4 zAR>tx$Q1O;8YmmIxcV_gO3>FjWQYm*;p*GZCprmUrJnk4ks_l+KD(l??Kzq^c7@<+ z;`A-&*LJ46_`{=eLpHXPyv2Ru*}qvr;3X)7`T}}kUFkI6k-+k=y~5hEdhP$WarQ4x z{(q$FCz(WUI%ZAr6g*k%TrwPyH)3IEVuQ<0!S&vJV$@N~`qBnz|E632DWavxC&v+g zN#u%+GF}T1|5^1AAi7sNa{c-B7awp-mU379rC`TT**ESB0g+?>nTkcNuX3r zSR=0s9Z*{3IG;`L%%g+E*0l^1HYXT4lsHuQ>qS~<=VUhbwcOLfQcbdnTDkimhv65*JmKF~%vCQ4 zKM<1@I)+<$clBl-M&R2sQ8oUjgwdi^tJdWc^!}#T{(j|p4d3OF!!Z+H`>Dj`G(2pQ zln9F_*5f3O9z4TrD#u+4G0(;XXE3>-^!M?Xa@-lzQ|hONr)ol$))_Ga(sVIH_wn2m5=ezqzF)!Jqsngus7KEc%}|fOw)F59cQ3 zRL9S2M-K{c%i~Rafe!0{mNfvQ4)(&q8YYkB8AtH#MiGgK+;U3O2N2;yCq76Rr)iaz zvo-*fWsp3d;Pjt+;GOG_}O!LKs7#k-{qk^^_=}L0bCmmauK|8Vus%{ zBdACvts|+e1(ZNq2w|X`LW~R_JZ^sfA~u@nJ?>o?B9Xwk-h|l&TGxsIhuDQp$`I`Y zRai{iQgoLgMb5W*PgRxzkdEvkRkbwZ8b(=L*0YehuB5Wo9-GkXI5tIKJslLGdhZ zw@_{_%vzVM?}ML(C}M#5ZaW-aVcC30(|su>_l5M07KG_iMf1V1lEy3v0j|P>^-z~@ ztY@!1TC9&u?a;_T76`W)-%UPB5~Ig37SIgSnhEbscMp`n3LNF0C`gsLam&g+0xt+S z==C1so@O*YiWu>4iz@?>sD>=sR8j24Yx+cy7vP>Td5=zUZl6AOay7LZ!*F_Ol%Akt zRjwo=fIe+2uPBf<>3qA|Wb$6ajN0NI*rIzp3>`H4N|U~xJVXxVYH_1-?qQDEF3f~S zPLDeV4c8bp))pDJMlbZ-aUugGDtCq7c2l1)>+|r#({B!h$mCr#M_%>_D;hHKs(s@^ z%MKdBl&@T=T&4}g9<=6bERa-$nH%FjcBtz+wxNxh^BU1r?Mi=A?6lL7{O*&)&>346 zeoqbbP{1}}hF6BUcb2_>DJ1AbOr0JPKu(5#K+SB}oAEeCIt$Ae(qYfFjX&9~eF>Ro zI@jN$oHQ;|mXW3}5K|J_pO{8KUy@0gn1 zMsShVF-kx~yLI1N9?Q#(P6HmL`SS^fV8-^Fu)v?IzD&RHz1dtjz99b|@>u*UEH@A; zF+B43%nH-yusE@3cQLBT%YVU>bkk#Oee&vAI~ zkkOV6CKO3|X3MyirBzvVr-$UPd#CDxK#JT_&h;nYIsqh9tR{ou$Bq(0;-@y(C-}P2 z_~I=_b65#o)TXVo5NDP=@B0u5`#O&}#IUN2+x>;PJ%7?4#q zYib}_zksSvh&yLF4r9=gE&9{h=VSMgeeC85N6!nsm@ZaQV{`_<64mA$9aRs@?|r7} ztXidkrk%g$a$WqlpA3YrC82tD&f53N3|FO=rYEP3^agqCa#M~WM#o_!<(x@~XB)Cq zvEapy9}=hD?TW0!U+md9I24XZ#+GW)HkdbxiTy6>k`;atKWfks$!d}?&f2*Yk>!RC zv?D~)H=XXvof9H{GIYeAf6pbpFq_BxW#Slx8w)*jr%)l##n~L4dH|2Ktbr*uem!Er zIXMB9>!brQKpBS7X8vdvZ4Uw-l4V}rUVhi`ZOhV^DCg3j!9}C!;n3Yr{1I-g zpS;0CLJ#oyF;ooz=$$t4tn^1IRL1_8)mW=KM>K5Q@I)#15 zs7Z}x6|0Bd!}%Ip=lOtbl`XDmw#m@dv=N-9^l}E#f-eB!5uLoW8H?K7MDnHbNoZP&&HOcQ0GGtZ zs=(z%KcQc!{T3O4r}ekG`N3iad+kcHI-i6gF#DvU@E+ws&rn`THBKO*KET1$@G*U` zO4d_>2E$KrSQHZPOqFJcbXK4WUfjW$Yi$s7qb}=evn*ilN|Ft-bCDPytZX_(08!(R z?uUoWh!>{zy-a=6KF^|b*fh3m$GHkJfyj-Fot-E9Hpz`Ga>BQ7G$C|*L+W6o&j=HN zSBp0ygJw+Z&x3!ljf4u%Y174rVp3RuS(+^4obAVpuQkD{m#2gv4elDL<`E52!6BhM0YqELU)M-F}&By!mlg1gLeabwsCP8tkqE% zq$&>^kaoa6a5I}^F`v!xkGjmnITlwWg9Y2Q{zBrP74v5puSH-kV< z)VAS9&x6vWEMn8mY8~xaQSRN${O7PrYE~B1s&&x;UL<$mj!gE7+WYli`!!r%{oR3! zdyf=jO9}zv23|L6K}R#*-&pBU4*28-wT^fpqX|JwnSeFg6;R5$Q9R=pd%CvRU(YH@ zhmIC)Vjz(i@2J&Dzn(?7v}Bwu8c#>`BQk)JR%;r0*`+2DDYKz6x0oT_=Mc)J+yNdF zQ2c@U+UeVs<0VM@}2p}2}N)p~y*6Dqc*C%*a!dFoa>So_@hQ;zU@X*(MQ?7G| z|D;L980zB-aYp|b>$dc{EhTftrbi{`m%Uwr$NQ-?bTWq{Upb<18_U~Qh(ogHkVaa9 zU;Rf-3G!mN-_GD6MhrduF2k6pilibrIXzR%(cg>9b~boviJgdq;mmmY8g8*SypqjY zDvbqKH=weVu?%5f229s*VW^W_=3#zPUAdcr9@nnZb3{3r7q{3yNfDUuPM|oxKm16f z(2>4sK4+&q-l+}qeQV0B@}vb-EFvmNf_pxXDG)E?h?r3rvAWV*f#Yw2;}xtqDTP%` zNr$;+I7?+k z1p3^Qu!Yk_>7nYKSVwO}L$(43ZBaWF&!0^}893wTNd<;aLzTJWZMm7QdAC9O_gtBOw__ zb5R|>Q$Z1~)~Yp`Cp-B%^JX%ZP5eCBqP(pC#@|2D56k2&4;JZ(&dDtE4dU14^C|nn1zP64GlXbb`01V6xyxh+%&aphzfJ`a z^-$r>i)UrhNSSV*mIS!tt!5P_aHnFNv~PDXHMp`3t79wB+{$jfMPu<|CtdCi#+2}|AsatOk8%ZnrLox+kf=pibl=`fG?5fbcI*!4Y@?l4~Lwu9c^ z`20V2_8_6B8!ifsjQ17`p8}-Vtq7pDi3PNnc((69MypoKM09RuzdzDY4RfwNpH|V) zrU0jce@;Mhl7*)}g+V7@$?G!M+SQR+Obu%mkT18TB5O~#glvs)hN;94Pt?EtwBNWv z1_{QRgEt&^^sVN;<1I1sa5`F9+;|k!FxbW*a33u@wx`NylPasUWif#Xu@O zzMbK`keiNtx05FY;*0z67+@9b!9;Ub7T!;kaKlj=_)&=O&ZXJd@GB%|y7oUm($Kbn zELmde{Ins0BEXxhnG}g74ep}4L$83!J5~n( zT;HQQGa}5oF_7M1ZwMDfu~zYZwxgS5h0>2ZtsgA31@~nus!8si0eaoFA?fgyMySoCFx_17D8yU zQx_14WQ|WPy+69AJ5Sp_RcR*X*4oX&6kVv|23hoI`s`pdiAp>ZHZ16cQFaxp9}~0Z z+q}KBF`Y0dAUBtnu}PS~LEIGAX(qQ$>Aj9NJQTF-C$r?ZA~Hbn-(%?qIq$h<^=&-K zZ511yrB%|5@(C!8{f-AeEhS(62Mjl6)SP%?mmjJ=guZVvTg(n;PfUy|Gk&5yDD&%; z9>0!OX6j`*=NC>(zHychQSFwBI9u~Nn-*T;6ZQ16H;VRN79GIeuj-BL=lJ3l^=Mo4 zZ~225ftp~9sKcLmii)kR#H}PYps7y-=yseo-LULBAx-^*t=SOpMX|70&WNHasRP*-v(TKF2tQvTlL~31n}mw$WU8YC%5j($&H; zBDu+Bv1^nzt^v@X@7&m2+d>$TVPqkIuUC_3uj%{Xq$Ekwbw-!rrE$HS3!DcVeo`#Y zaKUHDFo8T|?Fu@-xrl4UKofU*(PJsNOh2JMkJDsSK6}A(x%41z7Ax0DHTReEGgx%| zPl0wRC&fD&^0fV)C?7tcara@VSJFffHnGdzZtg#fqO^wZhww;Gf(}L=4`ZVDX_4_; z49)P=h7F_D(JBAL%fN61e|B^NcyT>TcZ8h1W>0fGm(&&vMXBQGv-#h9vIFv(8B$c}Ya`Elm|War#8nhxb@ zUkM9*it!&?j#Z6SI&)TuTg7>=!aU_}+e@A%5I{TAsTrgKgOZkiODZ3rjR1~1Ir-Vs zh+&BPxR|X6Su_yFN<6JiTz++D{Q`5Tiag>CG`>cFmk3<&Qz0F;%atEdNLgI+_wKNr{PmEdS1(H z7r-Z0jog~gd9q({z@!S=z3-Bh_yJ}4_lPd8iUpv_%Nm>?`#H0ldz<8!{`Sn#FV8`#Dmw+OBx=Hj#8 zmYtfkYEsYyUG@{UN=b?s(SH*V2xs0T@xv^SJJK_Ae;gi-u2uw|3qz*gdOkinpXL1Wn_)%)XmC|*tG z{aF57J6}!mlz#-bb`{moYnCLpUk&?!O!!O$KrH#;%-+H?C#!+Sfpich8qXdT)roTm zJOUotn(qsLQ!#-hKp79{Lan*eaxD}iinl6Xa|RROt^CE^^&Gm~dCs1+Hm)!*sg4rK z4kq6jus`i2#)q(yUR0L@9#MsUNxpMExp@DMq5s^7F?h!>pgg12&g zCAzch;x;cBfeQ}D_|o5LUexBj*m10a)!j+}Pv(Z?+)uxB0li6UI_Vs7t@{z0Xu@BQ zC;j_qr_S|soGQ(E_MOQASH%XX39OvP>{?{;)@mYsc=;3QXq2Vp`VgETSCU}JSQJ!hg~ zWkGmIvEWGyV#&?z61=Kco*}1QnwjZIUz|7#j?_^7iD|?8+Z_lEoUh|){QPj?TQ{9YhJ^tfEp|A9NU4pZCcGJIla(#d@H%?p@tNeWUY;Y zSLYEr!tB89B@&SdPd*Cs0X;_;h>Hl{=tbY`ZVPjp6MzhYf25-JgUsAd$`w(KK8vQA zBr!tCFCTzv{gdVH5g3t=scQFBLJ2LzgsIQC?_>KYKT%8dj-9rQMRHWb&*-wFlc&ycPFr@ z1sc7ns|6HQxGZ6$gWk>PWiLdj@R<;7%bT~;^4iAfe*}TW!RPvvrt>uzHCLbUC~cv+ z69y@%-JM}s-?g~LM+>eZgNT6B+&9A6t-WDdux zAeXg%ZsBsun%zY)Ce11PPF5>xk8Tc@(ebv496~`a-IDiv)4Z{#OJk%s=DUqnVtYTa zahQHCo2zAoF3aa$%MB(w172ueNmw*d!9LlUer2?P2&~x3oUFhwhb7gr()@Iy{RDeE zgV`F-yWVG;x;|k0?um?Ignn}?q)=3{5eki^XYQ~^bldU~qpUL1Qmt{4+_K7~V#{JS zR|kphnNl9r<5pX^7Zg)aQ)U7bnQ$7*xhes;rp6!Q zE8dir9urEJd2NA%I!+T!t0v7L{qszW#(P@Q$@Ck>pD`tCcO-lZP5e89aWlsz+lkVy zTBP4l$HErg5)uQO^FcLg&7{$rtXE&<3k)waa`p9*T{Ah{QI2mT%r1Vs9<6iHXUEM7 ztq#3+pAdhKCBW0b;w>Nm_cQc(+C`UfTUw7#SyBmyb1Z^gCmfL$bkf%`(VA&2`arMC zUoHJQI3_s%#@v7Rm>~!Ws;%ycFCTe(!UZ#Rhq+EBsJHj-uF^M=kFa!SO!b6ZKL&3M z41ie7@J(1l^JhOSD2xZChmvDK9H%h22g@S(tXuBYNifflZ^ z{a#a5R8)-IGY{#;#)^-V0Xy8U<|cej9hTN6#5P}nEu5@iw?K@l6Jf{GdSm`o>`!P- z-9OEYawb9lVNdXpo6_=UyaBL4qr;MIdPKHj7)~e30>e9!I#FoW=gVZ9@*2PK5zNp{ zW#}mH|9!s(=0 z)7ka9CZ}{^(FA}7tTL2V)U@MXNP5ee(|gSri)iFeWvj0YSQ?~HMd2?P(3cmd#o+F< zzB&f}`EDE!wGf@Er}r`CZ!%7lK#H2Oi7vdNdDs+5Mj7dw+ui|Ha40P}NAhGAF-mza z``aF;_~{zlpJ`w3W|SLzfLQn7Fo~GeKy=nZDl)g!FyK2kioV!f+8wW8KqI5}J=khcR=Re) zCrlS(PBvK! zcp)+6y(O+mDPK4AiOkIL&mVF7HbZ6gvLK3)7FW2tH3cl|6dY!@M~t>Ei4BywO)jT1 zMVSwS$ZJyXRqKkmF&gNGv-Ov1mV1Pv=9{LeVi3X~)+HPM(J#k+ZI(!^0}ODROQX|5 zn$(FJAqpwSCix~FW-n`1K&z)1 zvG4ze3CWBn-zV|JH}=QrxY)mk--n0D^gC{F%^2V7v*v})^QfWwh>Ghqxzz_3;h*?C zt}!dh$!l@=;fyBnuwjIgqRFf6G|$SG-~e&`4SUB@JX&h9jMBF5^oL-SX&q zItNKWZwvt^=NN|Rv7-Pjj_?F3b#3W4d)=jDu6RLL_SBlLm&D?mQ@r@XWj|p{t`qHV zJMi?1k*AuG&*p`YH^zos>c$7(@wYs?aEp2@q{{dtp_-0u=Nl_eMzKp1Ud(|VzpzE!?dI`wgi|Ye#U5p5I)gS-7 z3V(h^`Pux2CU|x{x)d?p_XgK2{biTRiS?A_*#Xx;@m?|8 zlRAO0?VDE8u#4&f;Zp0)=Tq^sQ3=Iy{&#CtWL|kUY%W^=2I9h794=Nl_lhXIN~v$ti8%*+n$i|c!tMVvcB#1!iXFpHpk_8A6PqBHi>Br%}I zL!gQ0k*P=iz7(qddYipgj4p}*ES0OgroqXssY_xJ;-jymEn;{}Kti)weoZiW(?#}u zpw9GgDYQNj?!(8g1_%N96(qI9%kA{ldBvY|$AoRVTq4UQXJ82kplbP%I*BJv|MZfb z@6Yb>a(1P(Mkt|C?us-}UpENMkZ9^q8XT@K4g_vX#^Vpzy5m5-w9OAu>K+f5JC>BA zU6rM@T(~jG3=v(ZPS!KBT>cj>#z#APE4zUde0I^R_fq))Ze1Ks>F|}wyuzVUO1|nT zIw2sVg=66&J#4(C7BQ^F$DN9)#gYYP%g5n$4NTYU^9{+0`^XqF^Xw;BgyCVI5}H;) z(?~CMj0#d!9-H?(T}&Egh=7~U#vdbyv^PC!?7DIbggr5uA4?5mSYb#=T5C`BWdTA- z$^i6XDz)9o!JEADW;q8e3@&53wTFC0vbkVlZ$z%?jo6}&vhM?<^9Q2k@Padv_SbDE z;DixO#c@MiM9qE2vpqNB)b~dhCYn+#BRtxxr=;-h{&ky~&9qcd&wp9#mQ^wnBiUnApj`Vc@lwv8F zJ4)O`8YzuW$^US*7!^cPW=obDvP!+;j+a?P5MvOw35$Zt^YdmE_N!{XgBHG~{^q)E z8g+I1y7%XxNvh=ln1&r_6iOLVS|b$tD;cRwF@5!X`3cHx%}qQK?tK5JYL%YgsSG|= z$U;nAY5LgH=;rs?1D7F_+1$26ZWJ_`1LA>Ok{L|sJNyJkha`4Pb-M>SmCS_&IYaDw zIlY5LWkZc{Cy8)VG2&}&Y9SP63rkmhWqsOmDIuu-o;i#dvXJhNEA|q&OUPfma@urG za#n}y7}PmtqC--_1FgA7)`NFj?o*aYhR^z);DTm))`VB-pZDqCoJY%Tv!zLMa7G?N zMn=)W#L#b=`Lxwu%gTf{3@|zE zm9I>Q7Cs|K8A_CbS37{Ooo)m!hZOY>fY9Uz8aE0D1Ax=_-O(=9k&i86sE7H+eFgNn zf^Th|uhS;u!{4gE*G6>1FU>a6w#zaJh^Es<2}piX$sHhhI?vucD5pYs9Qx9D%4mm2y=FUWQW3arMwv ziK8tAgpV@_kE14(#^eJ8OiPqDxBJ6sF(^RaR=}W(|JI$QF!&3554JYd6BV3UC*ING z=K990J@ZbG1c@N3%<4!3Ufz!27{5pCGYXa<<(?jDw|VSvdst*H;Oz(8a_{=)aZBGe zjZO;(+w9U2KI>Z|HnJC;-~e@f#?Q@Q8n;dGF7Bp1Af!__=w*19V#0aRF{QLyKI*WZ zs2L`jK=wK!2JH#=`mhr6=uK^2-XbfZ{joWVb=V5~tlvkW!qv52UTZ*tWH?5i__5-_r)#Y@VB}D!T+NNaSppg@c0df_2t^Uyrh>y}bt~|F3RP^QTnDM#cGAefEM&6UDZh@lQfB6>--wfE0_Yb}|dfB^}8a%27 zksqX%d`*+Y)k%l$QTm)-FEa|8cB}%8Y!LY*h$5Fv1lCQ~^~WBL@@m9mrGyxMtIQlj zc2Q7umF3Es4F-9u_PCj*wf%0^5IUCEqm`54+bhO88m{AlZR?Cap=4kw!YDsJp(~1A zDb8@Ba6G#jCp6V0*$?#%+Y|o?`J|aH7X}4I29NNYpxO#IUk1$2Fm&VUk&$=~kqt&P z?U#KQNrOJHlqG;_J!QAaaUi z!oa5t;t7wr*@d3;Gf8pdr^8tUQ1KmK2>EVcP^4L2!7RQxJM=Zv1DC1%eL=;LF-$mT`LWM}A5Dl;P zlKAT)m4wcnqLXtKE6TL^6xUbAp^ic}UO_~M%Y!D1l;P>KpC=;tqA$Qyj)0+6Z2m`? z8{Tv`R(%uK%o>oM07vMi#X%Loj5r(IJ=E`hV$tM=h$g zn+rMS0|Ejzd~d7{W@}hlXWQ&qvySD0aKQ65y%}4rEoao1y^k&la-C-El1DN$zsr|j-Twks${Ot;ZVdC{tLc5C#hSx(|P&c@K zbm<6asWim*aRe%;iRBI;sv)}?D=>=NZ2wRbaC%_s^`(W}8LVdr@uUG^THV*3Y|d^0 z;?$5zKHHR1d3p-({D5=i>quY)>nZ#pK0-h~1ADH;-KSfLa&Q-FS2~4|cuR-44tDbV zhj7#Tg3E-UXY?IypfMb2gIcuM?ZqOiDcm)(R&M~mMg5J^QNs^u zZbJM%=g2D9$Q?!`LUK;zyBN6}wRhRDR;Yup3Pbuz1@ug?MfHM`mE}4`2o%Fn>=1$} zeAT|`*I2WgxbzFsQFD5cAUq+-gyEUehSYxaT>l+Pw8_SXGX1et$E?b}M@JAWf&&z% zl8ZuQu6}%NJ@F8?1*0m)r9A-Hz@R+0b8TjBG^hecM*sTcB+P@t|=W zwUbK3nFs~zbmY|E;D@gX*+2GTgnII@06$62 z5@da3cad3Ch0RrcxT)zK9Tq1UOoT#aprlL&TB_QpXVGl;U18O*Y10aCIH2B-M9fXD zN#f!H zrVk5^&zTQeDSaph#2f>tg$3i`$Q6ZEkL2mE)l{-QJ)r#&zn}_Nz>Be)5o)ZWG-cam z*V+9y>u_)NENq}~2vTZyGqpFmffD8+mh`X5b0Z*!N!CXga_GkXUh7Jo+NG}>??{DD zh|g9+I-Rm0jTDbSm*HT?-0x7qL~8Bh1Fk0eW;k}Uy?Tj7fbC+hl#`S%?=W+Qp`Sax z_18McWtM?=3$9O-J$i_Itpfs>7Ai-E1Alu~+UQ8X%}K=j6;}iZmVHzn4zyubQs{+}l#QTpkZ#v*S)mEq_)o`3Dh> zSL9ofIJ-ANI|CtFif$x}%bwhVVw}-k_zn|J z%214|YYF`8Ygz1dn*x8$QfqG(V64?CFnPo}HEXeAeaNgzP=7z05Vf+I9RSz~%Jr{X_{yf3&o;iwEFRBHmW~sv&t5$S&LCiV8^WqrxL4RekN7@8rjU zWs~*8mg|mnUnIE_a~y5gJR-0{Wnqz$`V9oXtpTU~BWP;&cUrYfO{xp^40ef}4AI9! zE_LQQH>L4BJTPTYoQsb=1;;Qq*tI%Zn%YffEEoDm%DVIbCorhE1UBXZnN#|J$9cdC z8STm9DD9g+y2Ih%beDz{ne)?galw55Qq3&`l}m~XPkPk65_~4=P%YMZE0;ziKTS6l z3c{>}c0geLEu3S_l~#Cn(f<1*=w8#?+iRHE)ExJ$&jS(vnW3vf4#Fo$GR<>1rKZSj zypAYI%e81O!^#5gmY2e7_bb*bCIxhFqx5fyyXtNkeNyJaNHHz9ryi$y+nd>y!DyPi z%C|F;ANo?xg6pAf^!a!8EM5F^l@$`EbHCO7G_V?3`6MXvC+QyIheV;)4XDS2yK^!) z%KU8ZSMeqIpo+C0ZL2v?qQ{i)4$z#kqr^3ku^jKK+b9yDn#C9VeK>;eG+C zMOi3U>mvCx-%Rt(N?qiZRcZBtvY5rxJ$OfpbbmTiqw&=eWOLym@FCJL^^Ch|@4wh4 znjGX1oGF{QDZ%j_GHH=-y3z!}j+}A1UOi*VmB6b;2OXExNZcZ=Q8#(ICyE5nAXzppNM<$@dE!wVy-01a^ z^ZZh3Yv7l-e?UsG-4}#}z^K&6+QkPRn_h=OCKHo>eKs|-a}q; zqct3?F;v5KB5KL^aGMLQ17+@0*A*I$Ce)od@1JvY&Vvr~O5!><)Ke(lOH-~A@aw3= ze#}RpWUO|p$87dFLBi)L?c9E5%gybYzk%HF-`3fwJ%!gXj}H~ zGWegDc;<$xC6!{s6PA{Tw+>l12IX}P>Wxt7P)$jJYKdGX|ZPB$(8?qBDq zzUrx#!n9jvIKyEK+oTT}hDudw?)8#|>wI2b{OAWV_{+^pnTy)HwWBUl-sc(m{7Uj? zj%>u_Xh*@s_b8FnqB6|;fX_blx=B9$Ny+)6E9AZpTU(?e{?6746HOPRGIJU`BW!}r zqMYF|V@#XhCdg3VZ$Aq0kklImLM}WZ=Fb@`(|@5Vka6)Uj`dOF(Lvn{n+|>8I(;Bx zH9qT*S=CPoheFgDz$!ZFh5lFx@oQxOGy1Fb40>Ia))D>wR8<>wF$bGb!n?wJa6%eG ziG1}O`Xm_qrl-5WNhqaoKsMlH&-cKJT7}QsWkzX5X{kkwadsh#?VW9+jUf@HI_=*vihRhKi<76oenZd*S6e4KxTt>%8E2O@wz@3s~hA6vax zp!Son8!0-%)0P_C?6>|Zh4N7UAme$~CDP3D5(StyUq2Ox0f3w|uUFRghoEw^ zxodM&P2wSI{gN)i)863EYJ-GYP*<(!DKCTH`q6?=F8*_&vRz?*yRXCHA1hQr-zk9L z(Z6}l<62;7DU3DxVaV_n5#wv&^lEahPUWh=c88sNGi%Oh@j*h}6*hJ}0fBx5ij80A zuYZK_lGfx;H83g%gi3N_Qxru2#m53{3`rSR=S~~i?rhWf-nSD^hWNG{S0$sRuAU&B zb8{ACFV`UJvK*e3d(?|fE;XE(k0u&lifeFtqY#AB>Vp+Nh=BAwbEo-{9$)_<_E*{2 zp1b(BgI7(|$;6f#h7ucqv*kf(ETl?RRw|6!JCca{H}+jK)gf=eT?B<0B-~ku>i+3u zlrSFR{H0a<=;qvfU8`Rs`W#F1TWnJLuo7<`V)lY)FjXml*sGu;O@Q8tLRWosOzHdv zuwtKG+f3*LhG&1#`N$^qLqC|!ov78xQ0$(Zk3=U6%2J3*$79+&{uLCB*h^X3%ahdi z4GBi0Y8>+uhW>_(1?bk;H7?^D7?dz<_&&Se2Ies8t8*&u>M(1C6>cH6Rmt_&uRUW)mAuS>xtXOFr+bNNrjtb2QX zfo__eAVd$W-2T4WGWPhT4zCkyZ-L;24P{gn$wjGQ9WIs9pe^QMkTF}X72met=MYqi z_mU-f-I$;krH1`=i3M;$+l4hWYq!0!vx7#EqER7r6LlKQLcq}lOKvT#KdGzGi|^xX z3d-G%PEz>>6hVNJ1O^8qiYYsP6~py5$VzOPXfLxdza3WVi$EkmSxCj;lVSXfx0oj* z(>bVxB?*FOr5%#LdXcLfb84!x9UE;BC$Sn|Rly;8<$B65iN=o$Macpk9EQY=s0U^m zN}l{*0AxU$zZ+8i@q88%3_=p2c-C|k_518(5`ZZ2AEp?^h`Ody)ns@gl{~(qaUS_w ziMUAVWYZv&^%3WuheMcV*t}A}WUV>Q7O_AUtKG<#zus4@bQRm4^0ieF7L8?D4 zUV6-Kd;@|v@7$s{;`p0ZTNn|2*ygz(>vep92&}3i%g5$XCU+i1rfh-~p9{!F{ajOq zwYhkryH!h}1)uFau#>mLSfhYiRBKW*;ga5eXNzoGSz}H3$|pp$B7cK09r?{2iSrM9 zB$5SP7~r+U_2omkDf^??|G`jiG4?<%R9Y;j;TH#i-G|A9v;d1HxBNr=s~(Nr(6$9Ef@3a`N+=0E~hk z!hACzGkT>OPP*(SEO|BjDR~?I`nG6!UtjEATcBk-y9C4@dKq`DPUMS7tdUg6vR&kc zkA4%qTcif#;Gl{1k^D4w$?7z!WMwl_O`oiha?OuUWqt2;de+%vg_jgdmmhKXPd`l} z2LVx)UplC3a`9t%I`on+MT3``$4D(^yhJL%tN&2?tn`B=QKvo#$ib?7j`y?r z-VP}DUI$=UuBbvJZ(zyd*+!A54!KE1-@(tNi{BpuACVhf#?B0RZIjsDtyTe?rjcag zfa(_P#pMvcaHs30TBj&5Byh28tbyKFoz`}4*02@5Yw+~;_~f;Pm%i z(k5WHPb13OJE(low=~kq%x?O-ECtydtJFLV(L+z@zYQBDRsO@xb}Y!BJo%9Swy#i( zUWaM8iA{3(r>XKT;z?Ziw$%W%@eVduTPHDtpT6A*bR5FC2kc15rME=PTYG(?FCYLO{aairI&Dz|W)%szn15Pjc^MWRx>fN6LH%BpB39^n!E}OWGt*Yg`7(gRF26TJ%9WINTeU}-EHHjtJ#A{j*6L$b zU66w2edJ_VcJ4DY)VloLJOM+i{Bz)JK#rq<7CgArl7SS=jge!ywF)?xqv}6(71PWG zVC*tO%B$yOOgNNVdMF}UgugiT$LT|~?Y$0~l^UbW znkOqrI#BI}It;h?Ukf$l7EN-`1}miYA0ae^IUH|lpFvYi^+O|SqKMBe_m@P@9ReH+F=N zBiw+ck>Jsbi~4M+E+B#)OIKf;q2)%EtMU8tdv=n#XY&n)tgvbmaW+QorRaEXO!+z7 z@Yw^dXGg{c?Tq7-)NI`yVrz~C2Q|?FIR*dR{Q~4K$vh$CwRQQqq@w%CjzpzDDHAQC z+_)$;+L>dRglZ0g&C%8-xvdh((X3UIW(GUB$l&t2OkM4qa&E2B`YOCnO5_gVf$9k% zxh^ij{H`O1#Lzn)O0sQO!+QlP-ggdXyDV&3Bgm-~aG5)6ET3%YYB9~S5*H;3WZTtp zO^D-lxAEC|DE!U$=H?nR(Ay{sLeJVRl@Gz~o83QKd79gaw#ZOUH-aQ>e9fUh_S-L+ z+aYM39b#*mx_Dim()N?d@M)s2l+TkYr`|wj>1?B+oQnG{$r(~CgoVexBW=Y^7@(&U zNs)t%K0wlPRn>dE7axO$M3( zX^hNxV=$QjRJz3~hACMI_M*DSmOhIvU%3FhA=MYH&Vpf)q93e~tmQ9&gcdXVde0k` zuwa=0R#1`Tdyo{$C8TP+V z@XF%3yCr>w;N+IB>D|)aBW%_0E!}CfkG}Ik8syzO#U`r|fN?0}+gbGJw9Dly5F`Kq z6G@&4uAoQVRf7osVTApvndj{^wSD;TS)Z|2qw6ZO;PyV7p3ns|Xu;|LHWc0NLjjj;WGWR$hYcvN@yCJB|I` zz5!5_C6j-nw|v>6mpxTVkt9@s&93%YYfn>Ay34UW>k{vsG2hJCm)zq5k(-pGZ;GvG z@piK!CZmNEP?(LQzhD%cO^#y0b&`_+00r~t8pP<+_WEryJxI$8Z=@JHUnc_rPUxiQ z>4(_qC)>HdcKkq8j(HAkID`UUa{bJ|ju_vqoWCV(&q1*}*MXmwqx_P1sfPZ2h3@dr4#~y_A{E)Q>#BGeee-D6|P=>DLH`)Nqr{D{{(LHb(OHG zkN^Mx000dPM3+;{)@xRYuhcV;Qy+GXI*bj^lgA>B$p9yyV!Z3p6hpW&A=yjbWEJ%m z;w-Q$L8)E-sHdOVvBx+KaDohBh4r7RObQ)>HvYE6q7~c?dQEY^e3masL>{EXdwQvRUtAS z>D2a1;_P7zi_pJwvVkhJSsqh}&Do&m4x5&&O-YnuqVIVEeqq9VU@CrI_tqv7+7cRL!(@6Iq@!o<{%un*%a|Z`5?zZ ztc-AID^Ygi@kDCtLH04OTpBg3t?{OyL-tzzIXR%-=a)^&_rQYWV5DmH_8+SJW6J@& zolP?7JD+imMenAoc)2wYEKbHtv}+YBE@3XJV%3mzOm0Z;&=tnfC~JCQpDbJ$;OnlL!50g&3CvR_X>!*dW@E zuI6Ek1yzx0cAeuzK!b}M{`}CFp1g7D>Zv4ckjqvApg6sZ=7q9=FvrR#`Z%{UGniT? z7qTOI&SR$$i#jC}-UNwTCf&{qV-3@0UR3R8b|JoXpZWihWWSmsNO+t23EUkmq{{RT znaHfdjg0a4)MaMq;GDRZ=aMK%LlfuQ0i*66YJwnKCec!X=QU<%DXuZph%abjgqkG1 z>oBJ-?V-FhnG0~;_l$n2M%-L&m+GjhPqWdQnYb+IXE#OqyC+s;pSs)e@|}IN#jbz> z(DAH`-FmDM-$X48)}T95lS_~1DQ=BDKKVo(b{`Ht#=70d7UTu2=3tL2wOcGD%oxF4 z_sJvj#eAyu7hQjjq03%8LXR1zej=+YU*Q*-*v6N{cx_-XB6vDQ>Jq7Gi1y-8LzN3Z zC{_IlY+Xp7L)mCU#K~`1*eRQ*f>aGj9ts@i+i2#to9YE=Fj^=_<kWy}9Tjz>$& zzd%ur!-QF3Aa_iG12u)`n@BEUwJO2&8@><^f27c8Ak! z7vT;*om3`5Feo1FDRS9FjQr(qBB}PvAH5k9KwQ<8KJDD}N*FhDxZ-bDWSz-iktqYx zag72p0G%e)NE14|il^HwlAE?sBR@P}7W5lBRkHnLWHJHqO}(pO6oVYSUzVp!q82$(EWH zZM89*FftK=K=)!GtZ(32-9Btt_Ym{1*|{<;&gjlb z_sM2B43Vp@#MVXEd=y?QrH|h+V_gjau{hl? z!%$)iFXc`cTn?cHgWz6^;J9b)^n*FtSmG876yb#^#H=+~h)2SA(9g_#3U^^`!JeA5 z*;g~G;#h=HqD5aT!~UrNI*Gnfrg!_QD{=6xhnBSPQa+M#&D~QM+CB^i;(`)21tqmj z6yXd20I3B__|p(ppUkD!v5SVd=mSci-o9J}J+PQUY53c3P55D|!5~V*JTllOB8bu_pPxBMdv)i-@g-G49ql zSA|ltxZ6h17@SbQK%gt=Z;gXDO1uK*1-UYs^*wh@(>fKjb&Kr`V-5B5CnaoU`tY3B zFfGZH_GPBNtBtg7wp-HzPlo~6ogy2%G7awEa#$nR#CnRP&VTL7ybpGqH>dZ5Q5M>b zF$Gw`7I!uZ{pL5>ZK%@_RfFDznz$j8h)sBMF2fBuQDIWjAT7%FBWQy)CtA7c#@Pwj zZI{NB9^-&au3%J!zz4axIE^ZUeswX$(K9##4R3|k(=mQzphg_Xf-UMBHAj1SNkhy5 zu43LT)yHfbv9FhkST5ko7T?Gh#_GE5#LGIN<13pNj7N6&D7_kxsI z1@N#8?*JMd?8Vyl$+4PL8n67NjWA^)0rM>AXClBag|GktHh|mlj|9C@0Tmuok?lND z4@t5V39nEKa8qq5b!ymmnGtKTD#m;|RP2M}nW(H}K_`LqH5tY`&oqqLG`e35Y?Jhz zYmF82viOmou}+2{Nm-9q{2}1B)nh#19Ypask_zDJ&Aup~4Ct`m<*ZH~YsJW+7(9@A zuyAAf3RHEjUs-Lw-iZgKVit6XcUu9haYGO250$=adZpbf z@37-8SNT`-ZJluV`%GTkdUXtHH5Kp-MQcawCHqh(@Zn@9t;{AA9Ng%>Ec7n5i5)28 zY{454uu5Os*RTKr4T&mY14jC;w9WV%mW&$nu+$8j5OAPm3&ns&bA%t+W2xvVu*x#{ zH0Y6R`plPl^eIW0Rdu#HAmm+gR}kCa000001;lGmcnlhKe;F+Xg3U}^c12hnx+TBb zUN6TqEn7JdiNSNap@i5H@%<^v20n67+39njS(DfQ!=$OpLBlQ+rsk)WoL$=>&{n5;fBN&ttmzz#oXq3k? zZ<&VtCtc=^YwOYY z_KH!xvN_j8t!QIJmQLT}YsmgGsX$P&i$SgF0K^TLWK|$E|RC z@>4wW2_wMdXn|;JhY$%}Ukg`$*8~8MDAWdRp@p(<+; z;yUGTvDlG#R#wcNLN$FXX}UV#;odIirE?n0TcuXGeev^0(X{2Mj36-eDt+`Pbhgr? zcgs?{uOLOo<1N|Zv`nj$A39^YoJk#~Yh8yoMah!5uyfl7SHMiAP z&Poudo6?D5k>-?tua~w>h35@WJ#8_C9C7!x-0)d+dBaXmp0 zL~j}oXAvIHzhO{@Tch2Btigm!5W0G@DA%x1%af0W7UbGKBIHHlf@$lg)0~tcQ8%Er zj(sI%d<(2#mfAmW(wrvwQJSiAEQPNj7*Tt?+Rxa1&~^!GfM{NuOYL zwxvqstHuALYB!};lJa{L(89YTapdod*bX8yI&H757ClvNtDk^!^u zaXr`6L))!ElogQ78J`V372Pd?w)Cp@FXj8ju^Tpa?v?<8?Gs8}TOmI2;JygKL(j!% zd#X*y3tEld4iXGwd{BH+sS+{z&jS zn>a1ztpfd}vE$_s;}mbR^WzHcNlrKI6MR>wtVI6D6vUk&j{hZr(x|L^hG!8S?U=nm z+e>4ZaNdq!w(MIlsh698O1U#CQEVR93I00MV| zw`|&EEafxgh*AJdqE5_RP70>mz$OntE;1y|Afkk{eC_7s3^&6{XiR#h29R zOMI6NgKzL!`UUXAVL}Ap7eH%yhn-N#`H1otP_mG{<&sVmd5jr+=r+-gx)hR=3xvSR zvf6D&i25P(d5Cdcfe-7;9(yl+1Nb_Y z8tBX0pa1LzpPEPmb|MCt&+q#^-DU^~xPV3HM^BQ*!tw;YY zM9h=HQvGakDODjRMz3&4Yir66@B4rrV6+t&*~5HY0X&)-Gi_W%MMZ$b&(zhX&W8{< zkzFHl%!c@x_}@$+vU(2P0$xi8eX}lNcJIQYf)Q;tquu=TPaS~__!t7s)6T^0n74~- zlk)a>ONxz*VCC1u2s|bX2L8?}Z8e*Hl51$N;<8*BmqMujvk!W)m?5$fkF&yDRBGf1 zf$9p52t~Bok9eIQb*TT@{v2uizH5i3mHrL(Tj-qWZ{DS8sEKoZFoxIumq0?aT;Z|e zC~;DC!PqcbniFPWQ_quwpM!QH2AJ4fof8yL(OLZhv=moQFc^P-e$io^i{sXNaCu6w zxx-_`P~xOteB!ssArTMU9H=@ps}ba@Ce@Lxjut!|O<oAcJ$7#3gIt=?;?RRCPRkm%f4g9$N%Zw|?4fN4xpw51ySZ8%!PX zJT(^jhhIp3B?Mk{b!QLyKV|QreAI@S1tAu&F%kpZGJarYD&!3_p37!xcl5qeu@Z*d zHkdo$w*k7o)<&DtNLyFjgI^FJ-Jr(kX9M3i>Ry^x_eOAeT_!GggW1EzR1k9OSK}XW z1!ret+%wwGjKl~`7;2@z=6mS#ZllA+w6tw7cfUV|6Tu$mF|-;f>?Ji}BIMr8xg;te zX*4(}tk#M$#lvp-zBaYrH{%UpV+m-q)WlYQEQS;UK3ty$V2FSK5Y{N1hD=B(!S;Y#8Wx_Q(!)IWFBdrO zxDgw}l(Lhe$S_x3U@|Jw1lGXKEI|@1cy985&N~mR&!4R*mVpjmG)muki4!cE9wlh| zOUXel1w{bE7RUe^isg!kkzxlunf|KaR8R~taVB-8NIA~FLI|O}^nD@HKw;uM&-DuN z_}@#Fr4I2FOn{GIH&+HkKpJyY2kRegEDl;}1nuF+6x9(6Z=xkRm9a}Lh! z6~>qlPbgm8YeY1{1q5`US4%YL#@9WUBVSCOHTj&V)_kkK?1K~v;6$!9p?S)OZy)Qn6LJt?ZgZ!w$mSr>FD7s=gKy&88aRGE-B4pQSo8cf(uDJ-L!YcbT z`s=3#u;VcEO)ES&|IuzGBz>SK@|DEHV94r4t&#l}!D+IZN-F;L6#^QwlJ;!h5XX}?zpS~d=uuss}934R|_@S>k%=46&A!rlC#Z#x7tNQrDjx;Ea;eRS-&-A~3-+H+<(b z#u(kgme47{;V!L(@e0Pf>>()ve z`TnbD^HHn~|5Ex|Z0IX?G2vx${{dA|=kV3w zI=D2W`L`c~^D-eL1?flmvi{QYXpFRVZ^lpfp6TXFCpj7nlD?a+bp8IEs!r7CQaOEk z29_|KI4bQJ-@uY5hrJKsa{E3sf|cbTdBo}lTM+iP)=sVl8v50)Ta@8mRw8=j66?$! zh=@tkbcjCjVfB3^0%?Ce+gU0*9n|@zSQ;73!^IR-9n1_TMQk-#1r`q34z_5|O{#Yh zwpOS0=4+e?luV#UH(qh4Uc3rqwlw5x~-Wk_S`EcCba@WZ>z}lT}O{yUPyj_B!>nB z7ya1%V?>Mafu9@r0C+jXbc%x~b@Fjoo?4O#sX>McBlNGAlf^&9ec<1*5dgaXcz3rr zu5@fD5s+#7C!N&%OCowAI0)2(U`w}YI{!JjL5=uG*~F`Y$@4BBH0b&9o~H*F+$E zT2K@-Wvt=tAX9A~Ye1Et{2iQAzN2l9;Av48SZn5+OPCn|=Ikgi!9xaako?>dJ0L#Z zWP1cwtJf>3FdP!5xytHwyb4nSX(HE2mv(`$^4T`J!4sE(g&;x?*YXCm1tkD~O9&YGqmq?vlVmPumF^{4|?B4Lc~ zxX{pE?<&2wqCpsGSbHg3fE71Rfmi7e4!KYVR z^cd|ogW^(ov|rcAw7dXix?u8BKXJuDCUXj)Auo|WANfme^yze`L@luvYxfmj)TL|; z9wZKZ`i@upy0sPn0NDp&p~qtMi0ApTaTR~<>>VR7@)cs+&m;$zvU$!CsqGhFpe3gMp;GDzw1>lxkh~7t~}B>v`NYl;u}{6v9ngi+A+A zu5miF=agL$1^O%w_Zu(>*;+QAkdVfzTtlx}@pBDI8CDwtktCO%SK52#Ms=LW47P`v zV$=9`$a|az==MhWQjn!{Zr_ef4E#-xub_^fl>5K4UEQSveGq1cca0|0V7%d^e*LJW zd-aDb(|eDU(UuuHHZ)m46XW*L{2PV;0E1`T(j}L$)R$AUM#2cDVyKTpQqpoSm19}+ zl6%GHN`nd0}wFJ8;CU!{?JdEBQV zP0#I^(8}uOe#S>6>txigCalDVS-3Au9{oGza{FG%Ma!rS{}5mdlq?OPhAm9)5Vg`{ zZ5>tU-+JpGN^+3%qXxA&&b$cNLYr=e_c>OplkFm+za$`4I%5I#j!`CG40oltflBcE zugh57S=3mxD`=vzg2L7lqRv;Qh5g+7Ogogldrtw#ItD!{2K7|Im_8J*@PBI3!0sy7iULP`6Av8Sh86$ywZ`hfe zVx{wqOT=jVN{&Fw%-p0&(S8M9-k@$wjWvOrqR7tu^s)ZP9U}tf{DFkhFmyy zTIJB2@0*#c(2bL21}o@ToJfYXoR|+U_;jjr)U2PSc~$W+iG>H^LqOI{b3pPw6EDhW z{VIg6&{+Eq8K{~p&t`;6vm@Xu#X=jER0z6qAHdnJ1{#mxg@l+~>?IhZvHZX}^e#yg z8+01sdXi^P^TJoDZCr~F&{$iHJx5{OfK=N?XIJG30~6&(3$h9g;xoh(rG5>Y5)|xlk+O!2!&MtRR=CJnWF0}$QfTZr%r=+MXfmq2_D5A1$ ze(Q(7WDk-uyp&SC#}p{d+r5G63^0ipa@|eHj{MGBmCEi6cbKS4E2n*!QLg*#uqDk7 z<`IGgz|NcPwErQkhx*O`dQn>L?Nz12mY-dQcEE3!Jb5xSDvWt>D*ylcYjk0?TfsoS zSkR9~1ms*{n#5|SWS2C{pHcF2yPjAB?jpS}$c%a3dW1~KNS z=L3@E@B2Eq4erTKY8!$+r~r`8@NG!BYq;u9JnDq-ztlIDy?0Ni;MniJy?9-dR(JX+5sJ= zCDBhFaI%jEI`W#IB(W!2F^PXenw^>Fexs2$suqSE#%C%_&_;LHvItw$B=czwBc0Qa zpP{x@G7%(rlRLv>okWhWp$y7}H>FslMONoZclh3+#-MLalbB$ynjD|;o<&1gu*Hf* z!~!^tq$akxNk8$QUgY+POPjU4F$GO523ZP%;AED1>AtdCZ4>Vf|*;1Y+YxHh3O9#i90tV zVn@dnEel`ew#{e|17>z&Q3xdJ^eVrA%u-H*g@dSIU?O%P8yF{5YcR#{GNYjAuiziN z5W>pLnxUIRb9*jVa=^aC8ISClcFAXj*v04}zlrCNdPKqp1#)ko4>*wzh~E*QEM}}< z$K(kziL#Knw!(T^&(0Eh4WQ{y!GXooIMaXSXD{ratp&-Xh4~cOK^^HuEtZ4|k}Fmp z+UFSg;fKE*ls*7-1kgP}%&V`B4vdcVW~?=iswb@xVkxrI$TdSjRBFc#x5Oy#e;|Ng zUeWbjaSmU#4+WRnfEGcR)N0rx`I_bU{Ns`+BI!R`&^nGQbq5rw2>2tL!PW9fP9XxT z%@Be{efjpU+PbI07GaAdleQ#=mQ!;dAn%0(8yXrfECf#RYodrucQ=-fX8rZ-%)x)p z-=sC5wnAAJhu>Gj;M)!rBQp0R#Zx|mIJRB6^USbnDDj0s(>ccAU zMoEg4+l#dPGeP@c*4!v9wN2O@PLn10;v{hf>(-@R#9Ye*L!iIZ*_R>N5AD;w40?iE z4~1LlenX~W-@t--r#8!OOq-UWWe$qs)fqT84NSDfi{h@C5ON^o_;?T8+Z$aAfM2777!P|&L)L2O@}0LSn(*pSh?W?zVoC56Su z7}{fHQ^V*-B(u~PgNF5naju4Pj4+z!{tkvC|j0aMiTm+D{eE*{3)mh!-9VY+pQ zR5DVNy26xz`*1YW7Rtk`@`Qp&d>)OIKx`^Nf{GwnQ7AVQ$#qh;7y~7nAGdHmGENNB zeCsQeO>8nnee$dcnwn_xJ$6RfiBy10B}zhSE*A|12|`b6A7G5WI}qm#57 zYUne_DC5?S!8vP*g7%HfAQjJcznJ4Kq7+`r)KunSt1K>8lvxQiB4;Oc5LVs<^P>Ww zK=64W`SrbYoI+F1ps*s(miCJ!@A;*0{+?#*u&jQlVnnO2>vfPVsEc?-y$4K?!wD3q zv3mRDQnw&6@3#q+R)@vBxhxgY`Z3J<){g!y9#46R$^kOvzB!~p_+Nh&tofd!#ur6# zh%j!^rO$g<;U)9)jbRJnK0>MaH{@|y$natN8T z1wiH!VM(7uV)F*TY;ILI7oxbu5!~V(TGD$WYv_ILxn3R-nO=7@!xwpmm|%wDh6ELV zQ|)%;cz8x-DLIdjtQ&*~rQC`nylWz+;78IT#z| z0hc3Z*3KvCCy8zeeXq)r<4A3HuWC_B#3Ff;E-LLs(>_1cJHcuLaW9PH$x*IPUx9^B zGE7k$<*A6d`{;%!JCGeeplL?r&x5hc8v%~VB0Xz=Iq){uty_<9`9DOGX`%&*DSAPW zVD`C}K>$nEIEE>?BskTf!T$QE*WrPaR!cU!kR$GTC$L}s_ zmZ_K}m-uH&Nv1|gyUV<^1;3dvFhjo6k6eh7`i37qsFNfZfV^P}7$>opXiA?xEEpm# z^tgT2X`-?+h$B@IR|7#ERCkSZMe!6Jd?R*F0!ZAW@`U%$+N$fci*duRPDvF#*IEvX zdJMH}@~mjT<8bn=q99M)j5-&l+$`RkeXh(ZRv>4-QP7>-nF}Nt7BeOVmjPj(n2JgZ z2~h0-zP<-}rPtU?WIh<*4C_e2L*rZz2S5oHPptL7O#yT0Me&tpH1|_}qF|=~0W2n% zClBMo?1vbsEL6x^AR2zVvWbGBq3iHVsC`CSG;vOty}5Z-Jia?V;RBUsCkFR2 z#TquDrRJq|6*vQ6@QnMtm0Re~l&dE!%$l3j_r#XotJMv6p&}9{G*N@J+g70oi7i-{ zaCU8Wvkj8sKg5eqA&wU;(^3;!C_3?q7ZJL$OgH6&Jk(ml<QMeVWUU)IZ@QGh?anlnF!~qyCM> zM2s$1uS4k|dan<16w#yMAOPfeK~LiG$OkjZ0$zEgQLW37p_4mb3)?rmkc7kEZpibT zjz%jC3RvH3U!`@T^HZ7?ZR^aD_bjaMTQ6j=7lOL)ZE(b+SiN;49DW&ppz+Y>a9-on zp9`s4C8qYh{49&i@mVsWaMu-}4ibe5fntP|h55g#Pnly;NT^X_6ISdf00C)u_>@%& zZ!i=s=%P6t@o9I^ha)o3#B;RhgylYnGjGfJwaBE2wBIuw7W;mdkjbQv(bJ>iQ^;DPm(@WYFSYFn%+1azji z0K1fU-6HdP!$?mANDhbuJAv&})!QtBqb{)(O=mdmHzIbEhQkwJctY017xIrK@BV|6 zHcVyOnl*?=_Vvd>o7qjUqOdA=<@i~|O9x@W75#aC3~Ti=`MDMhqm+(Y5Z#gX!S6pJ z#9QU-DJj6sIC@tC4^de|e{^K}vZY(CY&<0+=$f+6LmERP8l}%V-)6s=Qno$n{sXOq z)fuix5)3Z?qDkdjb`b$CcO*=~O^3_6VphSqP8>0qpV)4hai4mI+8zK_K$lR$h_JQu+ ztuOf`GjXMOcah|i*^@CfkvI;>A~d*4zgkI)RQ@@^fCAtq9j-!jJ}wpDHQiCu(+Wev z_%WZs$`|J6w*Kdn+02jiO zx==ysBxWY(tG#qp`_LHxD)2e6xKbzC5O~ExQ~m&Y8l&wZt^s$OGcR zLk($+WE%5rF(LWfBaJ0x(99gpeDYee4+R0X(Tk&Uwj25Dv_lDOU>HyAH$6G_D3ic5 zd0Sf9LJv3`CgLcpI@}l!lH>9J&RY$bci^TM*+om!YY{xR#3*M2vqA`}16{h&r-WwokE`=Fz4-=>A+pLzEaK4*!#(Ka5 zCAGy5Xs_HiNMA}Y^Ox(j8H}PX|NLPiD zf~(8&*f~2b%;=Dld?Qul!jzPx*8V78N;Dw=pg-D{3qovc#5XOYI3iJvGA)BhmTL62 zYUX%5@tW8>gt7FHj?{aEs$Hwu-7T|0GhU`xE~=SjYX%cMT0$5azpAu_Qf92b*>DfE zv9}$&wwD@nU|i3bYqcGhJj8zAtMp2h;xdVH3l_8L6#fuK6f0Z_Gb?Jf>^7bdd}m;{ z)wDl$Fs=PqKW!*4cq9`7l@-^7M*CuO&-^tYG-i>Cdz!Pr=zF^=+}_S_Exyfh?D1=w-`Ypzhy(~y-?Ldxw_maYO2 ztsTLC4l*(3{K6u_Cz!eEQrBN+`E*!a$=BNFCCGR{5J7PBce8SrTfWX9^Z{CYW<~0T zY+3R*AS(xE7g#HoG~^Vy@dEaq$|=ei}{^n7F^tbfP{ z<>3%4vpD-o(>i<|@g?QDGVy}^S>wBaIW0uO^bN_D&#G-0X&yu%1b@rVp75O8+E`CF zQxpRx5|!+qQVU;Hn$(JgAiW(+v%p;_RhjKe52tOwhvvD2POOf4vVrX`d(8X2ozRUW z%kKV$d@&%@SHDfUggUk|&rA>7+LA6_W`rET!lLs9bT0+bVpFp@@BJ*ntJGPI{dv(D zJG1I!GO<0z&{*56BPfKXJ6AUFXOzF)Rws2wdAR0amzw^kCarq=MUZjYa>>gcV*D~{ zUyQ;jI9b1nrq88XPzlcto~d@NnF4=x8uny7Q#Xkr04^wJkPUU&EQf>Tf^^yB+KR(P z?Y;J#DoiAy4pPoenUjek6}S+aAGG8>paG~^#fm&UX3O`t(bMzp&>4`;lCR^X^7x2e!!;EHmEjHn@5@+~7T7l5uzjTj-#%umXgX7`w z))%c#MIYS`MKkpP8-wn<#*gN-Yo0pl$X>W=HDaYElN^m-rGGDcQsY8AGc6a;$z+S zf;SNtYSJp;W6KAOkevCE+mTuu^>|(BoOQSbypdEy60JJ2jZ}CBo-DJReV0}tmVSj~ zy6Pm&N-hcNg`$XY8)N6wx5UifcjQ(#@r+xi#5;Elw!#Yor%opuL>(7i2%I#GVp}+= z|F(dXl+bf6rtRn&q$9L_`H|p8!vI%SF1!*Vx+0|#biqrZy7KdZAbJM7SU%H>?mizW zXRJ-;I5Pp<)ol8=H`KOXskBZWRC$g~vhfQytU5?9Q^e>$;!SF^cO2TM^#}5;|Emb7 zJ@Dn)lHne?kAz0($v`YDaB01cR)^5!ScE4R2 z0tI<{!%SZkev5JFgx*sUQWmdJeSRu$-`^>Q}2LOsD8XAm~7fF>Clt2!sf}< zBtNG>5J1#yVMTAuI0V<^ZEt+)x+c#z)-a_MFF9lltqTaN$Jh2kinCTC(R8V@(Na zov}<$%vcH$@1Dq%lvwcrvX5vB1qdj zXym2=p(^o{8eN$``hsn;ZNFxCW>&&ZYtVXFW%N2?#c`6h7^6?!hvN^*JU;ckN)^4E zQ}^ml9>d%VvPs2qBDSV<#|L+&$FQjYDGqBWg~KNMON$Hy4Pnrz9~Mi`;&Oa=)J22+fE3t7kdq_H-WUD=;KODVE9 zMuis5WR1KsA(HIEQ+9@t<&F1x=YG!T_x;?@@7#O;fpacDW~~2eRfcuuVcMo*tXoMm zsV4IQ(VeF84kBrv(zP|P;%-aqMeNo0af=bm=X=`VrcA;-Sbx}C zmQGxnL+&~)2B!)tFTw+7dKZBe83Im|0cWMozVDUNdw50OO?i?M3Xl|evwl0qzsT;K zHB0BU*CJnqqx(YAK|>rvCPzj+e?StPx92mP&u2zRC<|SEjUV+mn!*Hr%PD$Ue*@$p zXteJ*Ojss~{JKBTg$b~DY@tpZv@vxgo^Jd&FY6NgMKi98@S3HN2G`Fe__N;A9eTE5 z8*`G>fRK1b+vhWpDWu#}xramRDJZx^>2L=My~eGI{dH@2c0eFu6nE(lUB1%EMKhmY zRZUxV7v(2r|2$R0vL5BVmHI+kq#Y7CeT3bN4K@`QcFiu2lfBKlWPh~weI=4Q(MRW! z_gR+=Zcx=Da_Db0$D`+}f;qnyZ_DzeMlOVI;Z>}`-cj5dO!Inhhn4ukBCKW8OVOja z#PyEN1v@+HIVoYn2sQ6$B)k(okv(vFK>(V@9JsZ(&luUbvVrS&NJzT z>~Q%S3Rg$(bMLj7wpLn{6jCEgmN4dcJ6RoO5q+6L=(sWv7Gg=>bet@dKo?7#up7yY{ zktO0mAdRe~jo*df717>BOpae#$?qF~Yor+ID~!(Q`HSKJZ9+|V{RyFVYdyYaNsR=uYVJe3hY`?8LJ2Vt$5POu^1z8aQINX@0#M-0ZwxU3`b}fVo zanexJw0~$C=ZnwZd|NC7-{ZQ00WL9G#geb~vKFH1W*^akhmLy(5t-aPo^O$==4KZn zPq|duz0$&9e#Z75j(kAjRp28xhi_psyAA51a4yc7YyVfBN6N= zPmSjmRKd^r+AYfnI&$c|O>AJnRg@=-tKP%RfTh+D^Ys#?Cc5v^-`z4G>vUP6g)!8K z)t752HGMF^p#qP3liG=m-0_Fh@qKXCgAIvsrUzHL*dN_;a0Rt9UEng{t>uN)`|*73 z7Uw<9B3$FwCz|{wz2e!!d#ym%i}I@9 z-G~)PaA2+P*I7V4+nExd$Vc-=A1;g~j8+ww{tzT4={4UY8sT)d3C2E!8V+FDP=~fg zOxZ3m%~+=vV5=U)>P|Wlj~*W9rUtS(FY^)*fdeoj8u)1;Zwtt`;Ix)O zF<7)5^2&WRXRIWb3eU%ov26+}s!PiIgI@!f%>oRr&RL02s5qO3*;>#B*6?t2l$|mx z@u4Nrljd-~2Id0^rP^MfT4iL&LSnV@h^K~&2(Q+spm)yeBT#4bDFx;-tM72^Mz`J> zNp!GyK3J#aNU1(?Z>Q%5QE4=Iuf0fxe+=-%9*-Tw=owK4dkai9u`&V!8^Q+G8ZgnC zf~?*V?$J`q5ULB!N8fk`6@^D?56bjIH7fgkfdV+gP}72zte}y6w4Z_-j5MW0fktA3 zcm0{eBYtt}6TnWinFb0oUCIOt3;lc{c5a<{nf8mPbZ*-S?WBu%;u8Cg4rPK3@Ygl= z*mqKu$Toi57_Seq{%_E{)zl>-o?5%BIFOPUfGMyLQvz+{r?-qIi_YAPAdALEm)xFo zx&EP5i@ZO~X|mSTbbe~mF}sabnflh^Yho3pO4OOd<0d4enxw+8j7Hmj=Ol<%vHxp3$5>oTNn zlveb5gHP1QQ&1A9tl?N-8-+{D88BrOGLdgTz7|H8xl;IqFy$37J%Ab^{pC5%*MumD4A8Hr8)?=CWfkAv;sbxi-C seyqkQv;|aEQAJr%O_fovtg41kRzavJLsire$E?Y4GnM`KqW|mu0YZnO3jhEB literal 67482 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 diff --git a/src/file_watcher/tests.rs b/src/file_watcher/tests.rs index e7740e8d..14247963 100644 --- a/src/file_watcher/tests.rs +++ b/src/file_watcher/tests.rs @@ -3,6 +3,8 @@ use std::path::{Path, PathBuf}; use std::sync::mpsc; use std::time::Duration; +use crate::test_utils::temp; + use super::{Callback, Watcher}; use tempfile::TempDir; @@ -62,11 +64,7 @@ impl Delays { fn init_test_env() -> (TestEnv, TempDir) { // Create our dummy test env - let temp_dir = tempfile::Builder::new() - .prefix("inlyne-tests-") - .tempdir() - .unwrap(); - let base = temp_dir.path(); + let (temp_dir, base) = temp::dir(); let main_file = base.join("main.md"); let rel_file = base.join("rel.md"); fs::write(&main_file, "# Main\n\n[rel](./rel.md)").unwrap(); diff --git a/src/history.rs b/src/history.rs index cd7d9a4a..8820d170 100644 --- a/src/history.rs +++ b/src/history.rs @@ -59,14 +59,11 @@ mod tests { use std::fs; use super::*; + use crate::test_utils::temp; #[test] fn sanity() { - let temp_dir = tempfile::Builder::new() - .prefix("inlyne-tests-") - .tempdir() - .unwrap(); - let temp_path = temp_dir.path().canonicalize().unwrap(); + let (_temp_dir, temp_path) = temp::dir(); let root = temp_path.join("a"); let fork1 = temp_path.join("b"); diff --git a/src/image/cache/global/db.rs b/src/image/cache/global/db.rs new file mode 100644 index 00000000..fc66dfa8 --- /dev/null +++ b/src/image/cache/global/db.rs @@ -0,0 +1,144 @@ +use std::{ + fs, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use crate::{ + image::cache::{global::RemoteMeta, RemoteKey, StableImage}, + utils, +}; + +use anyhow::Context; +use http_cache_semantics::CachePolicy; +use rusqlite::{types::FromSqlError, Connection, OptionalExtension}; + +use super::wrappers::{CachePolicyBytes, StableImageBytes, SystemTimeSecs}; + +/// The current version for our database file +/// +/// We're a cache so we don't really have to keep worrying about preserving data permanently. If we +/// want to make some really nasty changes without dealing with migrations then we can bump this +/// version and rotate to a totally new file entirely. Old versions are handled durring garbage +/// collection +const VERSION: u32 = 0; + +fn file_name() -> String { + format!("image-cache-v{VERSION}.db3") +} + +const SCHEMA: &str = include_str!("db_schema.sql"); + +// TODO: create a connection pool so that we can actually re-use connections (and their cache) +// instead of having to create a new one for each worker or serialize all cache interactions +pub struct Db(Connection); + +impl Db { + pub fn default_path() -> anyhow::Result { + let cache_dir = utils::inlyne_cache_dir().context("Failed to locate cache dir")?; + let db_path = cache_dir.join(file_name()); + Ok(db_path) + } + + pub fn open_or_create(path: &Path) -> anyhow::Result { + let db_dir = path.parent().with_context(|| { + format!( + "Unable to locate database directory from: {}", + path.display() + ) + })?; + fs::create_dir_all(db_dir) + .with_context(|| format!("Failed creating db directory at: {}", db_dir.display()))?; + let conn = Connection::open(path)?; + Self::create_schema(&conn)?; + Ok(Self(conn)) + } + + fn create_schema(conn: &Connection) -> anyhow::Result<()> { + conn.execute(SCHEMA, ())?; + Ok(()) + } + + pub fn get_meta(&self, remote: &RemoteKey) -> anyhow::Result> { + let mut stmt = self + .0 + .prepare_cached("select generation, last_used, policy from images where url = ?1")?; + stmt.query_row([&remote.0], |row| { + let generation = row.get(0)?; + let last_used = row.get::<_, SystemTimeSecs>(1)?.into(); + let policy = (&row.get::<_, CachePolicyBytes>(2)?) + .try_into() + .map_err(|err| FromSqlError::Other(Box::new(err)))?; + Ok(RemoteMeta { + generation, + last_used, + policy, + }) + }) + .optional() + .map_err(Into::into) + } + + pub fn get_data( + &self, + remote: &RemoteKey, + generation: u32, + ) -> anyhow::Result> { + let mut stmt = self + .0 + .prepare_cached("select image from images where url = ?1 and generation = ?2")?; + stmt.query_row((&remote.0, generation), |row| { + let blah = row + .get::<_, StableImageBytes>(0)? + .try_into() + .map_err(|err| FromSqlError::Other(Box::new(err)))?; + Ok(blah) + }) + .optional() + .map_err(Into::into) + } + + pub fn insert( + &mut self, + remote: &RemoteKey, + policy: &CachePolicy, + image: StableImage, + now: SystemTime, + ) -> anyhow::Result<()> { + let url = &remote.0; + let now: SystemTimeSecs = now.try_into()?; + let policy: CachePolicyBytes = policy.try_into()?; + let image: StableImageBytes = image.into(); + + let mut stmt = self.0.prepare_cached( + "insert or replace into images (url, last_used, policy, image, generation) + values (?1, ?2, ?3, ?4, abs(random() % 1000000))", + )?; + stmt.execute((url, now, policy, image))?; + Ok(()) + } + + pub fn refresh( + &self, + remote: &RemoteKey, + generation: u32, + policy: &CachePolicy, + ) -> anyhow::Result<()> { + todo!(); + } + + pub fn refresh_last_used( + &self, + remote: &RemoteKey, + generation: u32, + now: SystemTime, + ) -> anyhow::Result<()> { + let url = &remote.0; + let now: SystemTimeSecs = now.try_into()?; + self.0.execute( + "update images set last_used = ?1 where url = ?2 and generation = ?3", + (now, url, generation), + )?; + Ok(()) + } +} diff --git a/src/image/cache/global/db_schema.sql b/src/image/cache/global/db_schema.sql new file mode 100644 index 00000000..0dbae1ba --- /dev/null +++ b/src/image/cache/global/db_schema.sql @@ -0,0 +1,7 @@ +create table if not exists images ( + url text primary key, + generation int not null, + last_used int not null, + policy blob not null, + image blob not null +) diff --git a/src/image/cache/global/mod.rs b/src/image/cache/global/mod.rs new file mode 100644 index 00000000..1f358a3d --- /dev/null +++ b/src/image/cache/global/mod.rs @@ -0,0 +1,225 @@ +use std::{ + fmt, fs, + path::{Path, PathBuf}, + time::SystemTime, +}; + +use super::{RemoteKey, StableImage, StandardRequest}; +use crate::utils; + +use anyhow::Context; +use http::request; +use http_cache_semantics::{BeforeRequest, CachePolicy, RequestLike}; +use serde::{Deserialize, Serialize}; + +mod db; +pub mod wrappers; + +// The database is currently externally versioned meaning that we switch to an entirely new file +// when we bump the version +// TODO: Garbage collection should also be adjusted to cleanup unused databases over time +const VERSION: u32 = 0; + +pub fn db_name() -> String { + format!("image-cache-v{VERSION}.db3") +} + +fn db_path() -> anyhow::Result { + let cache_dir = utils::inlyne_cache_dir().context("Failed to locate cache dir")?; + let db_path = cache_dir.join(db_name()); + Ok(db_path) +} + +pub struct Stats { + pub path: PathBuf, + pub inner: Option, +} + +pub struct StatsInner { + pub size: Bytes, +} + +impl Stats { + pub fn detect() -> anyhow::Result { + let path = db_path()?; + path.try_into() + } +} + +impl TryFrom for Stats { + type Error = anyhow::Error; + + fn try_from(path: PathBuf) -> Result { + let inner = if !path.is_file() { + None + } else { + let meta = fs::metadata(&path)?; + let size = meta.len().into(); + let inner = StatsInner { size }; + Some(inner) + }; + + Ok(Self { path, inner }) + } +} + +impl fmt::Display for Stats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { path, inner } = self; + match inner { + None => write!(f, "path (not found): {}", path.display()), + Some(inner) => { + writeln!(f, "path: {}", path.display())?; + write!(f, "total size: {}", inner.size) + } + } + } +} + +pub struct Bytes(u64); + +impl From for Bytes { + fn from(bytes: u64) -> Self { + Self(bytes) + } +} + +impl fmt::Display for Bytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut unit = "B"; + let mut dividend = 1; + while self.0 / dividend / 1_024 > 1 { + unit = match unit { + "B" => "KiB", + "KiB" => "MiB", + _ => break, + }; + dividend *= 1_024; + } + + write!(f, "{} {}", self.0 / dividend, unit) + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct RemoteMeta { + // TODO: switch to a content hash or uuid v4 + /// A generation used to uniquely identify this cache entry + /// + /// We use generations to keep track of the consistency of a cache entry between different + /// tranactions. If we increment the generation every time we invalidate the entry in some way + /// (e.g. changing the stored image) then we're able to keep track of if we're still referring + /// to the same image in siturations like iniital validation/revalidation/etc. + pub generation: u32, + pub last_used: SystemTime, + pub policy: CachePolicy, +} + +pub fn run_garbage_collector() -> anyhow::Result<()> { + let cache = Cache::load()?; + cache.run_garbage_collector() +} + +pub struct Cache(db::Db); + +impl Cache { + pub fn load() -> anyhow::Result { + let db_path = db::Db::default_path()?; + Self::load_from_file(&db_path) + } + + pub fn load_from_file(path: &Path) -> anyhow::Result { + let db = db::Db::open_or_create(path)?; + Ok(Self(db)) + } + + // TODO: rename to remove `remote_` since it's always remote now + pub fn check_remote_cache( + &self, + key: &RemoteKey, + now: SystemTime, + ) -> anyhow::Result { + let check = self.check_remote_cache_inner(key, now)?.unwrap_or_else(|| { + let req: StandardRequest = key.into(); + let parts = (&req).into(); + CacheCont::Miss(parts).into() + }); + Ok(check) + } + + // TODO: rename to remove `remote_` since it's always remote now + fn check_remote_cache_inner( + &self, + key: &RemoteKey, + now: SystemTime, + ) -> anyhow::Result> { + let Some(meta) = self.0.get_meta(key)? else { + return Ok(None); + }; + let req: StandardRequest = key.into(); + + let maybe_meta = match meta.policy.before_request(&req, now) { + BeforeRequest::Fresh(_) => { + let gen = meta.generation; + match self.0.get_data(key, gen)? { + None => None, + Some(image) => { + self.0.refresh_last_used(key, gen, now)?; + Some(CacheCheck::Fresh((meta.policy, image.into()))) + } + } + } + BeforeRequest::Stale { request, .. } => { + // NOTE: We're using comparing the headers of the original and `request` + // requests as a proxy of `http-cache-semantics` trying to refresh our original + // data vs just sending the request through unchanged + if req.headers() == request.headers() { + // No change to our usual headers means this is a new request + Some(CacheCont::Miss(request).into()) + } else { + self.0 + .get_data(key, meta.generation)? + .map(|image| CacheCont::TryRefresh((meta.policy, request, image)).into()) + } + } + }; + + Ok(maybe_meta) + } + + pub fn insert( + &mut self, + key: &RemoteKey, + policy: &CachePolicy, + image: StableImage, + now: SystemTime, + ) -> anyhow::Result<()> { + self.0.insert(key, policy, image, now) + } + + pub fn run_garbage_collector(&self) -> anyhow::Result<()> { + // TODO: pass over and remove entries and then run compaction. Can get the size of various + // parts of the image data table to determine when we should actually run compaction + // (things generally run better when there are pages that can be reused instead of always + // compacting down to the minimal size) + todo!(); + } +} + +#[must_use] +pub enum CacheCheck { + Fresh((CachePolicy, StableImage)), + Cont(CacheCont), +} + +impl From for CacheCheck { + fn from(cont: CacheCont) -> Self { + Self::Cont(cont) + } +} + +#[must_use] +pub enum CacheCont { + TryRefresh((CachePolicy, request::Parts, StableImage)), + Miss(request::Parts), +} diff --git a/src/image/cache/global/wrappers.rs b/src/image/cache/global/wrappers.rs new file mode 100644 index 00000000..a455ca4f --- /dev/null +++ b/src/image/cache/global/wrappers.rs @@ -0,0 +1,194 @@ +use std::{ + array, fmt, + time::{Duration, SystemTime, SystemTimeError}, +}; + +use crate::image::{cache::StableImage, ImageData}; + +use http_cache_semantics::CachePolicy; +use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; + +pub struct CachePolicyBytes(Vec); + +impl From<&CachePolicy> for CachePolicyBytes { + fn from(policy: &CachePolicy) -> Self { + let bytes = bincode::serialize(policy).unwrap(); + Self(bytes) + } +} + +impl TryFrom<&CachePolicyBytes> for CachePolicy { + type Error = bincode::Error; + + fn try_from(bytes: &CachePolicyBytes) -> Result { + let policy = bincode::deserialize(&bytes.0)?; + Ok(policy) + } +} + +impl ToSql for CachePolicyBytes { + fn to_sql(&self) -> rusqlite::Result> { + self.0.to_sql() + } +} + +impl FromSql for CachePolicyBytes { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let blob = value.as_blob()?; + Ok(Self(blob.to_vec())) + } +} + +pub struct SystemTimeSecs(u64); + +impl TryFrom for SystemTimeSecs { + type Error = SystemTimeError; + + fn try_from(time: SystemTime) -> Result { + let since_unix_epoch = time.duration_since(SystemTime::UNIX_EPOCH)?; + Ok(Self(since_unix_epoch.as_secs())) + } +} + +impl From for SystemTime { + fn from(secs: SystemTimeSecs) -> Self { + SystemTime::UNIX_EPOCH + Duration::from_secs(secs.0) + } +} + +impl ToSql for SystemTimeSecs { + fn to_sql(&self) -> rusqlite::Result> { + self.0.to_sql() + } +} + +impl FromSql for SystemTimeSecs { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let secs = value.as_i64()?; + let secs: u64 = secs.try_into().map_err(|_| FromSqlError::InvalidType)?; + Ok(Self(secs)) + } +} + +/// The representation of how a [`StableImage`] is stored in the DB +/// +/// The image gets stored as a blob of bytes with a variable-size footer. The footer consists of a +/// byte that indicates the kind of underlying [`StableImage`]. The size of footer depends on the +/// kind of the underlying image +/// +/// The reason that we use a footer instead of a header is because it's easier to avoid needlessly +/// copying around the bulky image data if that's what we root the blob around +pub struct StableImageBytes(Vec); + +impl StableImageBytes { + const COMPRESSED_SVG_KIND: u8 = 0; + const PRE_DECODED_KIND: u8 = 1; + // 1 (scale bool) + 8 (2 u32s for dimensions) + const PRE_DECODED_FOOTER_LEN: usize = 9; + + pub fn len(&self) -> usize { + self.0.len() + } +} + +#[derive(Clone, Debug)] +pub enum StableImageConvertError { + MissingKind, + InvalidKind(u8), + MissingPreDecodedFooter, + InvalidPreDecodedScale(u8), +} + +impl fmt::Display for StableImageConvertError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MissingKind => f.write_str("Missing stable image kind"), + Self::InvalidKind(kind) => write!(f, "Invalid stable image kind: {kind}"), + Self::MissingPreDecodedFooter => f.write_str("Missing pre-decoded image footer"), + Self::InvalidPreDecodedScale(scale) => write!(f, "Invalid pre-decoded scale: {scale}"), + } + } +} + +impl std::error::Error for StableImageConvertError {} + +impl TryFrom for StableImage { + type Error = StableImageConvertError; + + fn try_from(bytes: StableImageBytes) -> Result { + let mut bytes = bytes.0; + let kind = bytes.pop().ok_or(StableImageConvertError::MissingKind)?; + match kind { + StableImageBytes::COMPRESSED_SVG_KIND => Ok(Self::CompressedSvg(bytes)), + StableImageBytes::PRE_DECODED_KIND => { + let footer_start = bytes + .len() + .checked_sub(StableImageBytes::PRE_DECODED_FOOTER_LEN) + .ok_or(StableImageConvertError::MissingPreDecodedFooter)?; + let (dim_x, dim_y, scale) = { + let mut footer = bytes.drain(footer_start..); + let scale = match footer.next().expect("Length pre-checked") { + 0 => false, + 1 => true, + unknown => { + return Err(StableImageConvertError::InvalidPreDecodedScale(unknown)); + } + }; + let dim_x = array::from_fn(|_| footer.next().expect("Length pre-checked")); + let dim_y = array::from_fn(|_| footer.next().expect("Length pre-checked")); + let dim_x = u32::from_be_bytes(dim_x); + let dim_y = u32::from_be_bytes(dim_y); + (dim_x, dim_y, scale) + }; + let image_data = ImageData { + lz4_blob: bytes.into(), + scale, + dimensions: (dim_x, dim_y), + }; + Ok(Self::PreDecoded(image_data)) + } + unknown => Err(StableImageConvertError::InvalidKind(unknown)), + } + } +} + +impl From for StableImageBytes { + fn from(data: StableImage) -> Self { + match data { + StableImage::PreDecoded(ImageData { + lz4_blob, + scale, + dimensions: (dim_x, dim_y), + }) => { + let mut bytes = lz4_blob.to_vec(); + bytes.reserve_exact(Self::PRE_DECODED_FOOTER_LEN + 1); + bytes.push(scale.into()); + bytes.extend_from_slice(&dim_x.to_be_bytes()); + bytes.extend_from_slice(&dim_y.to_be_bytes()); + bytes.push(Self::PRE_DECODED_KIND); + Self(bytes) + } + StableImage::CompressedSvg(mut bytes) => { + bytes.reserve_exact(1); + bytes.push(Self::COMPRESSED_SVG_KIND); + Self(bytes) + } + } + } +} + +impl ToSql for StableImageBytes { + fn to_sql(&self) -> rusqlite::Result> { + self.0.to_sql() + } +} + +impl FromSql for StableImageBytes { + fn column_result(value: ValueRef<'_>) -> FromSqlResult { + let blob = value.as_blob()?; + Ok(Self(blob.to_vec())) + } +} + +// TODO: roundtrip prop-test some of ^^. Could try fuzzing with `divan` too since we shouldn't have +// to split out a separate library using that diff --git a/src/image/cache/mod.rs b/src/image/cache/mod.rs new file mode 100644 index 00000000..0ed06783 --- /dev/null +++ b/src/image/cache/mod.rs @@ -0,0 +1,552 @@ +//! Contains our image caching logic +//! +//! The current cache is a 2-layered cache consisting of a volatile per-session cache along with a +//! persistent per-user cache +//! +//! # Image source +//! +//! `inlyne` can load images from either local files stored on the user's computer, or from images +//! requested from remote URLs +//! +//! ## Local Images (from files) +//! +//! Local images are handled exclusively by the per-session cache since there's no point in taking +//! space from remote images which are much more important in terms of caching +//! +//! Validity is determined by storing and comparing the local file's last modified time where an +//! entry is valid if the last modified time is an exact match +//! +//! ## Remote Images (from URLs) +//! +//! Remote images are stored in all layers of the cache +//! +//! Validity is determined according to the rules codified in the `http-cache-semantics` crate +//! which depends on both the request and response headers. Our actions are determined by the +//! response from the `.before_request()` and `.after_response()` hooks +//! +//! # Cache Layers +//! +//! Like typical layered caches entries are retrieved by going down the layers, pulling the entries +//! up through all of the levels when updating +//! +//! ## L1 - Volatile Per-Session Cache +//! +//! The per-session cache provides 2 key functions: +//! +//! 1. A fast lookup to avoid reaching out to the global database on every request +//! - Reloading the page should not re-pull all of the images from the database +//! - The slowest aspects of checking this cache are either waiting for writers on the +//! `RwLock`s and stating the local file to get its last modified time +//! 2. The ability to make cheap copies of image data +//! - The bulk of the data is stored in `Arc<_>`s which are cheap to copy +//! +//! ## L2 - Persistent Per-User Cache +//! +//! The persistent per-user cache functions as a typical private HTTP cache. This affords most of +//! the typical benefits of an HTTP cache e.g. avoiding making requests on fresh content, avoiding +//! re-transferring bodies on matching E-Tags, etc. +//! +//! # Garbage Collection +//! +//! Entries are evicted based on both a global size limit and a global time-to-live (TTL). +//! Constraining along both of these allows for the cache to behave well for both very active and +//! inactive users. Active users can sit at the cache size limit assuming they look at enough +//! images often enough to fully saturate the cache to the size limit. Inactive users can have a +//! smaller cache as only the entries that are within the global TTL will be retained + +use std::{ + fmt, + io::{self, Read}, + path::PathBuf, + sync::Arc, + time::{Instant, SystemTime}, +}; + +use crate::{ + image::{ImageBuffer, ImageData}, + HistTag, +}; + +use http_cache_semantics::{AfterResponse, CachePolicy, RequestLike}; +use lz4_flex::frame::{FrameDecoder, FrameEncoder}; +use metrics::histogram; +use resvg::{tiny_skia, usvg}; +use serde::{Deserialize, Serialize}; +use url::Url; + +mod global; +// TODO: this shouldn't be pub +pub mod request; +mod session; +#[cfg(test)] +mod tests; + +pub use global::{ + run_garbage_collector as run_global_garbage_collector, Stats as GlobalStats, + StatsInner as GlobalStatsInner, +}; +use request::StandardRequest; + +// TODO: spawn a cache worker when creating the cache and return a handle that can communicate with +// it? Each request can be pushed to a thread-pool that shares the cache? + +const MAX_CACHE_SIZE_BYTES: u64 = 256 * 1_024 * 1_024; + +fn load_image(bytes: &[u8]) -> anyhow::Result { + let image = if let Ok(image) = ImageData::load(&bytes, true) { + image.into() + } else { + // TODO: how to verify that this is an svg? + let svg = std::str::from_utf8(bytes)?; + StableImage::from_svg(&svg) + }; + Ok(image) +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Key { + Remote(RemoteKey), + Local(PathBuf), +} + +// Internally stores a URL, but we keep it as a string to simplify DB storage and comparisons +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +pub struct RemoteKey(String); + +impl RemoteKey { + pub fn new_unchecked>(s: I) -> Self { + Self(s.into()) + } + + pub fn get(&self) -> &str { + &self.0 + } +} + +impl From for Key { + fn from(key: RemoteKey) -> Self { + Self::Remote(key) + } +} + +impl From<&RemoteKey> for Key { + fn from(key: &RemoteKey) -> Self { + key.to_owned().into() + } +} + +impl From for RemoteKey { + fn from(url: Url) -> Self { + Self(url.to_string()) + } +} + +impl From for ureq::Request { + fn from(key: RemoteKey) -> Self { + let req: StandardRequest = (&key).into(); + (&req).into() + } +} + +impl Key { + fn from_abs_path(path: PathBuf) -> Option { + if path.is_absolute() { + Some(Self::Local(path)) + } else { + None + } + } + + fn from_url(url: &str) -> anyhow::Result { + let url = Url::parse(url)?; + Ok(url.into()) + } +} + +impl From for Key { + fn from(url: Url) -> Self { + if url.scheme() == "file" { + let path = url.to_file_path().unwrap(); + Self::from_abs_path(path).expect("URLs are _always_ absolute paths") + } else { + Self::Remote(url.into()) + } + } +} + +impl From<&Key> for Key { + fn from(key_ref: &Key) -> Self { + key_ref.to_owned() + } +} + +#[derive(Clone, Debug)] +pub enum StableImage { + /// Pre-baked image data ready to be served + PreDecoded(ImageData), + /// Compressed SVG text + /// + /// SVGs get stored as the original text and rendered on demand instead of being pre-rendered + /// because the rendering for the same SVG can change depending on different dpi or font info. + /// This will likely be smaller anyways + CompressedSvg(Vec), +} + +impl StableImage { + pub fn from_svg(svg: &str) -> Self { + let mut input = io::Cursor::new(svg.as_bytes()); + // TODO: upstream a helper function that does this + let mut compressor = FrameEncoder::new(Vec::new()); + io::copy(&mut input, &mut compressor).expect("in-memory I/O failed"); + let output = compressor.finish().unwrap(); + Self::CompressedSvg(output) + } + + pub fn render(self, ctx: &SvgContext) -> ImageResult { + match self { + Self::PreDecoded(data) => Ok(data), + Self::CompressedSvg(compressed) => { + let mut svg_bytes = Vec::with_capacity(compressed.len()); + let mut decompressor = FrameDecoder::new(io::Cursor::new(compressed)); + decompressor + .read_to_end(&mut svg_bytes) + .map_err(|_| ImageError::SvgDecompressionError)?; + + let opt = usvg::Options::default(); + // TODO: loading the fontdb on every single SVG render is gonna be slow + let mut fontdb = usvg::fontdb::Database::new(); + fontdb.load_system_fonts(); + let mut tree = usvg::Tree::from_data(&svg_bytes, &opt)?; + // TODO: need to check and see if someone can pass a negative dpi and see what kind + // of issues it can cause + tree.size = tree.size.scale_to( + tiny_skia::Size::from_wh( + tree.size.width() * ctx.dpi, + tree.size.height() * ctx.dpi, + ) + .ok_or(ImageError::SvgInvalidDimensions)?, + ); + tree.postprocess(Default::default(), &fontdb); + let mut pixmap = + tiny_skia::Pixmap::new(tree.size.width() as u32, tree.size.height() as u32) + .ok_or(ImageError::SvgInvalidDimensions)?; + resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut()); + let image_buffer = + ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.data().into()) + .ok_or(ImageError::SvgContainerTooSmall)?; + Ok(ImageData::new(image_buffer, false)) + } + } + } +} + +impl From for StableImage { + fn from(data: ImageData) -> Self { + Self::PreDecoded(data) + } +} + +pub trait TimeSource: 'static { + fn now(&self) -> SystemTime; +} + +struct SystemTimeSource; + +impl TimeSource for SystemTimeSource { + fn now(&self) -> SystemTime { + SystemTime::now() + } +} + +// TODO: ban typical way of constructing to force usage of vv +/// Our custom `CacheOptions` (could be `const`) +fn cache_options() -> http_cache_semantics::CacheOptions { + // TODO: PR upstream for `const fn new() -> CacheOptions` + http_cache_semantics::CacheOptions { + // Our cache is per-user aka private + shared: false, + ..Default::default() + } +} + +pub struct Shared { + per_session: session::Cache, + time: Box, + svg_ctx: SvgContext, +} + +#[derive(Clone)] +pub struct SvgContext { + dpi: f32, +} + +impl Default for SvgContext { + fn default() -> Self { + Self { dpi: 1.0 } + } +} + +// TODO: restructure how a lot of this is done. Allow for checking the l1 cache without touching a +// db connection, and allow for either a pool of actual workers or an `Arc>` for +// a shareable in-memory db +#[derive(Clone)] +pub struct LayeredCache(Arc); + +impl LayeredCache { + pub fn new(svg_ctx: SvgContext) -> anyhow::Result { + Ok(Self::init(SystemTimeSource, svg_ctx)) + } + + #[cfg(test)] + pub fn new_with_time(time: T, svg_ctx: SvgContext) -> anyhow::Result + where + T: TimeSource, + { + Ok(Self::init(time, svg_ctx)) + } + + fn init