⭐
Log in to mark this repository.
calendar: subscribe to external iCal calendars
2c2327e · Gabriel Morell · 2026-10-19 16:35
Message
{commit_body(@commit)}
Files changed
modified
Cargo.lock
+273
−27
@@ -17,6 +17,12 @@ version = "1.0.2"
| 17 | 17 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 18 | 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" |
| 19 | 19 | |
| 20 | +[[package]] | |
| 21 | +name = "adler2" | |
| 22 | +version = "2.0.1" | |
| 23 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 24 | +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" | |
| 25 | + | |
| 20 | 26 | [[package]] |
| 21 | 27 | name = "ahash" |
| 22 | 28 | version = "0.7.7" |
@@ -200,7 +206,7 @@ dependencies = [
| 200 | 206 | "cc", |
| 201 | 207 | "cfg-if", |
| 202 | 208 | "libc", |
| 203 | − "miniz_oxide", | |
| 209 | + "miniz_oxide 0.7.1", | |
| 204 | 210 | "object", |
| 205 | 211 | "rustc-demangle", |
| 206 | 212 | ] |
@@ -211,6 +217,12 @@ version = "0.22.1"
| 211 | 217 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 212 | 218 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" |
| 213 | 219 | |
| 220 | +[[package]] | |
| 221 | +name = "base64" | |
| 222 | +version = "0.23.1" | |
| 223 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 224 | +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" | |
| 225 | + | |
| 214 | 226 | [[package]] |
| 215 | 227 | name = "base64ct" |
| 216 | 228 | version = "1.6.0" |
@@ -351,7 +363,7 @@ dependencies = [
| 351 | 363 | "instant", |
| 352 | 364 | "lazy_static", |
| 353 | 365 | "once_cell", |
| 354 | − "thiserror", | |
| 366 | + "thiserror 1.0.56", | |
| 355 | 367 | "tokio", |
| 356 | 368 | ] |
| 357 | 369 |
@@ -400,10 +412,11 @@ dependencies = [
| 400 | 412 | |
| 401 | 413 | [[package]] |
| 402 | 414 | name = "cc" |
| 403 | −version = "1.2.4" | |
| 415 | +version = "1.9.0" | |
| 404 | 416 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 405 | −checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" | |
| 417 | +checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df" | |
| 406 | 418 | dependencies = [ |
| 419 | + "find-msvc-tools", | |
| 407 | 420 | "shlex", |
| 408 | 421 | ] |
| 409 | 422 |
@@ -434,6 +447,16 @@ dependencies = [
| 434 | 447 | "windows-targets 0.52.6", |
| 435 | 448 | ] |
| 436 | 449 | |
| 450 | +[[package]] | |
| 451 | +name = "chrono-tz" | |
| 452 | +version = "0.10.4" | |
| 453 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 454 | +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" | |
| 455 | +dependencies = [ | |
| 456 | + "chrono", | |
| 457 | + "phf", | |
| 458 | +] | |
| 459 | + | |
| 437 | 460 | [[package]] |
| 438 | 461 | name = "clap" |
| 439 | 462 | version = "4.4.18" |
@@ -585,6 +608,15 @@ version = "2.4.0"
| 585 | 608 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 586 | 609 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" |
| 587 | 610 | |
| 611 | +[[package]] | |
| 612 | +name = "crc32fast" | |
| 613 | +version = "1.5.2" | |
| 614 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 615 | +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" | |
| 616 | +dependencies = [ | |
| 617 | + "cfg-if", | |
| 618 | +] | |
| 619 | + | |
| 588 | 620 | [[package]] |
| 589 | 621 | name = "crossbeam-queue" |
| 590 | 622 | version = "0.3.12" |
@@ -900,6 +932,23 @@ version = "2.3.0"
| 900 | 932 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 901 | 933 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" |
| 902 | 934 | |
| 935 | +[[package]] | |
| 936 | +name = "find-msvc-tools" | |
| 937 | +version = "0.1.15" | |
| 938 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 939 | +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" | |
| 940 | + | |
| 941 | +[[package]] | |
| 942 | +name = "flate2" | |
| 943 | +version = "1.1.10" | |
| 944 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 945 | +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" | |
| 946 | +dependencies = [ | |
| 947 | + "crc32fast", | |
| 948 | + "miniz_oxide 0.9.1", | |
| 949 | + "zlib-rs", | |
| 950 | +] | |
| 951 | + | |
| 903 | 952 | [[package]] |
| 904 | 953 | name = "flume" |
| 905 | 954 | version = "0.11.1" |
@@ -1153,6 +1202,22 @@ dependencies = [
| 1153 | 1202 | "windows-sys 0.59.0", |
| 1154 | 1203 | ] |
| 1155 | 1204 | |
| 1205 | +[[package]] | |
| 1206 | +name = "http" | |
| 1207 | +version = "1.5.0" | |
| 1208 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1209 | +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" | |
| 1210 | +dependencies = [ | |
| 1211 | + "bytes", | |
| 1212 | + "itoa", | |
| 1213 | +] | |
| 1214 | + | |
| 1215 | +[[package]] | |
| 1216 | +name = "httparse" | |
| 1217 | +version = "1.10.1" | |
| 1218 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1219 | +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | |
| 1220 | + | |
| 1156 | 1221 | [[package]] |
| 1157 | 1222 | name = "humantime" |
| 1158 | 1223 | version = "2.1.0" |
@@ -1579,9 +1644,9 @@ dependencies = [
| 1579 | 1644 | |
| 1580 | 1645 | [[package]] |
| 1581 | 1646 | name = "log" |
| 1582 | −version = "0.4.22" | |
| 1647 | +version = "0.4.34" | |
| 1583 | 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1584 | −checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" | |
| 1649 | +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" | |
| 1585 | 1650 | |
| 1586 | 1651 | [[package]] |
| 1587 | 1652 | name = "logos" |
@@ -1603,7 +1668,7 @@ dependencies = [
| 1603 | 1668 | "lazy_static", |
| 1604 | 1669 | "proc-macro2", |
| 1605 | 1670 | "quote", |
| 1606 | − "regex-syntax 0.8.2", | |
| 1671 | + "regex-syntax 0.8.11", | |
| 1607 | 1672 | "syn 2.0.90", |
| 1608 | 1673 | ] |
| 1609 | 1674 |
@@ -1680,6 +1745,16 @@ dependencies = [
| 1680 | 1745 | "adler", |
| 1681 | 1746 | ] |
| 1682 | 1747 | |
| 1748 | +[[package]] | |
| 1749 | +name = "miniz_oxide" | |
| 1750 | +version = "0.9.1" | |
| 1751 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1752 | +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" | |
| 1753 | +dependencies = [ | |
| 1754 | + "adler2", | |
| 1755 | + "simd-adler32", | |
| 1756 | +] | |
| 1757 | + | |
| 1683 | 1758 | [[package]] |
| 1684 | 1759 | name = "mio" |
| 1685 | 1760 | version = "1.0.3" |
@@ -1934,6 +2009,24 @@ version = "2.3.1"
| 1934 | 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 1935 | 2010 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" |
| 1936 | 2011 | |
| 2012 | +[[package]] | |
| 2013 | +name = "phf" | |
| 2014 | +version = "0.12.1" | |
| 2015 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2016 | +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" | |
| 2017 | +dependencies = [ | |
| 2018 | + "phf_shared", | |
| 2019 | +] | |
| 2020 | + | |
| 2021 | +[[package]] | |
| 2022 | +name = "phf_shared" | |
| 2023 | +version = "0.12.1" | |
| 2024 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2025 | +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" | |
| 2026 | +dependencies = [ | |
| 2027 | + "siphasher", | |
| 2028 | +] | |
| 2029 | + | |
| 1937 | 2030 | [[package]] |
| 1938 | 2031 | name = "pin-project-lite" |
| 1939 | 2032 | version = "0.2.13" |
@@ -1996,6 +2089,7 @@ version = "0.1.0"
| 1996 | 2089 | dependencies = [ |
| 1997 | 2090 | "async-trait", |
| 1998 | 2091 | "chrono", |
| 2092 | + "chrono-tz", | |
| 1999 | 2093 | "clap", |
| 2000 | 2094 | "color-eyre", |
| 2001 | 2095 | "colored", |
@@ -2011,6 +2105,7 @@ dependencies = [
| 2011 | 2105 | "lazy_static", |
| 2012 | 2106 | "log", |
| 2013 | 2107 | "ratatui", |
| 2108 | + "rrule", | |
| 2014 | 2109 | "sea-orm", |
| 2015 | 2110 | "sea-orm-migration", |
| 2016 | 2111 | "serde_json", |
@@ -2020,6 +2115,7 @@ dependencies = [
| 2020 | 2115 | "tracing-error", |
| 2021 | 2116 | "tracing-subscriber", |
| 2022 | 2117 | "tui-input", |
| 2118 | + "ureq", | |
| 2023 | 2119 | "wealthy", |
| 2024 | 2120 | "xdg", |
| 2025 | 2121 | ] |
@@ -2209,19 +2305,19 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
| 2209 | 2305 | dependencies = [ |
| 2210 | 2306 | "getrandom", |
| 2211 | 2307 | "libredox", |
| 2212 | − "thiserror", | |
| 2308 | + "thiserror 1.0.56", | |
| 2213 | 2309 | ] |
| 2214 | 2310 | |
| 2215 | 2311 | [[package]] |
| 2216 | 2312 | name = "regex" |
| 2217 | −version = "1.10.3" | |
| 2313 | +version = "1.13.1" | |
| 2218 | 2314 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 2219 | −checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" | |
| 2315 | +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" | |
| 2220 | 2316 | dependencies = [ |
| 2221 | 2317 | "aho-corasick", |
| 2222 | 2318 | "memchr", |
| 2223 | − "regex-automata 0.4.4", | |
| 2224 | − "regex-syntax 0.8.2", | |
| 2319 | + "regex-automata 0.4.18", | |
| 2320 | + "regex-syntax 0.8.11", | |
| 2225 | 2321 | ] |
| 2226 | 2322 | |
| 2227 | 2323 | [[package]] |
@@ -2235,13 +2331,13 @@ dependencies = [
| 2235 | 2331 | |
| 2236 | 2332 | [[package]] |
| 2237 | 2333 | name = "regex-automata" |
| 2238 | −version = "0.4.4" | |
| 2334 | +version = "0.4.18" | |
| 2239 | 2335 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 2240 | −checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" | |
| 2336 | +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" | |
| 2241 | 2337 | dependencies = [ |
| 2242 | 2338 | "aho-corasick", |
| 2243 | 2339 | "memchr", |
| 2244 | − "regex-syntax 0.8.2", | |
| 2340 | + "regex-syntax 0.8.11", | |
| 2245 | 2341 | ] |
| 2246 | 2342 | |
| 2247 | 2343 | [[package]] |
@@ -2252,9 +2348,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
| 2252 | 2348 | |
| 2253 | 2349 | [[package]] |
| 2254 | 2350 | name = "regex-syntax" |
| 2255 | −version = "0.8.2" | |
| 2351 | +version = "0.8.11" | |
| 2256 | 2352 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 2257 | −checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" | |
| 2353 | +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" | |
| 2258 | 2354 | |
| 2259 | 2355 | [[package]] |
| 2260 | 2356 | name = "rend" |
@@ -2265,6 +2361,20 @@ dependencies = [
| 2265 | 2361 | "bytecheck", |
| 2266 | 2362 | ] |
| 2267 | 2363 | |
| 2364 | +[[package]] | |
| 2365 | +name = "ring" | |
| 2366 | +version = "0.17.14" | |
| 2367 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2368 | +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | |
| 2369 | +dependencies = [ | |
| 2370 | + "cc", | |
| 2371 | + "cfg-if", | |
| 2372 | + "getrandom", | |
| 2373 | + "libc", | |
| 2374 | + "untrusted", | |
| 2375 | + "windows-sys 0.52.0", | |
| 2376 | +] | |
| 2377 | + | |
| 2268 | 2378 | [[package]] |
| 2269 | 2379 | name = "rkyv" |
| 2270 | 2380 | version = "0.7.45" |
@@ -2294,6 +2404,19 @@ dependencies = [
| 2294 | 2404 | "syn 1.0.109", |
| 2295 | 2405 | ] |
| 2296 | 2406 | |
| 2407 | +[[package]] | |
| 2408 | +name = "rrule" | |
| 2409 | +version = "0.14.0" | |
| 2410 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2411 | +checksum = "720acfb4980b9d8a6a430f6d7a11933e701ebbeba5eee39cc9d8c5f932aaff74" | |
| 2412 | +dependencies = [ | |
| 2413 | + "chrono", | |
| 2414 | + "chrono-tz", | |
| 2415 | + "log", | |
| 2416 | + "regex", | |
| 2417 | + "thiserror 2.0.21", | |
| 2418 | +] | |
| 2419 | + | |
| 2297 | 2420 | [[package]] |
| 2298 | 2421 | name = "rsa" |
| 2299 | 2422 | version = "0.9.7" |
@@ -2349,6 +2472,41 @@ dependencies = [
| 2349 | 2472 | "windows-sys 0.59.0", |
| 2350 | 2473 | ] |
| 2351 | 2474 | |
| 2475 | +[[package]] | |
| 2476 | +name = "rustls" | |
| 2477 | +version = "0.23.46" | |
| 2478 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2479 | +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" | |
| 2480 | +dependencies = [ | |
| 2481 | + "log", | |
| 2482 | + "once_cell", | |
| 2483 | + "ring", | |
| 2484 | + "rustls-pki-types", | |
| 2485 | + "rustls-webpki", | |
| 2486 | + "subtle", | |
| 2487 | + "zeroize", | |
| 2488 | +] | |
| 2489 | + | |
| 2490 | +[[package]] | |
| 2491 | +name = "rustls-pki-types" | |
| 2492 | +version = "1.15.1" | |
| 2493 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2494 | +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" | |
| 2495 | +dependencies = [ | |
| 2496 | + "zeroize", | |
| 2497 | +] | |
| 2498 | + | |
| 2499 | +[[package]] | |
| 2500 | +name = "rustls-webpki" | |
| 2501 | +version = "0.103.15" | |
| 2502 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2503 | +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" | |
| 2504 | +dependencies = [ | |
| 2505 | + "ring", | |
| 2506 | + "rustls-pki-types", | |
| 2507 | + "untrusted", | |
| 2508 | +] | |
| 2509 | + | |
| 2352 | 2510 | [[package]] |
| 2353 | 2511 | name = "rustversion" |
| 2354 | 2512 | version = "1.0.14" |
@@ -2401,7 +2559,7 @@ dependencies = [
| 2401 | 2559 | "serde_json", |
| 2402 | 2560 | "sqlx", |
| 2403 | 2561 | "strum", |
| 2404 | − "thiserror", | |
| 2562 | + "thiserror 1.0.56", | |
| 2405 | 2563 | "time", |
| 2406 | 2564 | "tracing", |
| 2407 | 2565 | "url", |
@@ -2500,7 +2658,7 @@ dependencies = [
| 2500 | 2658 | "proc-macro2", |
| 2501 | 2659 | "quote", |
| 2502 | 2660 | "syn 2.0.90", |
| 2503 | − "thiserror", | |
| 2661 | + "thiserror 1.0.56", | |
| 2504 | 2662 | ] |
| 2505 | 2663 | |
| 2506 | 2664 | [[package]] |
@@ -2626,9 +2784,9 @@ dependencies = [
| 2626 | 2784 | |
| 2627 | 2785 | [[package]] |
| 2628 | 2786 | name = "shlex" |
| 2629 | −version = "1.3.0" | |
| 2787 | +version = "2.0.1" | |
| 2630 | 2788 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 2631 | −checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" | |
| 2789 | +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | |
| 2632 | 2790 | |
| 2633 | 2791 | [[package]] |
| 2634 | 2792 | name = "signal-hook" |
@@ -2670,12 +2828,24 @@ dependencies = [
| 2670 | 2828 | "rand_core", |
| 2671 | 2829 | ] |
| 2672 | 2830 | |
| 2831 | +[[package]] | |
| 2832 | +name = "simd-adler32" | |
| 2833 | +version = "0.3.10" | |
| 2834 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2835 | +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" | |
| 2836 | + | |
| 2673 | 2837 | [[package]] |
| 2674 | 2838 | name = "simdutf8" |
| 2675 | 2839 | version = "0.1.5" |
| 2676 | 2840 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 2677 | 2841 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" |
| 2678 | 2842 | |
| 2843 | +[[package]] | |
| 2844 | +name = "siphasher" | |
| 2845 | +version = "1.0.4" | |
| 2846 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2847 | +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" | |
| 2848 | + | |
| 2679 | 2849 | [[package]] |
| 2680 | 2850 | name = "slab" |
| 2681 | 2851 | version = "0.4.9" |
@@ -2781,7 +2951,7 @@ dependencies = [
| 2781 | 2951 | "sha2", |
| 2782 | 2952 | "smallvec", |
| 2783 | 2953 | "sqlformat", |
| 2784 | − "thiserror", | |
| 2954 | + "thiserror 1.0.56", | |
| 2785 | 2955 | "time", |
| 2786 | 2956 | "tokio", |
| 2787 | 2957 | "tokio-stream", |
@@ -2836,7 +3006,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
| 2836 | 3006 | checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" |
| 2837 | 3007 | dependencies = [ |
| 2838 | 3008 | "atoi", |
| 2839 | − "base64", | |
| 3009 | + "base64 0.22.1", | |
| 2840 | 3010 | "bigdecimal", |
| 2841 | 3011 | "bitflags 2.4.2", |
| 2842 | 3012 | "byteorder", |
@@ -2869,7 +3039,7 @@ dependencies = [
| 2869 | 3039 | "smallvec", |
| 2870 | 3040 | "sqlx-core", |
| 2871 | 3041 | "stringprep", |
| 2872 | − "thiserror", | |
| 3042 | + "thiserror 1.0.56", | |
| 2873 | 3043 | "time", |
| 2874 | 3044 | "tracing", |
| 2875 | 3045 | "uuid", |
@@ -2883,7 +3053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
| 2883 | 3053 | checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" |
| 2884 | 3054 | dependencies = [ |
| 2885 | 3055 | "atoi", |
| 2886 | − "base64", | |
| 3056 | + "base64 0.22.1", | |
| 2887 | 3057 | "bigdecimal", |
| 2888 | 3058 | "bitflags 2.4.2", |
| 2889 | 3059 | "byteorder", |
@@ -2913,7 +3083,7 @@ dependencies = [
| 2913 | 3083 | "smallvec", |
| 2914 | 3084 | "sqlx-core", |
| 2915 | 3085 | "stringprep", |
| 2916 | − "thiserror", | |
| 3086 | + "thiserror 1.0.56", | |
| 2917 | 3087 | "time", |
| 2918 | 3088 | "tracing", |
| 2919 | 3089 | "uuid", |
@@ -3089,7 +3259,16 @@ version = "1.0.56"
| 3089 | 3259 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3090 | 3260 | checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" |
| 3091 | 3261 | dependencies = [ |
| 3092 | − "thiserror-impl", | |
| 3262 | + "thiserror-impl 1.0.56", | |
| 3263 | +] | |
| 3264 | + | |
| 3265 | +[[package]] | |
| 3266 | +name = "thiserror" | |
| 3267 | +version = "2.0.21" | |
| 3268 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3269 | +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" | |
| 3270 | +dependencies = [ | |
| 3271 | + "thiserror-impl 2.0.21", | |
| 3093 | 3272 | ] |
| 3094 | 3273 | |
| 3095 | 3274 | [[package]] |
@@ -3103,6 +3282,17 @@ dependencies = [
| 3103 | 3282 | "syn 2.0.90", |
| 3104 | 3283 | ] |
| 3105 | 3284 | |
| 3285 | +[[package]] | |
| 3286 | +name = "thiserror-impl" | |
| 3287 | +version = "2.0.21" | |
| 3288 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3289 | +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" | |
| 3290 | +dependencies = [ | |
| 3291 | + "proc-macro2", | |
| 3292 | + "quote", | |
| 3293 | + "syn 3.0.9", | |
| 3294 | +] | |
| 3295 | + | |
| 3106 | 3296 | [[package]] |
| 3107 | 3297 | name = "thread_local" |
| 3108 | 3298 | version = "1.1.8" |
@@ -3387,6 +3577,41 @@ version = "0.1.1"
| 3387 | 3577 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3388 | 3578 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" |
| 3389 | 3579 | |
| 3580 | +[[package]] | |
| 3581 | +name = "untrusted" | |
| 3582 | +version = "0.9.0" | |
| 3583 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3584 | +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" | |
| 3585 | + | |
| 3586 | +[[package]] | |
| 3587 | +name = "ureq" | |
| 3588 | +version = "3.4.2" | |
| 3589 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3590 | +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" | |
| 3591 | +dependencies = [ | |
| 3592 | + "base64 0.23.1", | |
| 3593 | + "flate2", | |
| 3594 | + "log", | |
| 3595 | + "percent-encoding", | |
| 3596 | + "rustls", | |
| 3597 | + "rustls-pki-types", | |
| 3598 | + "ureq-proto", | |
| 3599 | + "utf8-zero", | |
| 3600 | + "webpki-roots", | |
| 3601 | +] | |
| 3602 | + | |
| 3603 | +[[package]] | |
| 3604 | +name = "ureq-proto" | |
| 3605 | +version = "0.6.5" | |
| 3606 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3607 | +checksum = "841583fc36089d7d59419568ae157f43a5a03a2e5a81ba87c77d1596ef46e922" | |
| 3608 | +dependencies = [ | |
| 3609 | + "base64 0.23.1", | |
| 3610 | + "http", | |
| 3611 | + "httparse", | |
| 3612 | + "log", | |
| 3613 | +] | |
| 3614 | + | |
| 3390 | 3615 | [[package]] |
| 3391 | 3616 | name = "url" |
| 3392 | 3617 | version = "2.5.4" |
@@ -3404,6 +3629,12 @@ version = "1.0.5"
| 3404 | 3629 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 3405 | 3630 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" |
| 3406 | 3631 | |
| 3632 | +[[package]] | |
| 3633 | +name = "utf8-zero" | |
| 3634 | +version = "0.8.1" | |
| 3635 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3636 | +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" | |
| 3637 | + | |
| 3407 | 3638 | [[package]] |
| 3408 | 3639 | name = "utf8_iter" |
| 3409 | 3640 | version = "1.0.4" |
@@ -3525,6 +3756,15 @@ dependencies = [
| 3525 | 3756 | "unicode-segmentation", |
| 3526 | 3757 | ] |
| 3527 | 3758 | |
| 3759 | +[[package]] | |
| 3760 | +name = "webpki-roots" | |
| 3761 | +version = "1.0.9" | |
| 3762 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3763 | +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" | |
| 3764 | +dependencies = [ | |
| 3765 | + "rustls-pki-types", | |
| 3766 | +] | |
| 3767 | + | |
| 3528 | 3768 | [[package]] |
| 3529 | 3769 | name = "whoami" |
| 3530 | 3770 | version = "1.5.2" |
@@ -3912,6 +4152,12 @@ dependencies = [
| 3912 | 4152 | "syn 3.0.9", |
| 3913 | 4153 | ] |
| 3914 | 4154 | |
| 4155 | +[[package]] | |
| 4156 | +name = "zlib-rs" | |
| 4157 | +version = "0.6.8" | |
| 4158 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4159 | +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" | |
| 4160 | + | |
| 3915 | 4161 | [[package]] |
| 3916 | 4162 | name = "zmij" |
| 3917 | 4163 | version = "1.0.14" |
modified
Cargo.toml
+3
−0
@@ -60,6 +60,9 @@ itertools = "0.12.0"
| 60 | 60 | xdg = "2.5.2" |
| 61 | 61 | serde_json = "1.0.149" |
| 62 | 62 | icu_calendar = "2.3.0" |
| 63 | +rrule = "0.14.0" | |
| 64 | +chrono-tz = "0.10.4" | |
| 65 | +ureq = "3.4.2" | |
| 63 | 66 | |
| 64 | 67 | |
| 65 | 68 | [lints.clippy] |
modified
README.md
+19
−0
@@ -138,6 +138,25 @@ ppl calendars show hindu -y 2027 # or from any calendar, any year
| 138 | 138 | |
| 139 | 139 | Turning on a state turns on its country; turning off a country turns off its states. |
| 140 | 140 | |
| 141 | +#### Subscribed (iCal) calendars | |
| 142 | + | |
| 143 | +Any iCal calendar can be added alongside the built-in ones: a Google, Outlook or iCloud calendar's | |
| 144 | +"secret address in iCal format", a `webcal://` link, or a local `.ics` file. | |
| 145 | + | |
| 146 | +``` | |
| 147 | +ppl calendars subscribe work https://calendar.google.com/calendar/ical/.../basic.ics | |
| 148 | +ppl calendars subscribe family ~/calendars/family.ics | |
| 149 | +ppl calendars sync # fetch now | |
| 150 | +ppl calendars unsubscribe work | |
| 151 | +``` | |
| 152 | + | |
| 153 | +Subscribed calendars are switched on and off, shown, and reminded of like the built-in ones; their | |
| 154 | +group for motd defaults is `subscribed` (`ppl calendars set subscribed 1`). Repeating events, moved | |
| 155 | +or cancelled occurrences and multi-day events are handled; timed events show their local start time. | |
| 156 | +Remote calendars are cached in `~/.cache/pplapp/ical` and fetched again when they're 6 hours old, | |
| 157 | +with a 5 second timeout; if that fails (say, offline) the old copy is used. In the TUI, `i` on an | |
| 158 | +event shows its description, location and link. | |
| 159 | + | |
| 141 | 160 | #### Holidays in the motd |
| 142 | 161 | |
| 143 | 162 | `ppl motd` mentions upcoming holidays from the calendars that are on. Like tiers, each calendar has a |
modified
src/data.rs
+79
−6
@@ -783,13 +783,12 @@ pub async fn delete_editable(db: &DatabaseConnection, editable: &Editable) -> Re
| 783 | 783 | pub struct CalendarOps {} |
| 784 | 784 | |
| 785 | 785 | impl CalendarOps { |
| 786 | − /// Keys of the built-in calendars that are switched on | |
| 786 | + /// Keys of the calendars that are switched on, built-in and subscribed | |
| 787 | 787 | pub async fn enabled(db: &DatabaseConnection) -> Result<Vec<String>, DbErr> { |
| 788 | − Ok(Calendar::find() | |
| 789 | − .filter(calendar::Column::Enabled.eq(true)) | |
| 790 | − .all(db) | |
| 788 | + Ok(Self::rows(db) | |
| 791 | 789 | .await? |
| 792 | 790 | .into_iter() |
| 791 | + .filter(|c| c.enabled) | |
| 793 | 792 | .filter_map(|c| holidays::find(&c.key).map(|d| d.key.to_string())) |
| 794 | 793 | .collect()) |
| 795 | 794 | } |
@@ -801,6 +800,7 @@ impl CalendarOps {
| 801 | 800 | key: &str, |
| 802 | 801 | on: bool, |
| 803 | 802 | ) -> Result<Vec<&'static CalendarDef>, PplError> { |
| 803 | + Self::rows(db).await?; | |
| 804 | 804 | let Some(def) = holidays::find(key) else { |
| 805 | 805 | return Err(invalid(format!( |
| 806 | 806 | "no calendar '{}', see `ppl calendars` for the list", |
@@ -824,9 +824,80 @@ impl CalendarOps {
| 824 | 824 | Ok(changed) |
| 825 | 825 | } |
| 826 | 826 | |
| 827 | − /// Every row: calendars that have been switched on or given reminder settings, and group defaults | |
| 827 | + /// Every row: calendars that have been switched on or given reminder settings, group | |
| 828 | + /// defaults and subscriptions. Reading them makes the subscriptions findable. | |
| 828 | 829 | pub async fn rows(db: &DatabaseConnection) -> Result<Vec<calendar::Model>, DbErr> { |
| 829 | − Calendar::find().all(db).await | |
| 830 | + let rows = Calendar::find().all(db).await?; | |
| 831 | + for r in &rows { | |
| 832 | + if let Some(url) = &r.url { | |
| 833 | + holidays::register_subscription(&r.key, url); | |
| 834 | + } | |
| 835 | + } | |
| 836 | + Ok(rows) | |
| 837 | + } | |
| 838 | + | |
| 839 | + /// Subscribed calendars, as (key, url or path) | |
| 840 | + pub async fn subscriptions(db: &DatabaseConnection) -> Result<Vec<(String, String)>, DbErr> { | |
| 841 | + Ok(Self::rows(db) | |
| 842 | + .await? | |
| 843 | + .into_iter() | |
| 844 | + .filter_map(|r| Some((r.key, r.url?))) | |
| 845 | + .collect()) | |
| 846 | + } | |
| 847 | + | |
| 848 | + /// Subscribe to an iCal calendar, switched on. It's fetched (or read) first, so a bad | |
| 849 | + /// URL is refused rather than saved. Returns how many events it has. | |
| 850 | + pub async fn subscribe( | |
| 851 | + db: &DatabaseConnection, | |
| 852 | + name: &str, | |
| 853 | + source: &str, | |
| 854 | + ) -> Result<usize, PplError> { | |
| 855 | + let name = name.trim().to_lowercase(); | |
| 856 | + let valid = !name.is_empty() | |
| 857 | + && name.len() <= 24 | |
| 858 | + && name | |
| 859 | + .chars() | |
| 860 | + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-'); | |
| 861 | + if !valid { | |
| 862 | + return Err(invalid( | |
| 863 | + "a calendar name is up to 24 lowercase letters, digits and dashes, like work or family-ical".to_string(), | |
| 864 | + )); | |
| 865 | + } | |
| 866 | + if holidays::is_builtin(&name) { | |
| 867 | + return Err(invalid(format!( | |
| 868 | + "'{}' is a built-in calendar or group; pick another name", | |
| 869 | + name | |
| 870 | + ))); | |
| 871 | + } | |
| 872 | + let rows = Self::rows(db).await?; | |
| 873 | + if rows.iter().any(|r| r.key == name && r.url.is_some()) { | |
| 874 | + return Err(invalid(format!( | |
| 875 | + "already subscribed to '{}'; `ppl calendars unsubscribe {}` first to change it", | |
| 876 | + name, name | |
| 877 | + ))); | |
| 878 | + } | |
| 879 | + let count = crate::ical::sync(&name, source.trim())?; | |
| 880 | + Self::upsert(db, &name, |row| { | |
| 881 | + row.url = Set(Some(source.trim().to_string())); | |
| 882 | + row.enabled = Set(true); | |
| 883 | + }) | |
| 884 | + .await?; | |
| 885 | + Self::rows(db).await?; | |
| 886 | + Ok(count) | |
| 887 | + } | |
| 888 | + | |
| 889 | + pub async fn unsubscribe(db: &DatabaseConnection, name: &str) -> Result<(), PplError> { | |
| 890 | + let rows = Self::rows(db).await?; | |
| 891 | + let Some(row) = rows | |
| 892 | + .into_iter() | |
| 893 | + .find(|r| r.key.eq_ignore_ascii_case(name) && r.url.is_some()) | |
| 894 | + else { | |
| 895 | + return Err(invalid(format!("not subscribed to '{}'", name))); | |
| 896 | + }; | |
| 897 | + Calendar::delete_by_id(row.id).exec(db).await?; | |
| 898 | + crate::ical::remove_cache(&row.key); | |
| 899 | + holidays::unregister_subscription(&row.key); | |
| 900 | + Ok(()) | |
| 830 | 901 | } |
| 831 | 902 | |
| 832 | 903 | /// Set the motd window and mode of a calendar or group; None goes back to the default |
@@ -883,6 +954,7 @@ impl CalendarOps {
| 883 | 954 | sig_date_delta: Set(None), |
| 884 | 955 | sig_remind_enum: Set(None), |
| 885 | 956 | meta: Set(None), |
| 957 | + url: Set(None), | |
| 886 | 958 | date_ins: Set(today), |
| 887 | 959 | date_up: Set(today), |
| 888 | 960 | }, |
@@ -1188,6 +1260,7 @@ mod tests {
| 1188 | 1260 | sig_date_delta: delta, |
| 1189 | 1261 | sig_remind_enum: remind.map(|r| r.to_string()), |
| 1190 | 1262 | meta: None, |
| 1263 | + url: None, | |
| 1191 | 1264 | date_ins: today, |
| 1192 | 1265 | date_up: today, |
| 1193 | 1266 | } |
modified
src/do_calendars.rs
+49
−0
@@ -41,6 +41,16 @@ pub enum CalendarAction {
| 41 | 41 | }, |
| 42 | 42 | /// Go back to the defaults (a state's country, then the group, then on the day, daily) |
| 43 | 43 | Clear { target: String }, |
| 44 | + /// Add an iCal calendar from a URL (http, https, webcal) or an .ics file, switched on | |
| 45 | + Subscribe { | |
| 46 | + /// What to call it, like work; lowercase letters, digits and dashes | |
| 47 | + name: String, | |
| 48 | + url: String, | |
| 49 | + }, | |
| 50 | + /// Remove a subscribed calendar and its settings | |
| 51 | + Unsubscribe { name: String }, | |
| 52 | + /// Fetch subscribed calendars now, rather than when they're 6 hours old | |
| 53 | + Sync { names: Vec<String> }, | |
| 44 | 54 | /// Print a year's holidays from the calendars that are on, or the ones named |
| 45 | 55 | Show { |
| 46 | 56 | keys: Vec<String>, |
@@ -51,6 +61,10 @@ pub enum CalendarAction {
| 51 | 61 | } |
| 52 | 62 | |
| 53 | 63 | pub async fn do_calendars(args: &CalendarArgs, db: &DatabaseConnection) -> Result<(), PplError> { |
| 64 | + let subscriptions = CalendarOps::subscriptions(db).await?; | |
| 65 | + if matches!(&args.action, Some(CalendarAction::Show { .. })) { | |
| 66 | + crate::ical::refresh_stale(&subscriptions); | |
| 67 | + } | |
| 54 | 68 | let enabled = CalendarOps::enabled(db).await?; |
| 55 | 69 | let rows = CalendarOps::rows(db).await?; |
| 56 | 70 | match &args.action { |
@@ -68,6 +82,41 @@ pub async fn do_calendars(args: &CalendarArgs, db: &DatabaseConnection) -> Resul
| 68 | 82 | CalendarOps::set_reminder(db, t, r.delta, Some(*mode)).await?; |
| 69 | 83 | println!("{}: remind {}", t.label(), mode.label()); |
| 70 | 84 | } |
| 85 | + Some(CalendarAction::Subscribe { name, url }) => { | |
| 86 | + let count = CalendarOps::subscribe(db, name, url).await?; | |
| 87 | + println!( | |
| 88 | + "subscribed to {} ({} events); it's on, and refreshes every 6 hours", | |
| 89 | + name.trim().to_lowercase(), | |
| 90 | + count | |
| 91 | + ); | |
| 92 | + } | |
| 93 | + Some(CalendarAction::Unsubscribe { name }) => { | |
| 94 | + CalendarOps::unsubscribe(db, name).await?; | |
| 95 | + println!("unsubscribed from {}", name); | |
| 96 | + } | |
| 97 | + Some(CalendarAction::Sync { names }) => { | |
| 98 | + for n in names { | |
| 99 | + if !subscriptions.iter().any(|(k, _)| k.eq_ignore_ascii_case(n)) { | |
| 100 | + return Err(io::Error::new( | |
| 101 | + io::ErrorKind::NotFound, | |
| 102 | + format!("not subscribed to '{}'", n), | |
| 103 | + ) | |
| 104 | + .into()); | |
| 105 | + } | |
| 106 | + } | |
| 107 | + if subscriptions.is_empty() { | |
| 108 | + println!("no subscriptions; add one with `ppl calendars subscribe <name> <url>`"); | |
| 109 | + } | |
| 110 | + for (key, source) in &subscriptions { | |
| 111 | + if !names.is_empty() && !names.iter().any(|n| n.eq_ignore_ascii_case(key)) { | |
| 112 | + continue; | |
| 113 | + } | |
| 114 | + match crate::ical::sync(key, source) { | |
| 115 | + Ok(count) => println!("{}: {} events", key, count), | |
| 116 | + Err(e) => println!("{}: couldn't fetch, keeping what was there ({})", key, e), | |
| 117 | + } | |
| 118 | + } | |
| 119 | + } | |
| 71 | 120 | Some(CalendarAction::Clear { target }) => { |
| 72 | 121 | let t = ReminderTarget::parse(target)?; |
| 73 | 122 | CalendarOps::set_reminder(db, t, None, None).await?; |
modified
src/do_tui.rs
+7
−3
Click to load diff…
modified
src/entities/calendar.rs
+2
−0
Click to load diff…
modified
src/holidays.rs
+82
−2
Click to load diff…
added
src/ical.rs
+500
−0
Click to load diff…
modified
src/main.rs
+3
−0
Click to load diff…
added
src/migrator/m20261019_000015_add_url_to_calendar.rs
+43
−0
Click to load diff…
modified
src/migrator/mod.rs
+2
−0
Click to load diff…
modified
src/tui_calendars.rs
+19
−7
Click to load diff…
Parents: dea84dc