Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
644800907d | ||
|
|
2281c34529 | ||
|
|
c36c06ba94 | ||
|
|
fd216ff7c3 | ||
|
|
511f2123e6 | ||
|
|
7cc1e778bb | ||
|
|
7b13d74edc | ||
|
|
159b6199fa | ||
|
|
313f2ed0e9 | ||
|
|
6c64bcce8e | ||
|
|
56484a6d4d | ||
|
|
a973146366 | ||
|
|
f7110c254f | ||
|
|
ac7d6952eb | ||
|
|
5505a17081 | ||
|
|
039965966f | ||
|
|
94b3bb27ba | ||
|
|
3d142edf3e | ||
|
|
7f8529b837 | ||
|
|
ea6cd47b5d |
Generated
+133
-131
@@ -1,5 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
@@ -11,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.51"
|
||||
version = "0.1.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
|
||||
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -51,9 +53,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.8.0"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
@@ -114,10 +116,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.29"
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -135,6 +143,15 @@ dependencies = [
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "779d043b6a0b90cc4c0ed7ee380a6504394cee7efd7db050e3774eee387324b2"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -168,36 +185,36 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
|
||||
checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
|
||||
checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
|
||||
checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
|
||||
checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.18"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
|
||||
checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -218,20 +235,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
"wasi 0.10.3+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.7"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55"
|
||||
checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -263,13 +280,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
|
||||
checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
"itoa 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -303,9 +320,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.15"
|
||||
version = "0.14.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c"
|
||||
checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -316,7 +333,7 @@ dependencies = [
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"itoa 0.4.8",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
@@ -367,26 +384,50 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
|
||||
checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.55"
|
||||
@@ -398,7 +439,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kitchen-fridge"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bitflags",
|
||||
@@ -407,6 +448,7 @@ dependencies = [
|
||||
"env_logger",
|
||||
"ical-daladim",
|
||||
"ics",
|
||||
"itertools",
|
||||
"log",
|
||||
"minidom",
|
||||
"once_cell",
|
||||
@@ -427,9 +469,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.108"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -537,9 +579,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
||||
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
@@ -547,9 +589,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
@@ -567,15 +609,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.71"
|
||||
version = "0.9.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
|
||||
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
@@ -608,7 +650,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand 0.7.3",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -636,9 +678,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||
checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
@@ -648,15 +690,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.22"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
|
||||
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
@@ -666,9 +708,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
@@ -684,9 +726,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -699,24 +741,12 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
"libc",
|
||||
"rand_chacha 0.2.2",
|
||||
"rand_core 0.5.1",
|
||||
"rand_hc 0.2.0",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
"rand_pcg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.3",
|
||||
"rand_hc 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
@@ -724,17 +754,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -746,31 +766,13 @@ dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -779,7 +781,7 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
|
||||
dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -819,15 +821,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.6"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
|
||||
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
@@ -854,9 +857,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "sanitize-filename"
|
||||
@@ -880,9 +883,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
|
||||
checksum = "d09d3c15d814eda1d6a836f2f2b56a6abc1446c8a34351cb3180d3db92ffe4ce"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
@@ -893,9 +896,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.4.2"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
|
||||
checksum = "e90dd10c41c6bfc633da6e0c659bd25d31e0791e5974ac42970267d59eba87f7"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -903,18 +906,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
version = "1.0.133"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -923,32 +926,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.71"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
|
||||
checksum = "c059c05b48c5c0067d4b4b2b4f0732dd65feb52daf7e0ea09cd87e7dadc1af79"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"itoa 1.0.1",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.7"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
|
||||
checksum = "a86232ab60fa71287d7f2ddae4a7073f6b7aac33631c3015abb556f08c6d0a3e"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
@@ -968,9 +971,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||
checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -979,13 +982,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"rand 0.8.4",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
@@ -1047,11 +1050,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.14.0"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
||||
checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
@@ -1064,9 +1066,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.6.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9efc1aba077437943f7515666aa2b882dfabfbfdf89c819ea75a8d6e9eaba5e"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1169,7 +1171,7 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
"getrandom 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1196,9 +1198,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
version = "0.10.3+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
checksum = "46a2e384a3f170b0c7543787a91411175b71afd56ba4d3a0ae5678d4e2243c0e"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kitchen-fridge"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
authors = ["daladim"]
|
||||
edition = "2018"
|
||||
description = "A CalDAV (ical file management over WebDAV) library"
|
||||
@@ -35,3 +35,4 @@ ics = "0.5"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
csscolorparser = { version = "0.5", features = ["serde"] }
|
||||
once_cell = "1.8"
|
||||
itertools = "0.10"
|
||||
|
||||
+10
-45
@@ -1,40 +1,32 @@
|
||||
//! This is an example of how kitchen-fridge can be used
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::{Utc};
|
||||
use url::Url;
|
||||
|
||||
use kitchen_fridge::{client::Client, traits::CalDavSource};
|
||||
use kitchen_fridge::traits::CalDavSource;
|
||||
use kitchen_fridge::calendar::SupportedComponents;
|
||||
use kitchen_fridge::Item;
|
||||
use kitchen_fridge::Task;
|
||||
use kitchen_fridge::Event;
|
||||
use kitchen_fridge::task::CompletionStatus;
|
||||
use kitchen_fridge::cache::Cache;
|
||||
use kitchen_fridge::CalDavProvider;
|
||||
use kitchen_fridge::traits::BaseCalendar;
|
||||
use kitchen_fridge::traits::CompleteCalendar;
|
||||
use kitchen_fridge::utils::pause;
|
||||
|
||||
mod shared;
|
||||
use shared::initial_sync;
|
||||
use shared::{URL, USERNAME, EXAMPLE_EXISTING_CALENDAR_URL, EXAMPLE_CREATED_CALENDAR_URL};
|
||||
|
||||
const CACHE_FOLDER: &str = "test_cache/provider_sync";
|
||||
|
||||
|
||||
// TODO: change these values with yours
|
||||
pub const URL: &str = "https://my.server.com/remote.php/dav/files/john";
|
||||
pub const USERNAME: &str = "username";
|
||||
pub const PASSWORD: &str = "secret_password";
|
||||
|
||||
pub const EXAMPLE_EXISTING_CALENDAR_URL: &str = "https://my.server.com/remote.php/dav/calendars/john/a_calendar_name/";
|
||||
pub const EXAMPLE_CREATED_CALENDAR_URL: &str = "https://my.server.com/remote.php/dav/calendars/john/a_calendar_that_we_have_created/";
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
println!("This examples show how to sync a remote server with a local cache, using a Provider.");
|
||||
println!("Make sure you have edited the constants in this file to include correct URLs and credentials.");
|
||||
println!("This example show how to sync a remote server with a local cache, using a Provider.");
|
||||
println!("Make sure you have edited the constants in the 'shared.rs' file to include correct URLs and credentials.");
|
||||
println!("You can also set the RUST_LOG environment variable to display more info about the sync.");
|
||||
println!("");
|
||||
println!("This will use the following settings:");
|
||||
@@ -44,39 +36,12 @@ async fn main() {
|
||||
println!(" * EXAMPLE_CREATED_CALENDAR_URL = {}", EXAMPLE_CREATED_CALENDAR_URL);
|
||||
pause();
|
||||
|
||||
let cache_path = Path::new(CACHE_FOLDER);
|
||||
|
||||
let client = Client::new(URL, USERNAME, PASSWORD).unwrap();
|
||||
let cache = match Cache::from_folder(&cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => {
|
||||
log::warn!("Invalid cache file: {}. Using a default cache", err);
|
||||
Cache::new(&cache_path)
|
||||
}
|
||||
};
|
||||
let mut provider = CalDavProvider::new(client, cache);
|
||||
|
||||
let cals = provider.local().get_calendars().await.unwrap();
|
||||
println!("---- Local items, before sync -----");
|
||||
kitchen_fridge::utils::print_calendar_list(&cals).await;
|
||||
|
||||
println!("Starting a sync...");
|
||||
println!("Depending on your RUST_LOG value, you may see more or less details about the progress.");
|
||||
// Note that we could use sync_with_feedback() to have better and formatted feedback
|
||||
if provider.sync().await == false {
|
||||
log::warn!("Sync did not complete, see the previous log lines for more info. You can safely start a new sync.");
|
||||
}
|
||||
provider.local().save_to_folder().unwrap();
|
||||
|
||||
println!("---- Local items, after sync -----");
|
||||
let cals = provider.local().get_calendars().await.unwrap();
|
||||
kitchen_fridge::utils::print_calendar_list(&cals).await;
|
||||
let mut provider = initial_sync(CACHE_FOLDER).await;
|
||||
|
||||
add_items_and_sync_again(&mut provider).await;
|
||||
}
|
||||
|
||||
async fn add_items_and_sync_again(provider: &mut CalDavProvider)
|
||||
{
|
||||
async fn add_items_and_sync_again(provider: &mut CalDavProvider) {
|
||||
println!("\nNow, we'll add a calendar and a few tasks and run the sync again.");
|
||||
pause();
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::path::Path;
|
||||
|
||||
use kitchen_fridge::cache::Cache;
|
||||
use kitchen_fridge::client::Client;
|
||||
use kitchen_fridge::traits::CalDavSource;
|
||||
use kitchen_fridge::CalDavProvider;
|
||||
|
||||
// TODO: change these values with yours
|
||||
pub const URL: &str = "https://next.anshorei.me/remote.php/dav/files/test";
|
||||
pub const USERNAME: &str = "test";
|
||||
pub const PASSWORD: &str = "1ETsJD6BPTIMLhmVQcCK";
|
||||
|
||||
// pub const EXAMPLE_EXISTING_CALENDAR_URL: &str =
|
||||
// "https://my.server.com/remote.php/dav/calendars/john/a_calendar_name/";
|
||||
pub const EXAMPLE_CREATED_CALENDAR_URL: &str =
|
||||
"https://next.anshorei.me/remote.php/dav/calendars/test/a_calendar_that_we_have_created/";
|
||||
// https://next.anshorei.me/remote.php/dav
|
||||
pub const EXAMPLE_EXISTING_CALENDAR_URL: &str =
|
||||
"https://next.anshorei.me/remote.php/dav/calendars/test/personal/";
|
||||
|
||||
fn main() {
|
||||
panic!("This file is not supposed to be executed");
|
||||
}
|
||||
|
||||
/// Initializes a Provider, and run an initial sync from the server
|
||||
pub async fn initial_sync(cache_folder: &str) -> CalDavProvider {
|
||||
let cache_path = Path::new(cache_folder);
|
||||
|
||||
let client = Client::new(URL, USERNAME, PASSWORD).unwrap();
|
||||
let cache = match Cache::from_folder(&cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => {
|
||||
log::warn!("Invalid cache file: {}. Using a default cache", err);
|
||||
Cache::new(&cache_path)
|
||||
}
|
||||
};
|
||||
let mut provider = CalDavProvider::new(client, cache);
|
||||
|
||||
let cals = provider.local().get_calendars().await.unwrap();
|
||||
println!("---- Local items, before sync -----");
|
||||
kitchen_fridge::utils::print_calendar_list(&cals).await;
|
||||
|
||||
println!("Starting a sync...");
|
||||
println!(
|
||||
"Depending on your RUST_LOG value, you may see more or less details about the progress."
|
||||
);
|
||||
// Note that we could use sync_with_feedback() to have better and formatted feedback
|
||||
if provider.sync().await == false {
|
||||
log::warn!("Sync did not complete, see the previous log lines for more info. You can safely start a new sync.");
|
||||
}
|
||||
provider.local().save_to_folder().unwrap();
|
||||
|
||||
println!("---- Local items, after sync -----");
|
||||
let cals = provider.local().get_calendars().await.unwrap();
|
||||
kitchen_fridge::utils::print_calendar_list(&cals).await;
|
||||
|
||||
provider
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//! This is an example of how kitchen-fridge can be used.
|
||||
//! This binary simply toggles all completion statuses of the tasks it finds.
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use kitchen_fridge::item::Item;
|
||||
use kitchen_fridge::task::CompletionStatus;
|
||||
use kitchen_fridge::CalDavProvider;
|
||||
use kitchen_fridge::utils::pause;
|
||||
|
||||
mod shared;
|
||||
use shared::initial_sync;
|
||||
use shared::{URL, USERNAME};
|
||||
|
||||
const CACHE_FOLDER: &str = "test_cache/toggle_completion";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
println!("This example show how to sync a remote server with a local cache, using a Provider.");
|
||||
println!("Make sure you have edited the constants in the 'shared.rs' file to include correct URLs and credentials.");
|
||||
println!("You can also set the RUST_LOG environment variable to display more info about the sync.");
|
||||
println!("");
|
||||
println!("This will use the following settings:");
|
||||
println!(" * URL = {}", URL);
|
||||
println!(" * USERNAME = {}", USERNAME);
|
||||
pause();
|
||||
|
||||
let mut provider = initial_sync(CACHE_FOLDER).await;
|
||||
|
||||
toggle_all_tasks_and_sync_again(&mut provider).await.unwrap();
|
||||
}
|
||||
|
||||
async fn toggle_all_tasks_and_sync_again(provider: &mut CalDavProvider) -> Result<(), Box<dyn Error>> {
|
||||
let mut n_toggled = 0;
|
||||
|
||||
for (_url, cal) in provider.local().get_calendars_sync()?.iter() {
|
||||
for (_url, item) in cal.lock().unwrap().get_items_mut_sync()?.iter_mut() {
|
||||
match item {
|
||||
Item::Task(task) => {
|
||||
match task.completed() {
|
||||
false => task.set_completion_status(CompletionStatus::Completed(Some(Utc::now()))),
|
||||
true => task.set_completion_status(CompletionStatus::Uncompleted),
|
||||
};
|
||||
n_toggled += 1;
|
||||
}
|
||||
Item::Event(_) => {
|
||||
// Not doing anything with calendar events
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("{} items toggled.", n_toggled);
|
||||
println!("Syncing...");
|
||||
|
||||
provider.sync().await;
|
||||
|
||||
println!("Syncing complete.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -23,11 +23,11 @@
|
||||
inkscape:collect="always"
|
||||
id="linearGradient850-3">
|
||||
<stop
|
||||
style="stop-color:#7a7a7a;stop-opacity:1"
|
||||
style="stop-color:#555555;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop846" />
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop848" />
|
||||
</linearGradient>
|
||||
@@ -49,10 +49,10 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.7"
|
||||
inkscape:cx="730.92011"
|
||||
inkscape:cx="732.34868"
|
||||
inkscape:cy="461.7807"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer2"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2510"
|
||||
inkscape:window-height="1376"
|
||||
@@ -71,10 +71,23 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="Fond"
|
||||
style="display:none">
|
||||
<rect
|
||||
style="fill:#102335;fill-opacity:1;stroke:#b40f00;stroke-width:0.26499999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect867"
|
||||
width="213.55655"
|
||||
height="212.42261"
|
||||
x="0"
|
||||
y="0.37030393" />
|
||||
</g>
|
||||
<g
|
||||
inkscape:label="Fridge"
|
||||
inkscape:groupmode="layer"
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
+2
-1
@@ -27,7 +27,8 @@ const MAIN_FILE: &str = "data.json";
|
||||
///
|
||||
/// It automatically updates the content of the folder when dropped (see its `Drop` implementation), but you can also manually call [`Cache::save_to_folder`]
|
||||
///
|
||||
/// Most of its methods are part of the `CalDavSource` trait implementation
|
||||
/// Most of its functionality is provided by the `CalDavSource` async trait it implements.
|
||||
/// However, since these functions do not _need_ to be actually async, non-async versions of them are also provided for better convenience. See [`Cache::get_calendar_sync`] for example
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
backing_folder: PathBuf,
|
||||
|
||||
@@ -19,7 +19,8 @@ use crate::mock_behaviour::MockBehaviour;
|
||||
|
||||
/// A calendar used by the [`cache`](crate::cache) module
|
||||
///
|
||||
/// Most of its methods are part of traits implementations
|
||||
/// Most of its functionality is provided by the async traits it implements.
|
||||
/// However, since these functions do not _need_ to be actually async, non-async versions of them are also provided for better convenience. See [`CachedCalendar::add_item_sync`] for example
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct CachedCalendar {
|
||||
name: String,
|
||||
@@ -134,6 +135,14 @@ impl CachedCalendar {
|
||||
)
|
||||
}
|
||||
|
||||
/// The non-async version of [`Self::get_items_mut`]
|
||||
pub fn get_items_mut_sync(&mut self) -> Result<HashMap<Url, &mut Item>, Box<dyn Error>> {
|
||||
Ok(self.items.iter_mut()
|
||||
.map(|(url, item)| (url.clone(), item))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
/// The non-async version of [`Self::get_item_by_url`]
|
||||
pub fn get_item_by_url_sync<'a>(&'a self, url: &Url) -> Option<&'a Item> {
|
||||
self.items.get(url)
|
||||
@@ -253,6 +262,10 @@ impl CompleteCalendar for CachedCalendar {
|
||||
self.get_items_sync()
|
||||
}
|
||||
|
||||
async fn get_items_mut(&mut self) -> Result<HashMap<Url, &mut Item>, Box<dyn Error>> {
|
||||
self.get_items_mut_sync()
|
||||
}
|
||||
|
||||
async fn get_item_by_url<'a>(&'a self, url: &Url) -> Option<&'a Item> {
|
||||
self.get_item_by_url_sync(url)
|
||||
}
|
||||
@@ -314,6 +327,14 @@ impl DavCalendar for CachedCalendar {
|
||||
Ok(self.items.get(url).cloned())
|
||||
}
|
||||
|
||||
async fn get_items_by_url(&self, urls: &[Url]) -> Result<Vec<Option<Item>>, Box<dyn Error>> {
|
||||
let mut v = Vec::new();
|
||||
for url in urls {
|
||||
v.push(DavCalendar::get_item_by_url(self, url).await?);
|
||||
}
|
||||
Ok(v)
|
||||
}
|
||||
|
||||
async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box<dyn Error>> {
|
||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?;
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ pub enum SearchFilter {
|
||||
// /// Return only completed tasks
|
||||
// CompletedTasks,
|
||||
// /// Return only calendar events
|
||||
// Events,
|
||||
Events,
|
||||
}
|
||||
|
||||
impl Default for SearchFilter {
|
||||
|
||||
@@ -14,6 +14,20 @@ use crate::item::Item;
|
||||
use crate::item::VersionTag;
|
||||
use crate::item::SyncStatus;
|
||||
use crate::resource::Resource;
|
||||
use crate::utils::find_elem;
|
||||
|
||||
// static TASKS_BODY: &str = r#"
|
||||
// <c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
||||
// <d:prop>
|
||||
// <d:getetag />
|
||||
// </d:prop>
|
||||
// <c:filter>
|
||||
// <c:comp-filter name="VCALENDAR">
|
||||
// <c:comp-filter name="VTODO" />
|
||||
// </c:comp-filter>
|
||||
// </c:filter>
|
||||
// </c:calendar-query>
|
||||
// "#;
|
||||
|
||||
static TASKS_BODY: &str = r#"
|
||||
<c:calendar-query xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
||||
@@ -22,12 +36,20 @@ static TASKS_BODY: &str = r#"
|
||||
</d:prop>
|
||||
<c:filter>
|
||||
<c:comp-filter name="VCALENDAR">
|
||||
<c:comp-filter name="VTODO" />
|
||||
</c:comp-filter>
|
||||
</c:filter>
|
||||
</c:calendar-query>
|
||||
"#;
|
||||
|
||||
static MULTIGET_BODY_PREFIX: &str = r#"
|
||||
<c:calendar-multiget xmlns:d="DAV:" xmlns:c="urn:ietf:params:xml:ns:caldav">
|
||||
<d:prop>
|
||||
<c:calendar-data />
|
||||
</d:prop>
|
||||
"#;
|
||||
static MULTIGET_BODY_SUFFIX: &str = r#"
|
||||
</c:calendar-multiget>
|
||||
"#;
|
||||
|
||||
|
||||
|
||||
@@ -191,6 +213,40 @@ impl DavCalendar for RemoteCalendar {
|
||||
Ok(Some(item))
|
||||
}
|
||||
|
||||
async fn get_items_by_url(&self, urls: &[Url]) -> Result<Vec<Option<Item>>, Box<dyn Error>> {
|
||||
// Build the request body
|
||||
let mut hrefs = String::new();
|
||||
for url in urls {
|
||||
hrefs.push_str(&format!(" <d:href>{}</d:href>\n", url.path()));
|
||||
}
|
||||
let body = format!("{}{}{}", MULTIGET_BODY_PREFIX, hrefs, MULTIGET_BODY_SUFFIX);
|
||||
|
||||
// Send the request
|
||||
let xml_replies = crate::client::sub_request_and_extract_elems(&self.resource, "REPORT", body, "response").await?;
|
||||
|
||||
// This is supposed to be cached
|
||||
let version_tags = self.get_item_version_tags().await?;
|
||||
|
||||
// Parse the results
|
||||
let mut results = Vec::new();
|
||||
for xml_reply in xml_replies {
|
||||
let href = find_elem(&xml_reply, "href").ok_or("Missing HREF")?.text();
|
||||
let mut url = self.resource.url().clone();
|
||||
url.set_path(&href);
|
||||
let ical_data = find_elem(&xml_reply, "calendar-data").ok_or("Missing calendar-data")?.text();
|
||||
|
||||
let vt = match version_tags.get(&url) {
|
||||
None => return Err(format!("Inconsistent data: {} has no version tag", url).into()),
|
||||
Some(vt) => vt,
|
||||
};
|
||||
|
||||
let item = crate::ical::parse(&ical_data, url.clone(), SyncStatus::Synced(vt.clone()))?;
|
||||
results.push(Some(item));
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box<dyn Error>> {
|
||||
let del_response = reqwest::Client::new()
|
||||
.delete(item_url.clone())
|
||||
@@ -205,4 +261,3 @@ impl DavCalendar for RemoteCalendar {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//! Support for compile-time configuration options
|
||||
//! Support for library configuration options
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
+92
-11
@@ -1,27 +1,100 @@
|
||||
//! Calendar events (iCal `VEVENT` items)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ical::property::Property;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::item::SyncStatus;
|
||||
use crate::utils::random_url;
|
||||
|
||||
/// TODO: implement `Event` one day.
|
||||
/// This crate currently only supports tasks, not calendar events.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
/// An event
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
/// The event URL
|
||||
url: Url,
|
||||
|
||||
/// Persistent, globally unique identifier for the calendar component.
|
||||
uid: String,
|
||||
name: String,
|
||||
|
||||
/// The sync status of this item
|
||||
sync_status: SyncStatus,
|
||||
/// The time this event was created.
|
||||
/// This is not required by RFC5545. This will be populated in events created by this crate, but can be None for events coming from a server
|
||||
creation_date: Option<DateTime<Utc>>,
|
||||
last_modified: DateTime<Utc>,
|
||||
|
||||
start_date: DateTime<Utc>,
|
||||
end_date: DateTime<Utc>,
|
||||
|
||||
/// The display name of the event
|
||||
name: String,
|
||||
|
||||
/// The PRODID, as defined in iCal files
|
||||
ical_prod_id: String,
|
||||
|
||||
/// Extra parameters that have not been parsed from the iCal file (because they're not supported (yet) by this crate).
|
||||
/// They are needed to serialize this item into an equivalent iCal file
|
||||
extra_parameters: Vec<Property>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub fn new() -> Self {
|
||||
unimplemented!();
|
||||
pub fn new(
|
||||
name: String,
|
||||
parent_calendar_url: &Url,
|
||||
start_date: DateTime<Utc>,
|
||||
end_date: DateTime<Utc>,
|
||||
) -> Self {
|
||||
let new_url = random_url(parent_calendar_url);
|
||||
let new_sync_status = SyncStatus::NotSynced;
|
||||
let new_uid = Uuid::new_v4().to_hyphenated().to_string();
|
||||
let new_creation_date = Some(Utc::now());
|
||||
let new_last_modified = Utc::now();
|
||||
let ical_prod_id = crate::ical::default_prod_id();
|
||||
let extra_parameters = Vec::new();
|
||||
Self::new_with_parameters(
|
||||
name,
|
||||
new_uid,
|
||||
new_url,
|
||||
new_sync_status,
|
||||
new_creation_date,
|
||||
new_last_modified,
|
||||
start_date,
|
||||
end_date,
|
||||
ical_prod_id,
|
||||
extra_parameters,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_parameters(
|
||||
name: String,
|
||||
uid: String,
|
||||
new_url: Url,
|
||||
sync_status: SyncStatus,
|
||||
creation_date: Option<DateTime<Utc>>,
|
||||
last_modified: DateTime<Utc>,
|
||||
start_date: DateTime<Utc>,
|
||||
end_date: DateTime<Utc>,
|
||||
ical_prod_id: String,
|
||||
extra_parameters: Vec<Property>,
|
||||
) -> Self {
|
||||
Self {
|
||||
url: new_url,
|
||||
uid,
|
||||
name,
|
||||
sync_status,
|
||||
creation_date,
|
||||
last_modified,
|
||||
start_date,
|
||||
end_date,
|
||||
ical_prod_id,
|
||||
extra_parameters,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> &Url {
|
||||
unimplemented!();
|
||||
&self.url
|
||||
}
|
||||
|
||||
pub fn uid(&self) -> &str {
|
||||
@@ -33,15 +106,23 @@ impl Event {
|
||||
}
|
||||
|
||||
pub fn ical_prod_id(&self) -> &str {
|
||||
unimplemented!()
|
||||
&self.ical_prod_id
|
||||
}
|
||||
|
||||
pub fn creation_date(&self) -> Option<&DateTime<Utc>> {
|
||||
unimplemented!()
|
||||
self.creation_date.as_ref()
|
||||
}
|
||||
|
||||
pub fn last_modified(&self) -> &DateTime<Utc> {
|
||||
unimplemented!()
|
||||
&self.last_modified
|
||||
}
|
||||
|
||||
pub fn start_date(&self) -> &DateTime<Utc> {
|
||||
&self.start_date
|
||||
}
|
||||
|
||||
pub fn end_date(&self) -> &DateTime<Utc> {
|
||||
&self.end_date
|
||||
}
|
||||
|
||||
pub fn sync_status(&self) -> &SyncStatus {
|
||||
|
||||
+183
-34
@@ -2,26 +2,33 @@
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use chrono::{Date, DateTime, NaiveDate, NaiveTime, TimeZone, Utc};
|
||||
use ical::parser::ical::component::{IcalCalendar, IcalEvent, IcalTodo};
|
||||
use chrono::{DateTime, TimeZone, Utc};
|
||||
use url::Url;
|
||||
|
||||
use crate::Item;
|
||||
use crate::item::SyncStatus;
|
||||
use crate::Task;
|
||||
use crate::task::CompletionStatus;
|
||||
use crate::Event;
|
||||
|
||||
use crate::Item;
|
||||
use crate::Task;
|
||||
|
||||
/// Parse an iCal file into the internal representation [`crate::Item`]
|
||||
pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<Item, Box<dyn Error>> {
|
||||
pub fn parse(
|
||||
content: &str,
|
||||
item_url: Url,
|
||||
sync_status: SyncStatus,
|
||||
) -> Result<Item, Box<dyn Error>> {
|
||||
let mut reader = ical::IcalParser::new(content.as_bytes());
|
||||
let parsed_item = match reader.next() {
|
||||
None => return Err(format!("Invalid iCal data to parse for item {}", item_url).into()),
|
||||
Some(item) => match item {
|
||||
Err(err) => return Err(format!("Unable to parse iCal data for item {}: {}", item_url, err).into()),
|
||||
Ok(item) => item,
|
||||
Err(err) => {
|
||||
return Err(
|
||||
format!("Unable to parse iCal data for item {}: {}", item_url, err).into(),
|
||||
)
|
||||
}
|
||||
Ok(item) => item,
|
||||
},
|
||||
};
|
||||
|
||||
let ical_prod_id = extract_ical_prod_id(&parsed_item)
|
||||
@@ -29,9 +36,93 @@ pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<It
|
||||
.unwrap_or_else(|| super::default_prod_id());
|
||||
|
||||
let item = match assert_single_type(&parsed_item)? {
|
||||
CurrentType::Event(_) => {
|
||||
Item::Event(Event::new())
|
||||
},
|
||||
CurrentType::Event(event) => {
|
||||
let mut name = None;
|
||||
let mut uid = None;
|
||||
let mut last_modified = None;
|
||||
let mut creation_date = None;
|
||||
let mut start_date = None;
|
||||
let mut end_date = None;
|
||||
let mut extra_parameters = Vec::new();
|
||||
|
||||
for prop in &event.properties {
|
||||
match prop.name.as_str() {
|
||||
"SUMMARY" => name = prop.value.clone(),
|
||||
"UID" => uid = prop.value.clone(),
|
||||
"DTSTART" => {
|
||||
start_date = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"DTEND" => {
|
||||
end_date = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"DTSTAMP" => {
|
||||
last_modified = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"LAST-MODIFIED" => {
|
||||
last_modified = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"CREATED" => {
|
||||
// The property can be specified once, but is not mandatory
|
||||
creation_date = parse_date_time_from_property(&prop.value)
|
||||
}
|
||||
_ => {
|
||||
// This field is not supported. Let's store it anyway, so that we are able to re-create an identical iCal file
|
||||
extra_parameters.push(prop.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let name = match name {
|
||||
Some(name) => name,
|
||||
None => return Err(format!("Missing name for item {}", item_url).into()),
|
||||
};
|
||||
let uid = match uid {
|
||||
Some(uid) => uid,
|
||||
None => return Err(format!("Missing UID for item {}", item_url).into()),
|
||||
};
|
||||
let last_modified = match last_modified {
|
||||
Some(dt) => dt,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Missing DTSTAMP for item {}, but this is required by RFC5545",
|
||||
item_url
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let start_date = match start_date {
|
||||
Some(dt) => dt,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Missing DTSTART for item {}, but this is required by RFC5545",
|
||||
item_url
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let end_date = match end_date {
|
||||
Some(dt) => dt,
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Missing DTEND for item {}, but this is required by RFC5545",
|
||||
item_url
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
|
||||
Item::Event(Event::new_with_parameters(
|
||||
name,
|
||||
uid,
|
||||
item_url,
|
||||
sync_status,
|
||||
creation_date,
|
||||
last_modified,
|
||||
start_date,
|
||||
end_date,
|
||||
ical_prod_id,
|
||||
extra_parameters,
|
||||
))
|
||||
}
|
||||
|
||||
CurrentType::Todo(todo) => {
|
||||
let mut name = None;
|
||||
@@ -39,29 +130,44 @@ pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<It
|
||||
let mut completed = false;
|
||||
let mut last_modified = None;
|
||||
let mut completion_date = None;
|
||||
let mut completion_percent = None;
|
||||
let mut creation_date = None;
|
||||
let mut extra_parameters = Vec::new();
|
||||
|
||||
for prop in &todo.properties {
|
||||
match prop.name.as_str() {
|
||||
"SUMMARY" => { name = prop.value.clone() },
|
||||
"UID" => { uid = prop.value.clone() },
|
||||
"SUMMARY" => name = prop.value.clone(),
|
||||
"UID" => uid = prop.value.clone(),
|
||||
"DTSTAMP" => {
|
||||
// The property can be specified once, but is not mandatory
|
||||
// "This property specifies the date and time that the information associated with
|
||||
// the calendar component was last revised in the calendar store."
|
||||
last_modified = parse_date_time_from_property(&prop.value)
|
||||
},
|
||||
// "In the case of an iCalendar object that doesn't specify a "METHOD"
|
||||
// property [e.g.: VTODO and VEVENT], this property is equivalent to the "LAST-MODIFIED" property".
|
||||
last_modified = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"LAST-MODIFIED" => {
|
||||
// The property can be specified once, but is not mandatory
|
||||
// "This property specifies the date and time that the information associated with
|
||||
// the calendar component was last revised in the calendar store."
|
||||
// In practise, for VEVENT and VTODO, this is generally the same value as DTSTAMP.
|
||||
last_modified = parse_date_time_from_property(&prop.value);
|
||||
}
|
||||
"COMPLETED" => {
|
||||
// The property can be specified once, but is not mandatory
|
||||
// "This property defines the date and time that a to-do was
|
||||
// actually completed."
|
||||
completion_date = parse_date_time_from_property(&prop.value)
|
||||
},
|
||||
}
|
||||
"PERCENT-COMPLETE" => {
|
||||
if let Some(value) = &prop.value {
|
||||
completion_percent = value.parse().ok()
|
||||
}
|
||||
}
|
||||
"CREATED" => {
|
||||
// The property can be specified once, but is not mandatory
|
||||
creation_date = parse_date_time_from_property(&prop.value)
|
||||
},
|
||||
}
|
||||
"STATUS" => {
|
||||
// Possible values:
|
||||
// "NEEDS-ACTION" ;Indicates to-do needs action.
|
||||
@@ -88,7 +194,13 @@ pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<It
|
||||
};
|
||||
let last_modified = match last_modified {
|
||||
Some(dt) => dt,
|
||||
None => return Err(format!("Missing DTSTAMP for item {}, but this is required by RFC5545", item_url).into()),
|
||||
None => {
|
||||
return Err(format!(
|
||||
"Missing DTSTAMP for item {}, but this is required by RFC5545",
|
||||
item_url
|
||||
)
|
||||
.into())
|
||||
}
|
||||
};
|
||||
let completion_status = match completed {
|
||||
false => {
|
||||
@@ -96,15 +208,29 @@ pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<It
|
||||
log::warn!("Task {:?} has an inconsistent content: its STATUS is not completed, yet it has a COMPLETED timestamp at {:?}", uid, completion_date);
|
||||
}
|
||||
CompletionStatus::Uncompleted
|
||||
},
|
||||
}
|
||||
true => CompletionStatus::Completed(completion_date),
|
||||
};
|
||||
let completion_percent = completion_percent.unwrap_or(match completed {
|
||||
false => 0f32,
|
||||
true => 100f32,
|
||||
});
|
||||
|
||||
Item::Task(Task::new_with_parameters(name, uid, item_url, completion_status, sync_status, creation_date, last_modified, ical_prod_id, extra_parameters))
|
||||
},
|
||||
Item::Task(Task::new_with_parameters(
|
||||
name,
|
||||
uid,
|
||||
item_url,
|
||||
completion_status,
|
||||
completion_percent,
|
||||
sync_status,
|
||||
creation_date,
|
||||
last_modified,
|
||||
ical_prod_id,
|
||||
extra_parameters,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// What to do with multiple items?
|
||||
if reader.next().map(|r| r.is_ok()) == Some(true) {
|
||||
return Err("Parsing multiple items are not supported".into());
|
||||
@@ -113,33 +239,38 @@ pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<It
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
fn parse_date(dt: &str) -> Result<DateTime<Utc>, chrono::format::ParseError> {
|
||||
NaiveDate::parse_from_str("20220413", "%Y%m%d")
|
||||
.map(|dt| dt.and_time(NaiveTime::from_hms(0, 0, 0)))
|
||||
.map(|dt| DateTime::from_utc(dt, Utc))
|
||||
}
|
||||
|
||||
fn parse_date_time(dt: &str) -> Result<DateTime<Utc>, chrono::format::ParseError> {
|
||||
Utc.datetime_from_str(dt, "%Y%m%dT%H%M%S")
|
||||
Utc.datetime_from_str(dt, "%Y%m%dT%H%M%SZ")
|
||||
.or_else(|_err| Utc.datetime_from_str(dt, "%Y%m%dT%H%M%S"))
|
||||
.or_else(|_err| parse_date(dt))
|
||||
}
|
||||
|
||||
fn parse_date_time_from_property(value: &Option<String>) -> Option<DateTime<Utc>> {
|
||||
value.as_ref()
|
||||
.and_then(|s| {
|
||||
value.as_ref().and_then(|s| {
|
||||
parse_date_time(s)
|
||||
.map_err(|err| {
|
||||
log::warn!("Invalid timestamp: {}", s);
|
||||
log::warn!("Invalid timestamp: '{}'", s);
|
||||
err
|
||||
})
|
||||
.ok()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
fn extract_ical_prod_id(item: &IcalCalendar) -> Option<&str> {
|
||||
for prop in &item.properties {
|
||||
if &prop.name == "PRODID" {
|
||||
return prop.value.as_ref().map(|s| s.as_str())
|
||||
return prop.value.as_ref().map(|s| s.as_str());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
enum CurrentType<'a> {
|
||||
Event(&'a IcalEvent),
|
||||
Todo(&'a IcalTodo),
|
||||
@@ -169,7 +300,6 @@ fn assert_single_type<'a>(item: &'a IcalCalendar) -> Result<CurrentType<'a>, Box
|
||||
return Err("Only a single TODO or a single EVENT is supported".into());
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
const EXAMPLE_ICAL: &str = r#"BEGIN:VCALENDAR
|
||||
@@ -251,11 +381,17 @@ END:VCALENDAR
|
||||
|
||||
assert_eq!(task.name(), "Do not forget to do this");
|
||||
assert_eq!(task.url(), &item_url);
|
||||
assert_eq!(task.uid(), "0633de27-8c32-42be-bcb8-63bc879c6185@some-domain.com");
|
||||
assert_eq!(
|
||||
task.uid(),
|
||||
"0633de27-8c32-42be-bcb8-63bc879c6185@some-domain.com"
|
||||
);
|
||||
assert_eq!(task.completed(), false);
|
||||
assert_eq!(task.completion_status(), &CompletionStatus::Uncompleted);
|
||||
assert_eq!(task.sync_status(), &sync_status);
|
||||
assert_eq!(task.last_modified(), &Utc.ymd(2021, 03, 21).and_hms(0, 16, 0));
|
||||
assert_eq!(
|
||||
task.last_modified(),
|
||||
&Utc.ymd(2021, 03, 21).and_hms(0, 16, 0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -264,11 +400,19 @@ END:VCALENDAR
|
||||
let sync_status = SyncStatus::Synced(version_tag);
|
||||
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||
|
||||
let item = parse(EXAMPLE_ICAL_COMPLETED, item_url.clone(), sync_status.clone()).unwrap();
|
||||
let item = parse(
|
||||
EXAMPLE_ICAL_COMPLETED,
|
||||
item_url.clone(),
|
||||
sync_status.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let task = item.unwrap_task();
|
||||
|
||||
assert_eq!(task.completed(), true);
|
||||
assert_eq!(task.completion_status(), &CompletionStatus::Completed(Some(Utc.ymd(2021, 04, 02).and_hms(8, 15, 57))));
|
||||
assert_eq!(
|
||||
task.completion_status(),
|
||||
&CompletionStatus::Completed(Some(Utc.ymd(2021, 04, 02).and_hms(8, 15, 57)))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -277,7 +421,12 @@ END:VCALENDAR
|
||||
let sync_status = SyncStatus::Synced(version_tag);
|
||||
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||
|
||||
let item = parse(EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE, item_url.clone(), sync_status.clone()).unwrap();
|
||||
let item = parse(
|
||||
EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE,
|
||||
item_url.clone(),
|
||||
sync_status.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let task = item.unwrap_task();
|
||||
|
||||
assert_eq!(task.completed(), true);
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
//! CalDAV is described as "Calendaring Extensions to WebDAV" in [RFC 4791](https://datatracker.ietf.org/doc/html/rfc4791) and [RFC 7986](https://datatracker.ietf.org/doc/html/rfc7986) and the underlying iCal format is described at least in [RFC 5545](https://datatracker.ietf.org/doc/html/rfc5545). \
|
||||
//! This library has been intensivley tested with Nextcloud servers. It should support Owncloud and iCloud as well, since they use the very same CalDAV protocol.
|
||||
//!
|
||||
//! This initial implementation only supports TODO events. This it can fetch and update a CalDAV-hosted todo-list...just like [sticky notes on a kitchen fridge](https://www.google.com/search?q=kitchen+fridge+todo+list&tbm=isch) would. \
|
||||
//! This initial implementation only supports TODO events. Thus it can fetch and update a CalDAV-hosted todo-list...just like [sticky notes on a kitchen fridge](https://www.google.com/search?q=kitchen+fridge+todo+list&tbm=isch) would. \
|
||||
//! Supporting other items (and especially regular CalDAV calendar events) should be fairly trivial, as it should boil down to adding little logic in iCal files parsing, but any help is appreciated :-)
|
||||
//!
|
||||
//! ## Possible uses
|
||||
|
||||
+125
-48
@@ -6,7 +6,10 @@ use std::error::Error;
|
||||
use std::collections::HashSet;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use url::Url;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::traits::{BaseCalendar, CalDavSource, DavCalendar};
|
||||
use crate::traits::CompleteCalendar;
|
||||
@@ -16,6 +19,30 @@ pub mod sync_progress;
|
||||
use sync_progress::SyncProgress;
|
||||
use sync_progress::{FeedbackSender, SyncEvent};
|
||||
|
||||
/// How many items will be batched in a single HTTP request when downloading from the server
|
||||
#[cfg(not(test))]
|
||||
const DOWNLOAD_BATCH_SIZE: usize = 30;
|
||||
/// How many items will be batched in a single HTTP request when downloading from the server
|
||||
#[cfg(test)]
|
||||
const DOWNLOAD_BATCH_SIZE: usize = 3;
|
||||
|
||||
// I am too lazy to actually make `fetch_and_apply` generic over an async closure.
|
||||
// Let's work around by passing an enum, so that `fetch_and_apply` will know what to do
|
||||
enum BatchDownloadType {
|
||||
RemoteAdditions,
|
||||
RemoteChanges,
|
||||
}
|
||||
|
||||
impl Display for BatchDownloadType {
|
||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::RemoteAdditions => write!(f, "remote additions"),
|
||||
Self::RemoteChanges => write!(f, "remote changes"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A data source that combines two `CalDavSource`s, which is able to sync both sources.
|
||||
///
|
||||
/// Usually, you will only need to use a provider between a server and a local cache, that is to say a [`CalDavProvider`](crate::CalDavProvider), i.e. a `Provider<Cache, CachedCalendar, Client, RemoteCalendar>`. \
|
||||
@@ -159,8 +186,10 @@ where
|
||||
let cal_name = cal_local.name().to_string();
|
||||
|
||||
progress.info(&format!("Syncing calendar {}", cal_name));
|
||||
progress.reset_counter();
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: 0,
|
||||
details: "started".to_string()
|
||||
});
|
||||
|
||||
@@ -176,6 +205,7 @@ where
|
||||
let remote_items = cal_remote.get_item_version_tags().await?;
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: 0,
|
||||
details: format!("{} remote items", remote_items.len()),
|
||||
});
|
||||
|
||||
@@ -271,10 +301,13 @@ where
|
||||
progress.trace("Committing changes...");
|
||||
for url_del in local_del {
|
||||
progress.debug(&format!("> Pushing local deletion {} to the server", url_del));
|
||||
progress.increment_counter(1);
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: progress.counter(),
|
||||
details: Self::item_name(&cal_local, &url_del).await,
|
||||
});
|
||||
|
||||
match cal_remote.delete_item(&url_del).await {
|
||||
Err(err) => {
|
||||
progress.warn(&format!("Unable to delete remote item {}: {}", url_del, err));
|
||||
@@ -290,8 +323,10 @@ where
|
||||
|
||||
for url_del in remote_del {
|
||||
progress.debug(&format!("> Applying remote deletion {} locally", url_del));
|
||||
progress.increment_counter(1);
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: progress.counter(),
|
||||
details: Self::item_name(&cal_local, &url_del).await,
|
||||
});
|
||||
if let Err(err) = cal_local.immediately_delete_item(&url_del).await {
|
||||
@@ -299,61 +334,29 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
for url_add in remote_additions {
|
||||
progress.debug(&format!("> Applying remote addition {} locally", url_add));
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
details: Self::item_name(&cal_local, &url_add).await,
|
||||
});
|
||||
match cal_remote.get_item_by_url(&url_add).await {
|
||||
Err(err) => {
|
||||
progress.warn(&format!("Unable to get remote item {}: {}. Skipping it.", url_add, err));
|
||||
continue;
|
||||
},
|
||||
Ok(item) => match item {
|
||||
None => {
|
||||
progress.error(&format!("Inconsistency: new item {} has vanished from the remote end", url_add));
|
||||
continue;
|
||||
},
|
||||
Some(new_item) => {
|
||||
if let Err(err) = cal_local.add_item(new_item.clone()).await {
|
||||
progress.error(&format!("Not able to add item {} to local calendar: {}", url_add, err));
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Self::apply_remote_additions(
|
||||
remote_additions,
|
||||
&mut *cal_local,
|
||||
&mut *cal_remote,
|
||||
progress,
|
||||
&cal_name
|
||||
).await;
|
||||
|
||||
for url_change in remote_changes {
|
||||
progress.debug(&format!("> Applying remote change {} locally", url_change));
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
details: Self::item_name(&cal_local, &url_change).await,
|
||||
});
|
||||
match cal_remote.get_item_by_url(&url_change).await {
|
||||
Err(err) => {
|
||||
progress.warn(&format!("Unable to get remote item {}: {}. Skipping it", url_change, err));
|
||||
continue;
|
||||
},
|
||||
Ok(item) => match item {
|
||||
None => {
|
||||
progress.error(&format!("Inconsistency: modified item {} has vanished from the remote end", url_change));
|
||||
continue;
|
||||
},
|
||||
Some(item) => {
|
||||
if let Err(err) = cal_local.update_item(item.clone()).await {
|
||||
progress.error(&format!("Unable to update item {} in local calendar: {}", url_change, err));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::apply_remote_changes(
|
||||
remote_changes,
|
||||
&mut *cal_local,
|
||||
&mut *cal_remote,
|
||||
progress,
|
||||
&cal_name
|
||||
).await;
|
||||
|
||||
|
||||
for url_add in local_additions {
|
||||
progress.debug(&format!("> Pushing local addition {} to the server", url_add));
|
||||
progress.increment_counter(1);
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: progress.counter(),
|
||||
details: Self::item_name(&cal_local, &url_add).await,
|
||||
});
|
||||
match cal_local.get_item_by_url_mut(&url_add).await {
|
||||
@@ -375,8 +378,10 @@ where
|
||||
|
||||
for url_change in local_changes {
|
||||
progress.debug(&format!("> Pushing local change {} to the server", url_change));
|
||||
progress.increment_counter(1);
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.clone(),
|
||||
items_done_already: progress.counter(),
|
||||
details: Self::item_name(&cal_local, &url_change).await,
|
||||
});
|
||||
match cal_local.get_item_by_url_mut(&url_change).await {
|
||||
@@ -404,6 +409,78 @@ where
|
||||
cal.get_item_by_url(url).await.map(|item| item.name()).unwrap_or_default().to_string()
|
||||
}
|
||||
|
||||
async fn apply_remote_additions(
|
||||
mut remote_additions: HashSet<Url>,
|
||||
cal_local: &mut T,
|
||||
cal_remote: &mut U,
|
||||
progress: &mut SyncProgress,
|
||||
cal_name: &str
|
||||
) {
|
||||
for batch in remote_additions.drain().chunks(DOWNLOAD_BATCH_SIZE).into_iter() {
|
||||
Self::fetch_batch_and_apply(BatchDownloadType::RemoteAdditions, batch, cal_local, cal_remote, progress, cal_name).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn apply_remote_changes(
|
||||
mut remote_changes: HashSet<Url>,
|
||||
cal_local: &mut T,
|
||||
cal_remote: &mut U,
|
||||
progress: &mut SyncProgress,
|
||||
cal_name: &str
|
||||
) {
|
||||
for batch in remote_changes.drain().chunks(DOWNLOAD_BATCH_SIZE).into_iter() {
|
||||
Self::fetch_batch_and_apply(BatchDownloadType::RemoteChanges, batch, cal_local, cal_remote, progress, cal_name).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_batch_and_apply<I: Iterator<Item = Url>>(
|
||||
batch_type: BatchDownloadType,
|
||||
remote_additions: I,
|
||||
cal_local: &mut T,
|
||||
cal_remote: &mut U,
|
||||
progress: &mut SyncProgress,
|
||||
cal_name: &str
|
||||
) {
|
||||
progress.debug(&format!("> Applying a batch of {} locally", batch_type) /* too bad Chunks does not implement ExactSizeIterator, that could provide useful debug info. See https://github.com/rust-itertools/itertools/issues/171 */);
|
||||
|
||||
let list_of_additions: Vec<Url> = remote_additions.map(|url| url.clone()).collect();
|
||||
match cal_remote.get_items_by_url(&list_of_additions).await {
|
||||
Err(err) => {
|
||||
progress.warn(&format!("Unable to get the batch of {} {:?}: {}. Skipping them.", batch_type, list_of_additions, err));
|
||||
},
|
||||
Ok(items) => {
|
||||
for item in items {
|
||||
match item {
|
||||
None => {
|
||||
progress.error(&format!("Inconsistency: an item from the batch has vanished from the remote end"));
|
||||
continue;
|
||||
},
|
||||
Some(new_item) => {
|
||||
let local_update_result = match batch_type {
|
||||
BatchDownloadType::RemoteAdditions => cal_local.add_item(new_item.clone()).await,
|
||||
BatchDownloadType::RemoteChanges => cal_local.update_item(new_item.clone()).await,
|
||||
};
|
||||
if let Err(err) = local_update_result {
|
||||
progress.error(&format!("Not able to add item {} to local calendar: {}", new_item.url(), err));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Notifying every item at the same time would not make sense. Let's notify only one of them
|
||||
let one_item_name = match list_of_additions.get(0) {
|
||||
Some(url) => Self::item_name(&cal_local, &url).await,
|
||||
None => String::from("<unable to get the name of the first batched item>"),
|
||||
};
|
||||
progress.increment_counter(list_of_additions.len());
|
||||
progress.feedback(SyncEvent::InProgress{
|
||||
calendar: cal_name.to_string(),
|
||||
items_done_already: progress.counter(),
|
||||
details: one_item_name,
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ pub enum SyncEvent {
|
||||
/// Sync has just started but no calendar is handled yet
|
||||
Started,
|
||||
/// Sync is in progress.
|
||||
InProgress{ calendar: String, details: String},
|
||||
InProgress{ calendar: String, items_done_already: usize, details: String},
|
||||
/// Sync is finished
|
||||
Finished{ success: bool },
|
||||
}
|
||||
@@ -20,7 +20,7 @@ impl Display for SyncEvent {
|
||||
match self {
|
||||
SyncEvent::NotStarted => write!(f, "Not started"),
|
||||
SyncEvent::Started => write!(f, "Sync has started..."),
|
||||
SyncEvent::InProgress{calendar, details} => write!(f, "[{}] {}...", calendar, details),
|
||||
SyncEvent::InProgress{calendar, items_done_already, details} => write!(f, "{} [{}/?] {}...", calendar, items_done_already, details),
|
||||
SyncEvent::Finished{success} => match success {
|
||||
true => write!(f, "Sync successfully finished"),
|
||||
false => write!(f, "Sync finished with errors"),
|
||||
@@ -53,16 +53,33 @@ pub fn feedback_channel() -> (FeedbackSender, FeedbackReceiver) {
|
||||
/// A structure that tracks the progression and the errors that happen during a sync
|
||||
pub struct SyncProgress {
|
||||
n_errors: u32,
|
||||
feedback_channel: Option<FeedbackSender>
|
||||
feedback_channel: Option<FeedbackSender>,
|
||||
counter: usize,
|
||||
}
|
||||
impl SyncProgress {
|
||||
pub fn new() -> Self {
|
||||
Self { n_errors: 0, feedback_channel: None }
|
||||
Self { n_errors: 0, feedback_channel: None, counter: 0 }
|
||||
}
|
||||
pub fn new_with_feedback_channel(channel: FeedbackSender) -> Self {
|
||||
Self { n_errors: 0, feedback_channel: Some(channel) }
|
||||
Self { n_errors: 0, feedback_channel: Some(channel), counter: 0 }
|
||||
}
|
||||
|
||||
/// Reset the user-info counter
|
||||
pub fn reset_counter(&mut self) {
|
||||
self.counter = 0;
|
||||
}
|
||||
/// Increments the user-info counter.
|
||||
pub fn increment_counter(&mut self, increment: usize) {
|
||||
self.counter += increment;
|
||||
}
|
||||
/// Retrieves the current user-info counter.
|
||||
/// This counts "arbitrary things", that's provided as a convenience but it is not used internally
|
||||
/// (e.g. that can be used to keep track of the items handled for the current calendar)
|
||||
pub fn counter(&self) -> usize {
|
||||
self.counter
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub fn is_success(&self) -> bool {
|
||||
self.n_errors == 0
|
||||
|
||||
+69
-24
@@ -1,10 +1,10 @@
|
||||
//! To-do tasks (iCal `VTODO` item)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use chrono::{DateTime, Utc};
|
||||
use ical::property::Property;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::item::SyncStatus;
|
||||
use crate::utils::random_url;
|
||||
@@ -49,11 +49,11 @@ pub struct Task {
|
||||
last_modified: DateTime<Utc>,
|
||||
/// The completion status of this task
|
||||
completion_status: CompletionStatus,
|
||||
completion_percent: f32,
|
||||
|
||||
/// The display name of the task
|
||||
name: String,
|
||||
|
||||
|
||||
/// The PRODID, as defined in iCal files
|
||||
ical_prod_id: String,
|
||||
|
||||
@@ -62,7 +62,6 @@ pub struct Task {
|
||||
extra_parameters: Vec<Property>,
|
||||
}
|
||||
|
||||
|
||||
impl Task {
|
||||
/// Create a brand new Task that is not on a server yet.
|
||||
/// This will pick a new (random) task ID.
|
||||
@@ -74,24 +73,45 @@ impl Task {
|
||||
let new_last_modified = Utc::now();
|
||||
let new_completion_status = if completed {
|
||||
CompletionStatus::Completed(Some(Utc::now()))
|
||||
} else { CompletionStatus::Uncompleted };
|
||||
} else {
|
||||
CompletionStatus::Uncompleted
|
||||
};
|
||||
let new_completion_percent = if completed { 100f32 } else { 0f32 };
|
||||
let ical_prod_id = crate::ical::default_prod_id();
|
||||
let extra_parameters = Vec::new();
|
||||
Self::new_with_parameters(name, new_uid, new_url, new_completion_status, new_sync_status, new_creation_date, new_last_modified, ical_prod_id, extra_parameters)
|
||||
Self::new_with_parameters(
|
||||
name,
|
||||
new_uid,
|
||||
new_url,
|
||||
new_completion_status,
|
||||
new_completion_percent,
|
||||
new_sync_status,
|
||||
new_creation_date,
|
||||
new_last_modified,
|
||||
ical_prod_id,
|
||||
extra_parameters,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a new Task instance, that may be synced on the server already
|
||||
pub fn new_with_parameters(name: String, uid: String, new_url: Url,
|
||||
pub fn new_with_parameters(
|
||||
name: String,
|
||||
uid: String,
|
||||
new_url: Url,
|
||||
completion_status: CompletionStatus,
|
||||
sync_status: SyncStatus, creation_date: Option<DateTime<Utc>>, last_modified: DateTime<Utc>,
|
||||
ical_prod_id: String, extra_parameters: Vec<Property>,
|
||||
) -> Self
|
||||
{
|
||||
completion_percent: f32,
|
||||
sync_status: SyncStatus,
|
||||
creation_date: Option<DateTime<Utc>>,
|
||||
last_modified: DateTime<Utc>,
|
||||
ical_prod_id: String,
|
||||
extra_parameters: Vec<Property>,
|
||||
) -> Self {
|
||||
Self {
|
||||
url: new_url,
|
||||
uid,
|
||||
name,
|
||||
completion_status,
|
||||
completion_percent,
|
||||
sync_status,
|
||||
creation_date,
|
||||
last_modified,
|
||||
@@ -100,16 +120,39 @@ impl Task {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(&self) -> &Url { &self.url }
|
||||
pub fn uid(&self) -> &str { &self.uid }
|
||||
pub fn name(&self) -> &str { &self.name }
|
||||
pub fn completed(&self) -> bool { self.completion_status.is_completed() }
|
||||
pub fn ical_prod_id(&self) -> &str { &self.ical_prod_id }
|
||||
pub fn sync_status(&self) -> &SyncStatus { &self.sync_status }
|
||||
pub fn last_modified(&self) -> &DateTime<Utc> { &self.last_modified }
|
||||
pub fn creation_date(&self) -> Option<&DateTime<Utc>> { self.creation_date.as_ref() }
|
||||
pub fn completion_status(&self) -> &CompletionStatus { &self.completion_status }
|
||||
pub fn extra_parameters(&self) -> &[Property] { &self.extra_parameters }
|
||||
pub fn url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
pub fn uid(&self) -> &str {
|
||||
&self.uid
|
||||
}
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
pub fn completed(&self) -> bool {
|
||||
self.completion_status.is_completed()
|
||||
}
|
||||
pub fn completion_percent(&self) -> f32 {
|
||||
self.completion_percent
|
||||
}
|
||||
pub fn ical_prod_id(&self) -> &str {
|
||||
&self.ical_prod_id
|
||||
}
|
||||
pub fn sync_status(&self) -> &SyncStatus {
|
||||
&self.sync_status
|
||||
}
|
||||
pub fn last_modified(&self) -> &DateTime<Utc> {
|
||||
&self.last_modified
|
||||
}
|
||||
pub fn creation_date(&self) -> Option<&DateTime<Utc>> {
|
||||
self.creation_date.as_ref()
|
||||
}
|
||||
pub fn completion_status(&self) -> &CompletionStatus {
|
||||
&self.completion_status
|
||||
}
|
||||
pub fn extra_parameters(&self) -> &[Property] {
|
||||
&self.extra_parameters
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "integration_tests"))]
|
||||
pub fn has_same_observable_content_as(&self, other: &Task) -> bool {
|
||||
@@ -137,7 +180,7 @@ impl Task {
|
||||
SyncStatus::LocallyDeleted(_) => {
|
||||
log::warn!("Trying to update an item that has previously been deleted. These changes will probably be ignored at next sync.");
|
||||
return;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +188,6 @@ impl Task {
|
||||
self.last_modified = Utc::now();
|
||||
}
|
||||
|
||||
|
||||
/// Rename a task.
|
||||
/// This updates its "last modified" field
|
||||
pub fn set_name(&mut self, new_name: String) {
|
||||
@@ -169,7 +211,10 @@ impl Task {
|
||||
}
|
||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||
/// Set the completion status, but forces a "master" SyncStatus, just like CalDAV servers are always "masters"
|
||||
pub fn mock_remote_calendar_set_completion_status(&mut self, new_completion_status: CompletionStatus) {
|
||||
pub fn mock_remote_calendar_set_completion_status(
|
||||
&mut self,
|
||||
new_completion_status: CompletionStatus,
|
||||
) {
|
||||
self.sync_status = SyncStatus::random_synced();
|
||||
self.completion_status = new_completion_status;
|
||||
}
|
||||
|
||||
@@ -83,6 +83,10 @@ pub trait DavCalendar : BaseCalendar {
|
||||
/// Returns a particular item
|
||||
async fn get_item_by_url(&self, url: &Url) -> Result<Option<Item>, Box<dyn Error>>;
|
||||
|
||||
/// Returns a set of items.
|
||||
/// This is usually faster than calling multiple consecutive [`DavCalendar::get_item_by_url`], since it only issues one HTTP request.
|
||||
async fn get_items_by_url(&self, urls: &[Url]) -> Result<Vec<Option<Item>>, Box<dyn Error>>;
|
||||
|
||||
/// Delete an item
|
||||
async fn delete_item(&mut self, item_url: &Url) -> Result<(), Box<dyn Error>>;
|
||||
|
||||
@@ -115,6 +119,9 @@ pub trait CompleteCalendar : BaseCalendar {
|
||||
/// Returns all items that this calendar contains
|
||||
async fn get_items(&self) -> Result<HashMap<Url, &Item>, Box<dyn Error>>;
|
||||
|
||||
/// Returns all items that this calendar contains
|
||||
async fn get_items_mut(&mut self) -> Result<HashMap<Url, &mut Item>, Box<dyn Error>>;
|
||||
|
||||
/// Returns a particular item
|
||||
async fn get_item_by_url<'a>(&'a self, url: &Url) -> Option<&'a Item>;
|
||||
|
||||
|
||||
+31
-15
@@ -1,17 +1,17 @@
|
||||
///! Some utility functions
|
||||
//! Some utility functions
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::hash::Hash;
|
||||
use std::io::{stdin, stdout, Read, Write};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use minidom::Element;
|
||||
use url::Url;
|
||||
|
||||
use crate::item::SyncStatus;
|
||||
use crate::traits::CompleteCalendar;
|
||||
use crate::traits::DavCalendar;
|
||||
use crate::Item;
|
||||
use crate::item::SyncStatus;
|
||||
|
||||
/// Walks an XML tree and returns every element that has the given name
|
||||
pub fn find_elems<S: AsRef<str>>(root: &Element, searched_name: S) -> Vec<&Element> {
|
||||
@@ -49,14 +49,10 @@ pub fn find_elem<S: AsRef<str>>(root: &Element, searched_name: S) -> Option<&Ele
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
pub fn print_xml(element: &Element) {
|
||||
let mut writer = std::io::stdout();
|
||||
|
||||
let mut xml_writer = minidom::quick_xml::Writer::new_with_indent(
|
||||
std::io::stdout(),
|
||||
0x20, 4
|
||||
);
|
||||
let mut xml_writer = minidom::quick_xml::Writer::new_with_indent(std::io::stdout(), 0x20, 4);
|
||||
let _ = element.to_writer(&mut xml_writer);
|
||||
let _ = writer.write(&[0x0a]);
|
||||
}
|
||||
@@ -74,7 +70,7 @@ where
|
||||
for (_, item) in map {
|
||||
print_task(item);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +88,7 @@ where
|
||||
for (url, version_tag) in map {
|
||||
println!(" * {} (version {:?})", url, version_tag);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,7 +96,14 @@ where
|
||||
pub fn print_task(item: &Item) {
|
||||
match item {
|
||||
Item::Task(task) => {
|
||||
let completion = if task.completed() { "✓" } else { " " };
|
||||
let completion = if task.completed() {
|
||||
String::from("✓")
|
||||
} else if task.completion_percent() > 0f32 {
|
||||
format!("{}%", task.completion_percent())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let completion = format!("{completion:>width$}", completion = completion, width = 4);
|
||||
let sync = match task.sync_status() {
|
||||
SyncStatus::NotSynced => ".",
|
||||
SyncStatus::Synced(_) => "=",
|
||||
@@ -108,12 +111,27 @@ pub fn print_task(item: &Item) {
|
||||
SyncStatus::LocallyDeleted(_) => "x",
|
||||
};
|
||||
println!(" {}{} {}\t{}", completion, sync, task.name(), task.url());
|
||||
},
|
||||
}
|
||||
Item::Event(event) => {
|
||||
let sync = match event.sync_status() {
|
||||
SyncStatus::NotSynced => ".",
|
||||
SyncStatus::Synced(_) => "=",
|
||||
SyncStatus::LocallyModified(_) => "~",
|
||||
SyncStatus::LocallyDeleted(_) => "x",
|
||||
};
|
||||
println!(
|
||||
" {}-{}{} {}\t{}",
|
||||
event.start_date(),
|
||||
event.end_date(),
|
||||
sync,
|
||||
event.name(),
|
||||
event.url()
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Compare keys of two hashmaps for equality
|
||||
pub fn keys_are_the_same<T, U, V>(left: &HashMap<T, U>, right: &HashMap<T, V>) -> bool
|
||||
where
|
||||
@@ -140,7 +158,6 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
/// Wait for the user to press enter
|
||||
pub fn pause() {
|
||||
let mut stdout = stdout();
|
||||
@@ -149,7 +166,6 @@ pub fn pause() {
|
||||
stdin().read_exact(&mut [0]).unwrap();
|
||||
}
|
||||
|
||||
|
||||
/// Generate a random URL with a given prefix
|
||||
pub fn random_url(parent_calendar: &Url) -> Url {
|
||||
let random = uuid::Uuid::new_v4().to_hyphenated().to_string();
|
||||
|
||||
+2
-2
@@ -694,13 +694,13 @@ async fn get_or_insert_calendar(source: &mut Cache, url: &Url)
|
||||
None => {
|
||||
let new_name = format!("Test calendar for URL {}", url);
|
||||
let supported_components = SupportedComponents::TODO;
|
||||
let color = csscolorparser::parse("#ff8000"); // TODO: we should rather have specific colors, depending on the calendars
|
||||
let color = csscolorparser::parse("#ff8000").unwrap(); // TODO: we should rather have specific colors, depending on the calendars
|
||||
|
||||
source.create_calendar(
|
||||
url.clone(),
|
||||
new_name.to_string(),
|
||||
supported_components,
|
||||
None,
|
||||
Some(color),
|
||||
).await
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ impl TestFlavour {
|
||||
scenarii: scenarii::scenarii_basic(),
|
||||
mock_behaviour: Arc::new(Mutex::new(MockBehaviour{
|
||||
add_item_behaviour: (2,3),
|
||||
get_item_by_url_behaviour: (1,4),
|
||||
get_item_by_url_behaviour: (1,12),
|
||||
..MockBehaviour::default()
|
||||
})),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user