From 7407a83d6f6f0e9b650634382feb58df2874338b Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Sun, 3 Jan 2021 01:56:52 +0300 Subject: [PATCH 1/4] edited lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index aa6a053..1867c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,7 +52,7 @@ dependencies = [ [[package]] name = "rsblocks" -version = "0.1.1" +version = "0.1.2" dependencies = [ "chrono", "yaml-rust", From 7eafb04737b4c6920e400addf6e4d0a1526c224b Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Mon, 4 Jan 2021 02:29:01 +0300 Subject: [PATCH 2/4] rsblocks now provides the current weather temerature --- Cargo.lock | 7 +++ Cargo.toml | 5 ++- README.md | 16 +++++-- rsblocks.yml | 27 ++++++++++-- src/lib.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 155 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1867c5d..6a74754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,6 +31,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +[[package]] +name = "minreq" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466d0a7e6bfcd54f69e4a17d4a4318985aaaf7fe3df4cd3b6f11ff551129ca3" + [[package]] name = "num-integer" version = "0.1.44" @@ -55,6 +61,7 @@ name = "rsblocks" version = "0.1.2" dependencies = [ "chrono", + "minreq", "yaml-rust", ] diff --git a/Cargo.toml b/Cargo.toml index c74fe6b..36889bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rsblocks" -version = "0.1.2" +version = "0.1.3" authors = ["mustafa salih "] edition = "2018" readme = "README.md" @@ -12,4 +12,5 @@ description = "a multi threaded status bar for dwm window manager for linux" [dependencies] chrono = "0.4" -yaml-rust = "0.4" \ No newline at end of file +yaml-rust = "0.4" +minreq = "2.2.1" \ No newline at end of file diff --git a/README.md b/README.md index 4077e8c..3b33e2f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # rsblocks -A minimal multi threaded fast status bar for dwm window manager written in **Rust** 🦀 +[github](https://github.com/MustafaSalih1993/rsblocks) +[crates](https://crates.io/crates/rsblocks) +[build](https://github.com/MustafaSalih1993/rsblocks/actions?query=workflow%3ARust) + +A multi threaded fast status bar for dwm window manager written in **Rust** 🦀


@@ -7,9 +11,10 @@ A minimal multi threaded fast status bar for dwm window manager written in **Rus ## Features * Multi Threads * Time/Date -* Used Memory -* Used Disk space -* Sound volume _reads from `alsa-utils` for now_ +* Memory Usage +* Disk Usage +* Weather Temperature +* Sound volume _reads from `alsa-utils`_ * Easy to configure with `rsblocks.yml` file @@ -58,5 +63,8 @@ cp ./rsblocks.yml ~/.config/rsblocks/rsblocks.yml ## Contributions All Contributions are welcome. +## Credits +* [wttr.in](https://github.com/chubin/wttr.in) for using their weather API + ## License [MIT](./LICENSE) diff --git a/rsblocks.yml b/rsblocks.yml index be6939f..76e83f6 100644 --- a/rsblocks.yml +++ b/rsblocks.yml @@ -1,28 +1,47 @@ -# This is the full configuration template available for rsblocks tool -# the names are clearly defines itself. +# This is the full configuration template available for rsblocks. -# NOTE: the (delay) is **seconds** with the float point to update the item in the bar. +# NOTE: the (delay) is in **SECONDS** and the float point "." is required. general: seperator: '┃' + time: icon: '' format: '%d %b, %I:%M:%S %p' delay: 1.0 + memory: enable: true icon: '▦' delay: 2.0 + disk: enable: true icon: '' delay: 120.0 -# reads from alsa-utils + +# reads from alsa, alsa-utils package should +# be installed on the system to make it work. volume: enable: false icon: '' delay: 0.18 + + +# weather format options is available on +# https://github.com/chubin/wttr.in +# NOTES: +# - if the city field set to empty, it will try to read location automatically. +# - if you have multiple formats and want to insert space between them use '+' instead. +# - giving (city) a value is recommended. +weather: + enable: true + icon: '' + city: '' + format: '%l:+%t' + delay: 7200.0 # 7200 seconds = 2 hours + diff --git a/src/lib.rs b/src/lib.rs index 786d4cf..edb30f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use chrono::prelude::*; +use minreq; use std::env; use std::fs::File; use std::io::Error; @@ -15,6 +16,7 @@ pub enum ThreadsData { Disk(String), Memory(String), Time(String), + Weather(String), } #[derive(Clone)] @@ -24,6 +26,7 @@ pub struct Config { pub memory: Memory, pub disk: Disk, pub volume: Volume, + pub weather: Weather, } #[derive(Clone)] @@ -53,6 +56,24 @@ pub struct Volume { pub delay: f64, } +#[derive(Clone)] +pub struct Weather { + pub city: String, + pub format: String, + pub icon: String, + pub enabled: bool, + pub delay: f64, +} + +/* +// TODO: error handling required if rsblocks.yml file is empty + +this function is responsible to check if the rsblocks.yml file +exists to call parse_config to read it OTHERWISE +it will call load_defaults to load a hardcoded default configuration + +it will always return a valid configuration inside a Result. +*/ pub fn load_config() -> Result { let yml_source = env::var("HOME").unwrap() + "/.config/rsblocks/rsblocks.yml"; let mut data = String::new(); @@ -64,12 +85,14 @@ pub fn load_config() -> Result { } }; file.read_to_string(&mut data)?; - let yml_content = &YamlLoader::load_from_str(&data).unwrap()[0]; let config = parse_config(yml_content); Ok(config) } +/* +this is simply returns a hardcoded configuration as default +*/ fn load_defaults() -> Config { Config { seperator: String::from("|"), @@ -93,28 +116,47 @@ fn load_defaults() -> Config { enabled: false, delay: 0.17, }, + weather: Weather { + city: String::from(""), + format: String::from("+%t"), + icon: String::from(""), + enabled: false, + delay: 7200.0, //7200 seconds = 2 hours + }, } } +/* +it will read and parse the rsblocks.yml file content and return a valid configuration +IF some content is missing in the rsblocks.yml file, it will set +a default values to that +*/ fn parse_config(doc: &yaml::Yaml) -> Config { // parsing icons and set default if not exist in the config file let seperator = get_or_set_string(doc, "general", "seperator", "|"); let time_icon = get_or_set_string(doc, "time", "icon", ""); - let time_format = get_or_set_string(doc, "time", "format", "%T"); let mem_icon = get_or_set_string(doc, "memory", "icon", ""); let disk_icon = get_or_set_string(doc, "disk", "icon", ""); let volume_icon = get_or_set_string(doc, "volume", "icon", ""); + let weather_icon = get_or_set_string(doc, "weather", "icon", ""); + + //parsing formats and city weather + let time_format = get_or_set_string(doc, "time", "format", "%T"); + let weather_format = get_or_set_string(doc, "weather", "format", "%l:+%t"); + let weather_city = get_or_set_string(doc, "weather", "city", ""); // parsing enabled state (everything false by default) let disk_enabled = get_or_set_bool(doc, "disk", "enable"); let memory_enabled = get_or_set_bool(doc, "memory", "enable"); let volume_enabled = get_or_set_bool(doc, "volume", "enable"); + let weather_enabled = get_or_set_bool(doc, "weather", "enable"); // parsing update_delay state (should be all seconds in f64 type) let time_delay = get_or_set_f32(doc, "time", "delay", 1.0); - let disk_delay = get_or_set_f32(doc, "disk", "delay", 60.0); + let disk_delay = get_or_set_f32(doc, "disk", "delay", 120.0); let memory_delay = get_or_set_f32(doc, "memory", "delay", 2.0); let volume_delay = get_or_set_f32(doc, "volume", "delay", 0.17); + let weather_delay = get_or_set_f32(doc, "weather", "delay", 7200.0); Config { seperator, @@ -138,6 +180,13 @@ fn parse_config(doc: &yaml::Yaml) -> Config { enabled: volume_enabled, delay: volume_delay, }, + weather: Weather { + city: weather_city, + format: weather_format, + icon: weather_icon, + enabled: weather_enabled, + delay: weather_delay, + }, } } @@ -222,6 +271,19 @@ pub fn run(config: Config) { }); } + // Weather thread + if config.weather.enabled { + let weather_tx = tx.clone(); + let configcp = config.clone(); + let weather_data = get_weather(&configcp); + let mut weather_data = ThreadsData::Weather(weather_data); + thread::spawn(move || loop { + weather_tx.send(weather_data).unwrap(); + weather_data = ThreadsData::Weather(get_weather(&configcp)); + thread::sleep(Duration::from_secs_f64(configcp.weather.delay)) + }); + } + // Time thread { let time_tx = tx; @@ -238,14 +300,15 @@ pub fn run(config: Config) { { // NOTE: order matters to the final format - let mut bar: Vec = vec!["".to_string(); 4]; + let mut bar: Vec = vec!["".to_string(); 5]; //iterating the values recieved from the threads for data in rx { match data { ThreadsData::Sound(x) => bar[0] = x, - ThreadsData::Disk(x) => bar[1] = x, - ThreadsData::Memory(x) => bar[2] = x, - ThreadsData::Time(x) => bar[3] = x, + ThreadsData::Weather(x) => bar[1] = x, + ThreadsData::Disk(x) => bar[2] = x, + ThreadsData::Memory(x) => bar[3] = x, + ThreadsData::Time(x) => bar[4] = x, } // match ends here @@ -254,6 +317,9 @@ pub fn run(config: Config) { } } +/* +this will call the command 'xsetroot -name ' +*/ pub fn update(bar: &Vec) { // TODO: FIX ME, this solution sucks let mut x = String::new(); @@ -276,6 +342,7 @@ pub fn update(bar: &Vec) { ############################# HELPER FUNCTIONS BELOW ################################### */ + pub fn get_time(config: &Config) -> String { let now = Local::now(); @@ -287,6 +354,30 @@ pub fn get_time(config: &Config) -> String { ) } +/* +CREDIT: thanks for wttr.in to use their API +will make a GET request from wttr.in +*/ +fn get_weather(config: &Config) -> String { + let format = if config.weather.format.is_empty() { + String::from("%l:+%t") + } else { + config.weather.format.clone() + }; + + let url = format!("http://wttr.in/{}?format=\"{}", config.weather.city, format); + let err_string = String::from("Error"); + let res = match minreq::get(url).send() { + Ok(resp) => match resp.as_str() { + Ok(res_str) => res_str.trim_matches('"').to_string(), + Err(_) => err_string, + }, + Err(_) => err_string, + }; + + format!(" {} {} {}", config.weather.icon, res, config.seperator) +} + pub fn get_disk(config: &Config) -> String { let cmd = Command::new("sh") .arg("-c") @@ -308,7 +399,7 @@ pub fn get_disk(config: &Config) -> String { ) } -// TODO: what a horrible solution to get the sound, i dont like it +// FIX ME: what a horrible solution to get the sound, i dont like it // find another way pub fn get_volume(config: &Config) -> String { @@ -331,6 +422,11 @@ pub fn get_volume(config: &Config) -> String { format!(" {} {} {}", config.volume.icon, vol, config.seperator) } +/* +mem_used = (mem_total + shmem - mem_free - mem_buffers - mem_cached - mem_srecl +thanks for htop's developer on stackoverflow for providing this algorithm to +calculate used memory. +*/ pub fn get_memory(config: &Config) -> Result { let mut buf = String::new(); @@ -392,7 +488,12 @@ pub fn get_memory(config: &Config) -> Result { } Ok(result) } -// helper function for the get_memory function + +/* +this helper function will split the line(first argument) by the character(:) +and then parse the right splited item as u32 +then assign that to the "assignable"(2nd argument). +*/ fn assign_val(line: &str, assignable: &mut u32) { let parsed: u32 = line.split(':').collect::>()[1] .trim() From 8d4666b64eb22b132f7cb05da57aed5c115575b4 Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Mon, 4 Jan 2021 02:30:43 +0300 Subject: [PATCH 3/4] cargo.lock updated --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6a74754..64b6a19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ dependencies = [ [[package]] name = "rsblocks" -version = "0.1.2" +version = "0.1.3" dependencies = [ "chrono", "minreq", From 90f6944da4cff411fa28755339b242775fbb4d7d Mon Sep 17 00:00:00 2001 From: mustafa salih Date: Mon, 4 Jan 2021 02:43:17 +0300 Subject: [PATCH 4/4] updated screenshot --- screenshots/1.png | Bin 6093 -> 6878 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/screenshots/1.png b/screenshots/1.png index 67bff904e53f53ed079826f9a8db651be48e4f27..495c216cfa2ba8e9079fea93022a7559fe8bb3a5 100644 GIT binary patch literal 6878 zcmV<48X@J0P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+O1b@k}SCi{I65^2m*va$3b5azJZVLq`Ify%-glS z8{w|$s%n)AK_(=ca{l$tTmFF$nby+OypP^1K5ewYfg7){&v?#M+jD;Xpvv#jALE7O z{Ssp+@87>I-Y*8m_16nuYtZ~TUi|%p{cVUohM$k*%6Y%PJBe|AjZ=FY8s~M>hue2o z=iAVFPkdZ?f7{2c*Cl_K9|H!IwqhY+K7y#9v%fr${E`H&uXDq^91Cf}1s4)}8v`Ie z!uz_``W>JjAb;JJ-_ieo@EiCX?-}wNmi>(pZvONE>2KrmS)aczEFX{f=Yw${Qh$c? z{@%O&-t&qsS_h*2oly_iUkK&I3CQJnt>Z9!75DZW1BchB3zYA=>cp{V5QCLlsL`gN zOXs;RT5z%PVZ)Vqfv6mFF&?}g##$b9*<{ET2XBi{5lQkszR zmx-zLE4Sp~Sujs=eU%Uu+iwGa2x}{xL8dpVokXcqHD)6Mjt+tXo-=A=f&o(H4JWyY zbS}ouxi;Q0dgiin=NyJMfe4i@25yD~$ZAcIKh_*M6e`+OwX3NY9U5JF>)Ly_o+1|} z)=aINncJ{%@#gB?&E1E`;33dJ9evpFF-8QJz(N6Af%$=v$x~*VI{UQgbIe#spVgMG zzHIp#D>iT0kcr*5ZQom)UryV{0*zq$?T;$r7+pgYz?fM-zKCHc2{loD$Sfe*< z{4kaC?86#Hi`kb6N;%Qw42*FjFmA~J5}K2<>eL!1bIDm9jG|D6lbYO=P01K2Y=>Ac zeYpF@++X6YsQ)G2_k+(l#ZD*fb4`Z(?OiZhw`mX2(*xOp4@bBK_ zrbCx%S6!D)@R1;HcoAV+-P~IE zq;`B&+78o0rVKP7i+-wI#^dI`$=cq<2#6VE4I;X)j{}P+$2)8K=$B_+Z=5c7Y?ZZeOb0%5=H3IjP}NTuFJ(p`(h)Ayy`O{TUs}Md1bkSXLn#_K0S$Y#X$pwE zb;_mo?HI*w2Tu(8+$Ry3W7~3LOX&kyhRuUqx3i4f5?cy9=S&U6?xlmIb`jc`j5>(i znq824`p#-$+QZH*5MZ>+E}0CfvE&~7fijpwjob+gXT!=8)z7|d+34&@X!J%6xEFzi zqWIoWm&uGeH3sl9+R4nRgTh)d{SF>s)t2dl4>ML5^KLSOkg&;Y4iFQDUO#R?^^J z_jCm>Oh?gSPXq@2@Jwm-83)8+@3asaL}fxr+K7V)Sd2T`B!PCmQY6(M7SI5a8HoItxrtto3djc3RZ$V;7-Oq{5LHJl z8H6z#1bWg9MHyf_(=IdO?RW)^azP9r9`MS#3eekt4fab?KQGorPnide2^uJ-Kxs5a z=b_0Eqb0zW;s6sp^PrbkI#8q(VN~VRThMM$qscRNq!JnTMTvM~h7KbQjRRfhZg^4t z4J&G9U^t-b$^ZZXg=s@WP)S2WAaHVTW@&6?004NLeUUv#!%!53Pg6yaih~7395S@x zU_n&GQL9jd3ZYhL)xqSYf6$~MNpW!$Tni5VELI&{oON|@6$HT_5H~j`MHeaYzNFA1 z#tV=4ao#Kn>;IhCo!)7`) zPaGx|3vH~lF)NxH@f2}H)pW`iG9Ig(w>WF%Dr?@8zc7^3SC+X>YZwVEVhIvND5#=@ zGHk?X*GaLEqVu?qf5`Pqtx8^xcAAmH?DtQAO90DT+%3k+*cX#{T{_Sbb?+0)+a;8)MXIcON010qN zS#tmY3ljhU3ljkVnw%H_000McNlirui*OMKsKjX+n?cpD|zwJ*wVPFQ7mF`Lz_(Chn#cSif~fFKG>eBW#h zuNV{s6i`3`1!Tl~5i=l&VrpisP__aJye?STI9u5`!`BoF)!R3SLZK>#MG-UJEZ}fB z_+pxy|FTtIkRfPbqXG&j@XFxf9pK>|0AK6r8~X=@>FFCQhUIMpQARrL(R$N8X2(V! zMqunCY@F@3tsjWRvhLS? zy!i(sV~fA9M%_#PYe?en51c+58UB>{ctmx!eDzj4`zeDO>uSpn@B3W#hrAkA0RSp0 zYR*%NC>&xAY~A)#YI2-pat_&r!{J@sW=)>zqpqPX z5(+sSR$5A2=KVXvlw#A?12{ZETU)QWxnBAwji$1D+iDqy{okb`9`ETLILXzA!O##1 zMRyWn6XP$9kO*PRHabr7fUm=Uy(Veju<84*&W@A6?y0RQM_A0_@&0p{+SyN0rqUYf zDr2LMw=_4%q8b<Mu(Jvs($2X{^s^rMQFXbOIM(Y@Sv~f3Gg@F+>`g(g3ZeHYYSp9rHT}3T0Xr-_J zqO*~Q)9>BN%1D-wt>3t_y}eaO*TB%o?8t$w5JdTE`_V^PNkXBBN>g!~U@OXm&R0(2QBZvcwDkz$-{&E}T7f z`N9bRz^ploRaMkt&K&~)h=jtI&%<8Ds${z79AzpkH6?ED{1tGEzJV#342AFiV&1|J zO~+f8J3{(MFt@Px^qCKPNi>+4 zS}_=!x!I|*cTM-0?K?Z<`sK)~il_bk{bVxK-Nlv57#`lYWz*IJ;Rm)N{*-apUu1QT zyZ7AlXMU-xDHHbhi~g~Qkq|)=Fr-1Ft6Eq)BE}5Q84D&lPCNJKPhFiHH_y2nKHIZ< z+bTHBW2k0?fXED*$CGikU_URj%)YVps#p1rc-p zkzmhgtU0HxmBV4fzs6lV4FE7UF_#eZ^mH=RwNx1zT3R}=OPV@;R!Y*}IgjM&1r~;^ zl}uV`%=8Z}$a~n-h?>ew^YG8SpUCI)OiZmjede9~?S~<|rn&p2Cf^u-Vu6@yXzLgt z{-mp@A>JZA>;qx%=$d}-W^uuz!BUTa2*<*=Vf41KkxyGWO>$4TaRGKImtu~8fAEai zI6F8k#!$@&0gV|%k0;|q!VnNqeTa@!Mt=p7mD<>?<>f_|Rt~8taR2~aJtHziDPcZw zLXYVGC1wQ7UoPD^d)`t`J1hFkFW9M^xs`o->Md!JYN|>*IBeZ9MtH*WtrP-*AQA~p z$Jq$@{F?u7?F1JR87j=n?Ca~5C1yO#y36PB z%1R6ECb%Tsz9K2?91Y**;5^;Nb~2|GmUq`loevo zxVrgWz7Qc6i>13%85-8M&U<%l003xcjfN;nLv|4ecy$f!it?h7@L6aX21cGSWaZ%2 zU*`ukj*ti&HhquL8-F7v;YN(qVg|1@N~fl#OyVx<>ncWRk9o>`jG-DdU>G2}70blT zkRA_>TUnlo?U!IjDx<$5k;wu9pIMM&Y5hEASXw!j6hGxOrEU#?R3vNt5(xn^xU z37f`u_F@v5k|niTnJ{tcliUmdKx0F-shKrhg`ulwL?T1HzCK_7kXtvRXZVGz+3?-` z;MK~?v|-LveqS%2FM!CD&MpoBfW>Nhkde6g%fmDMLc*49T>a5@%~3jZ^mQn#Z+$~E zW?>EhfLWMr`(hIJ;o7gc9qq@D?u?I%o;7(PmaW=aQC__J+f_g8SSJ<< z1LiLS06faPYhgW6a>~Wt!S!)=8V-j$_|ruLL(~7p%;TK&Qz!OjWhB7`3OMG8vMki%M0+;c(sEodYXiVaqnYt~>(+WHLl1 zL!)&KwtRhf|B(yd?K$D>;x(co)Yg+SwGx9;v%rqQugg9Z$< z!mwglm_hV-L?TII8fe_2J#*l_7?3zp8T}QR3<>zW;^G{Wan^8K^XSzUcp)b9tTO!M`d6L#rB#Bio*YD(P0%%q)rBkv|%&(|JP%6u~2-Cr^% z#+q7uyYn}pus{63)`q%D#3$HJn9OQz?CI{NP^gs^%=y8q8ETrGb{5=wHTER4C>H=w z!pu`%LZeWqu(9!ZeSQI9L?Y?0tAE0j%}w>s!x*1eURuCVA0>IkUzZ~Xt}HMphmS$; zl4eyyQ(Ij_E9v%SSaEeV<@$!E0D$`1iq_UfTl>imA0&|>$imv;Sok)PNSK?Q)-|Bm zJw|`*s`cLh0F;&KR<_Q95MEa&_t4(W;B{clhV4|EiY%1(@7#djsx!VH?nlb8kuw`k9lHY>!nti+MZ=&wj*NFd;`S{gez>~ZFH z6=g*RM&qMS?x!fz003l&(%0J~pP0d7HPPt|9Ka3CUs95;S5=}`kjChbUAkg(^x30u zdE|xIXzx;2ThZS3eBb7AcG~0YGyuRT?J+ufM)5ad7KX0N&&%ZVcu%s^SFHU?9t8>u z2%>~7|7`!BPuxA{nn-=3K_C$Q1Hz(yKfvel5EDSHQM!8%pZixUc9Y@E4geq)J+EmD zDsFtR>T?=RCF1DregPkD30?AuwXG8XKqMAHkW&7WOf-gNr<4l@u?jocZ+9IBVllws z`g(idopt~~cGg{cN7siBlB{f;Iqj_4nlb>up!#G(T_s!x3t6(E;7JyoW66T9&JH-z z$Wb)3fN}jwWNmd>@S=}SNB;Q5H%DkRghbQY)`>(USCkdJR2RH4qpUO{-yFy{&k*qW z0DzP`*W1`F0DuSgZ_f`|t)V%psj+T^MDX+pgu{dM1tNNSy2WAz?17FdALeexmE6%)9b+DE<8c zHmiB8sfA=mapYj{;A&!GrKYasJ#!%%4TsYv5b%He<;uXu-TR}qf4Ll^ODODTvzqmd zOh;l%L%w-Nf4_jo>r+uxgBRcsq>RTCL}KyCiBMZpR$f}@=@Te@fk-bFi&(8qhDN51 z4b^Z3RZnkhQvBuKz8QB^k@ zJO13CKMlIe*~P1@w4kl66#&3)Y0}X(WESP>>KRK8gbfFeGLs)=CIbLMmwt*tHr{dq z!iv(yxr5UtQ2+oS|4HUdzhF9DwX?GWYaEEAjP!)DW>#U#H=c^vi^t<|ID#regGeG< z**IC*I#r`&X=s#qJbppw+Jv}ht_0t^p4kuYIXb&Zw;LFm0mJ9Pqq~dCL`sHgs!Fzh^{)?d zb@Q7ramvx~ZP;A^fZRv-JiG%N>Z{t>tz%6sy=N|v*Prc^OP)a>;1@1hpAZ+r;jo_; z=X!Vtwzsi(yxvKZz5054*sYBtC&J6trT_qsvQm6}gIk&#xLl5h*Id4US5}%Q6pAp$ zU$RA~rmkgZG%oZ0Z49$Q1ERShGK1#v01(5bfjwIgpNU~sFwh(+w!b2gApu_i04Ogl z5(xNKHjcmkyaNCrcpft#`Ns@6Y^0~$z#tf-Z>*zhprdPGY2zem-1*H)c+qNS@6u3L z#b!0VP(Yy49i7}Fj_rhheUOp3bj4LE3HU$t`>ojk)_(f=UNV`|(%caF`+j)IIT9jtcX2UlQ)nxLq=ZY#%G9uBo0O^4 z`nsxPN4}Hs6u!H6fUkdOb7OthgJcY|LXQ~;=7z|Oz9F*5>+a&BnZ}R;5{V@2dl4P! zkp7BHCJO{S0DwTi+rQ_Nf2))NJ^+ABez`mY_m=)G^zRRzp;GC;{k)^}Y2J{;BogG~ zyTHiU9RK3E2(eiF>{(4hTnwDIzOh*U>8?MH{|~n6*98Irzvb&A@qeGs&&zzfwEwnG zW9xa?ae30~dC-$3ZZg27wCB6@03k1BIf1iJQ z`LC>CzTO$Eq(q&&;KPm%_L~qe-Zxx0cl531zox20j^ACq59aoU<8A%_GnA?HXZ2O3 zPxGYD#=U#M{x Ye{nk07*qoM6N<$f}PApxBvhE literal 6093 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 T3k0US00000NkvXXu0mjfvU$e$