diff --git a/Cargo.lock b/Cargo.lock index 44d34c9..1c3e0d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -31,9 +31,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -43,18 +43,18 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" @@ -73,65 +73,67 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.79" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "ashpd" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c018490e423efb6f032ef575f873ea57b61d44bec763cfe027b8e8852a027cf" +checksum = "4ac22eda5891cc086690cb6fa10121c0390de0e3b04eb269f2d766b00d3f2d81" dependencies = [ - "async-std", + "async-fs 2.1.2", + "async-net", "enumflags2", "futures-channel", "futures-util", @@ -155,23 +157,11 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 4.0.2", "event-listener-strategy", "futures-core", "pin-project-lite", @@ -179,15 +169,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.8.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ - "async-lock 3.2.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", - "futures-lite 2.1.0", + "fastrand 2.2.0", + "futures-lite 2.5.0", "slab", ] @@ -204,18 +193,14 @@ dependencies = [ ] [[package]] -name = "async-global-executor" -version = "2.4.1" +name = "async-fs" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ - "async-channel 2.1.1", - "async-executor", - "async-io 2.2.2", - "async-lock 3.2.0", + "async-lock 3.4.0", "blocking", - "futures-lite 2.1.0", - "once_cell", + "futures-lite 2.5.0", ] [[package]] @@ -240,21 +225,21 @@ dependencies = [ [[package]] name = "async-io" -version = "2.2.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6afaa937395a620e33dc6a742c593c01aced20aa376ffb0f628121198578ccc7" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ - "async-lock 3.2.0", + "async-lock 3.4.0", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.5.0", "parking", - "polling 3.3.1", - "rustix 0.38.28", + "polling 3.7.4", + "rustix 0.38.40", "slab", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -268,15 +253,26 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.2.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7125e42787d53db9dd54261812ef17e937c95a51e4d291373b670342fa44310c" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.2", + "event-listener 5.3.1", "event-listener-strategy", "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io 2.4.0", + "blocking", + "futures-lite 2.5.0", +] + [[package]] name = "async-process" version = "1.8.1" @@ -290,80 +286,54 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.28", + "rustix 0.38.40", "windows-sys 0.48.0", ] [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "async-signal" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" +checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ - "async-io 2.2.2", - "async-lock 2.8.0", + "async-io 2.4.0", + "async-lock 3.4.0", "atomic-waker", "cfg-if", "futures-core", "futures-io", - "rustix 0.38.28", + "rustix 0.38.40", "signal-hook-registry", "slab", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-std" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 1.13.0", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", + "windows-sys 0.59.0", ] [[package]] name = "async-task" -version = "4.6.0" +version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d90cd0b264dfdd8eb5bad0a2c217c1f88fa96a8573f40e7b12de23fb468f46" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -374,30 +344,30 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.21.5" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" @@ -437,25 +407,22 @@ dependencies = [ [[package]] name = "blocking" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.1.1", - "async-lock 3.2.0", + "async-channel", "async-task", - "fastrand 2.0.1", "futures-io", - "futures-lite 2.1.0", + "futures-lite 2.5.0", "piper", - "tracing", ] [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -465,15 +432,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cairo-rs" -version = "0.20.1" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a0ea147c94108c9613235388f540e4d14c327f7081c9e471fc8ee8a2533e69" +checksum = "d7fa699e1d7ae691001a811dda5ef0e3e42e1d4119b26426352989df9e94e3e6" dependencies = [ "bitflags 2.6.0", "cairo-sys-rs", @@ -492,51 +459,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "capnp" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95e65021d89250bbfe7c2791789ced2c4bdc21b0e8bb59c64f3fd6145a5fd678" - -[[package]] -name = "capnp" -version = "0.18.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e78424967d6d4c8e295a3b5114ae6d849052e9dbbed3e62589a76acda54e3f0" -dependencies = [ - "embedded-io", -] - -[[package]] -name = "capnp-futures" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8697b857f5b014ff378f02817426d3b6fb90a69f32e330fe010f24fe10cf8f1" -dependencies = [ - "capnp 0.18.10", - "futures", -] - -[[package]] -name = "capnp-rpc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b87a9e0f74600628e227d39b79ef8652c558a408999ac46ba22b19dbad0010" -dependencies = [ - "capnp 0.18.10", - "capnp-futures", - "futures", -] - -[[package]] -name = "capnpc" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbc3763fb3e6635188e9cc51ee11a26f8777c553ca377430818dbebaaf6042b" -dependencies = [ - "capnp 0.17.2", -] - [[package]] name = "cbc" version = "0.1.2" @@ -548,11 +470,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ - "libc", + "shlex", ] [[package]] @@ -571,18 +493,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -598,9 +526,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.12" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -608,9 +536,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.12" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -620,33 +548,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck 0.4.1", + "heck", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -663,36 +591,33 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -728,25 +653,30 @@ dependencies = [ ] [[package]] -name = "embedded-io" -version = "0.5.0" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bbadc628dc286b9ae02f0cb0f5411c056eb7487b72f0083203f115de94060" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "enumflags2" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" +checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", "serde", @@ -754,13 +684,13 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" +checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -771,9 +701,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -798,9 +728,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "4.0.2" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "218a870470cce1469024e9fb66b901aa983929d81304a1cdb299f28118e550d5" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -809,11 +739,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 4.0.2", + "event-listener 5.3.1", "pin-project-lite", ] @@ -840,9 +770,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "field-offset" @@ -850,15 +780,15 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset 0.9.0", + "memoffset 0.9.1", "rustc_version", ] [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -896,9 +826,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -911,9 +841,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -921,15 +851,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -938,9 +868,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -959,11 +889,11 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.1.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ - "fastrand 2.0.1", + "fastrand 2.2.0", "futures-core", "futures-io", "parking", @@ -972,32 +902,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1038,9 +968,9 @@ dependencies = [ [[package]] name = "gdk4" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c121aeeb0cf7545877ae615dac6bfd088b739d8abee4d55e7143b06927d16a31" +checksum = "75933c4a86e8a2428814d367e22c733304fdfabc87f415750fd2f55409b6ee48" dependencies = [ "cairo-rs", "gdk-pixbuf", @@ -1053,9 +983,9 @@ dependencies = [ [[package]] name = "gdk4-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3c03d1ea9d5199f14f060890fde68a3b5ec5699144773d1fa6abf337bfbc9c" +checksum = "20af0656d543aed3e57ac4120ef76d091c3c42ab1e0507a8febde7cd005640e2" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1068,15 +998,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "generational-arena" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e94aff08e743b651baaea359664321055749b398adff8740a7399af7796e7" -dependencies = [ - "cfg-if", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1089,20 +1010,22 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] name = "gettext-rs" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e49ea8a8fad198aaa1f9655a2524b64b70eb06b2f3ff37da407566c93054f364" +checksum = "a44e92f7dc08430aca7ed55de161253a22276dfd69c5526e5c5e95d1f7cf338a" dependencies = [ "gettext-sys", "locale_config", @@ -1110,9 +1033,9 @@ dependencies = [ [[package]] name = "gettext-sys" -version = "0.21.3" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c63ce2e00f56a206778276704bbe38564c8695249fdc8f354b4ef71c57c3839d" +checksum = "bb45773f5b8945f12aecd04558f545964f943dacda1b1155b3d738f5469ef661" dependencies = [ "cc", "temp-dir", @@ -1120,15 +1043,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d999e8fb09583e96080867e364bc1e701284ad206c76a5af480d63833ad43c" +checksum = "8826d2a9ad56ce3de1f04bea0bea0daff6f5f1c913cc834996cfea1f9401361c" dependencies = [ "futures-channel", "futures-core", @@ -1143,9 +1066,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7efc368de04755344f0084104835b6bb71df2c1d41e37d863947392a894779" +checksum = "b965df6f3534c84816b5c1a7d9efcb5671ae790822de5abe8e299797039529bc" dependencies = [ "glib-sys", "gobject-sys", @@ -1156,9 +1079,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf1ec6d3650bf9fdbc6cee242d4fcebc6f6bfd9bea5b929b6a8b7344eb85ff" +checksum = "86bd3e4ee7998ab5a135d900db56930cc19ad16681adf245daff54f618b9d5e1" dependencies = [ "bitflags 2.6.0", "futures-channel", @@ -1177,39 +1100,27 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6bf88f70cd5720a6197639dcabcb378dd528d0cb68cb1f45e3b358bcb841cd7" +checksum = "e7d21ca27acfc3e91da70456edde144b4ac7c36f78ee77b10189b3eb4901c156" dependencies = [ - "heck 0.5.0", - "proc-macro-crate 3.1.0", + "heck", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "glib-sys" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9eca5d88cfa6a453b00d203287c34a2b7cac3a7831779aa2bb0b3c7233752b" +checksum = "3d0b1827e8621fc42c0dfb228e5d57ff6a71f9699e666ece8113f979ad87c2de" dependencies = [ "libc", "system-deps", ] -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "gobject-sys" version = "0.20.4" @@ -1246,9 +1157,9 @@ dependencies = [ [[package]] name = "gsk4" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa21a2f7c51ee1c6cc1242c2faf3aae2b7566138f182696759987bde8219e922" +checksum = "b36933c1e79df378aa6e606576e680358a9582ed8c16f33e94899636e6fa6df6" dependencies = [ "cairo-rs", "gdk4", @@ -1261,9 +1172,9 @@ dependencies = [ [[package]] name = "gsk4-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9fb607554f9f4e8829eb7ea301b0fde051e1dbfd5d16b143a8a9c2fac6c01b" +checksum = "0877a9d485bd9ba5262b0c9bce39e63750e525e3aebeb359d271ca1f0e111f1d" dependencies = [ "cairo-sys-rs", "gdk4-sys", @@ -1277,9 +1188,9 @@ dependencies = [ [[package]] name = "gtk4" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e2d105ce672f5cdcb5af2602e91c2901e91c72da15ab76f613ad57ecf04c6d" +checksum = "9376d14d7e33486c54823a42bef296e882b9f25cb4c52b52f4d1d57bbadb5b6d" dependencies = [ "cairo-rs", "field-offset", @@ -1298,21 +1209,21 @@ dependencies = [ [[package]] name = "gtk4-macros" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e7b362c8fccd2712297903717d65d30defdab2b509bc9d209cbe5ffb9fabaf" +checksum = "a7c518d5dd41c57385c7cd30af52e261820c897fc1144e558bb88c303d048ae2" dependencies = [ - "proc-macro-crate 3.1.0", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "gtk4-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbe4325908b1c1642dbb48e9f49c07a73185babf43e8b2065b0f881a589f55b8" +checksum = "e653b0a9001ba9be1ffddb9373bfe9a111f688222f5aeee2841481300d91b55a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -1329,15 +1240,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.22" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -1348,40 +1259,46 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + [[package]] name = "hashlink" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "heck" -version = "0.5.0" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -1409,9 +1326,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -1420,83 +1337,111 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", - "pin-project-lite", ] [[package]] -name = "httparse" -version = "1.8.0" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] [[package]] -name = "httpdate" -version = "1.0.3" +name = "httparse" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "0.14.28" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", "h2", "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", + "hyper-util", "rustls", + "rustls-native-certs", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", ] [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", + "http-body-util", "hyper", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.59" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1516,99 +1461,225 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "indexmap" -version = "2.1.0" +name = "icu_locid" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ - "equivalent", - "hashbrown", + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", ] [[package]] -name = "inout" -version = "0.1.3" +name = "icu_locid_transform" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ - "block-padding", - "generic-array", + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", ] [[package]] -name = "instant" -version = "0.1.12" +name = "icu_locid_transform_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "icu_normalizer" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", ] [[package]] -name = "ipnet" -version = "2.9.0" +name = "icu_normalizer_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] -name = "itoa" -version = "1.0.10" +name = "icu_properties" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] [[package]] -name = "js-sys" -version = "0.3.66" +name = "icu_properties_data" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ - "wasm-bindgen", + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" +name = "icu_provider_macros" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "log", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.1", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] name = "libadwaita" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff9c222b5c783729de45185f07b2fec2d43a7f9c63961e777d3667e20443878" +checksum = "8611ee9fb85e7606c362b513afcaf5b59853f79e4d98caaaf581d99465014247" dependencies = [ "gdk4", "gio", @@ -1621,9 +1692,9 @@ dependencies = [ [[package]] name = "libadwaita-sys" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c44d8bdbad31d6639e1f20cc9c1424f1a8e02d751fc28d44659bf743fb9eca6" +checksum = "b099a223560118d4d4fa04b6d23f3ea5b7171fe1d83dfb7e6b45b54cdfc83af9" dependencies = [ "gdk4-sys", "gio-sys", @@ -1637,15 +1708,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" @@ -1665,9 +1736,15 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "locale_config" @@ -1684,9 +1761,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1694,12 +1771,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -dependencies = [ - "value-bag", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "malloc_buf" @@ -1727,9 +1801,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -1742,31 +1816,31 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.10" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1796,9 +1870,7 @@ version = "0.1.6" dependencies = [ "anyhow", "ashpd", - "async-channel 2.1.1", - "capnp 0.18.10", - "capnp-rpc", + "async-channel", "chrono", "futures", "gettext-rs", @@ -1821,12 +1893,11 @@ name = "ntfy-daemon" version = "0.1.0" dependencies = [ "anyhow", - "capnp 0.18.10", - "capnp-rpc", - "capnpc", + "async-channel", + "async-trait", "clap", "futures", - "generational-arena", + "http", "oo7", "rand", "regex", @@ -1834,7 +1905,7 @@ dependencies = [ "rusqlite", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "tokio", "tokio-stream", "tokio-util", @@ -1854,9 +1925,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -1868,11 +1939,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -1897,28 +1967,27 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1927,11 +1996,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -1939,9 +2007,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1977,32 +2045,35 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "oo7" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "220729ba847d98e1a9902c05e41dae79ce4a0b913dad68bc540dd3120a8c2b6b" +checksum = "aceca83a983f36dd8ee90230636fbf92897cb2dc2701d2ae66f885e20e56978d" dependencies = [ "aes", - "async-global-executor", - "async-std", + "async-fs 2.1.2", + "async-io 2.4.0", + "async-lock 3.4.0", + "blocking", "byteorder", "cbc", "cipher", "digest", + "futures-lite 2.5.0", "futures-util", "hkdf", "hmac", @@ -2019,9 +2090,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.62" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -2040,7 +2111,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2051,9 +2122,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.98" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -2079,9 +2150,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pango" -version = "0.20.4" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa26aa54b11094d72141a754901cd71d9356432bb8147f9cace8d9c7ba95f356" +checksum = "71e34e7ca2c52e3933d7e5251409a82b83725fa9d6d48fbdaacec056b3a0554a" dependencies = [ "gio", "glib", @@ -2103,15 +2174,15 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -2119,15 +2190,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2148,9 +2219,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -2160,12 +2231,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand 2.2.0", "futures-io", ] @@ -2193,23 +2264,27 @@ dependencies = [ [[package]] name = "polling" -version = "3.3.1" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf63fa624ab313c11656b4cda960bfc46c410187ad493c41f6ba2d8c1e991c9e" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.28", + "rustix 0.38.40", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" @@ -2223,27 +2298,79 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit 0.22.22", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.7", + "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.3", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2280,18 +2407,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -2301,9 +2428,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2312,9 +2439,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relm4-macros" @@ -2324,14 +2451,14 @@ checksum = "9340e2553c0a184a80a0bfa1dcf73c47f3d48933aa6be90724b202f9fbd24735" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "reqwest" -version = "0.11.23" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64", "bytes", @@ -2341,9 +2468,11 @@ dependencies = [ "h2", "http", "http-body", + "http-body-util", "hyper", "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -2352,12 +2481,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-native-certs", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -2369,21 +2501,22 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "winreg", + "windows-registry", ] [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2402,15 +2535,21 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -2431,73 +2570,87 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.12", + "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "log", + "once_cell", "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", "rustls-pemfile", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", ] [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2506,23 +2659,13 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2531,9 +2674,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -2541,57 +2684,58 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.194" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.194" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "serde_json" -version = "1.0.110" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fbd975230bada99c8bb618e0c365c2eefa219158d5c6c29610fd09ff1833257" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2639,11 +2783,17 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -2659,9 +2809,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -2675,19 +2825,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "sourceview5" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905c83b38d4aff1800a12adba65b083deba61b4d948f62fc2ff7ad7d77656d05" +checksum = "f0e07d99b15f12767aa1c84870c45667f42bf24fd6a989dc70088e32854ef56e" dependencies = [ "futures-channel", "futures-core", @@ -2720,15 +2870,15 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" @@ -2738,15 +2888,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2761,31 +2911,51 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "system-configuration" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", @@ -2798,7 +2968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr", - "heck 0.5.0", + "heck", "pkg-config", "toml", "version-compare", @@ -2812,58 +2982,88 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "temp-dir" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" +checksum = "bc1ee6eef34f12f765cb94725905c6312b6610ab2b0940889cfe58dae7bc3c72" [[package]] name = "tempfile" -version = "3.9.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.0.1", - "redox_syscall", - "rustix 0.38.28", - "windows-sys 0.52.0", + "fastrand 2.2.0", + "once_cell", + "rustix 0.38.40", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "1.0.56" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2876,9 +3076,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -2886,20 +3086,20 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.7", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -2914,30 +3114,32 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2945,26 +3147,25 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.8.2" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -2977,38 +3178,27 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3029,7 +3219,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -3085,31 +3275,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ - "memoffset 0.9.0", + "memoffset 0.9.1", "tempfile", "winapi", ] -[[package]] -name = "unicode-bidi" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" - [[package]] name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "untrusted" @@ -3119,25 +3294,25 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.1" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ "base64", "flate2", "log", "once_cell", "rustls", - "rustls-webpki", + "rustls-pki-types", "url", "webpki-roots", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" dependencies = [ "form_urlencoded", "idna", @@ -3145,11 +3320,23 @@ dependencies = [ "serde", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -3157,12 +3344,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "value-bag" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72e1902dde2bd6441347de2b70b7f5d59bf157c6c62f0c44572607a1d55bbe" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3177,15 +3358,15 @@ checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "waker-fn" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" [[package]] name = "want" @@ -3204,34 +3385,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -3241,9 +3423,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3251,28 +3433,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "wasm-streams" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -3283,9 +3465,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3293,9 +3485,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "winapi" @@ -3325,7 +3520,37 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -3343,7 +3568,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3363,17 +3597,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3384,9 +3619,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3396,9 +3631,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3408,9 +3643,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3420,9 +3661,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3432,9 +3673,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3444,9 +3685,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3456,48 +3697,83 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.31" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "memchr", ] [[package]] -name = "xdg-home" +name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ - "nix", - "winapi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", ] [[package]] name = "zbus" -version = "3.14.1" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" +checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" dependencies = [ "async-broadcast", "async-executor", - "async-fs", + "async-fs 1.6.0", "async-io 1.13.0", "async-lock 2.8.0", "async-process", @@ -3532,9 +3808,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "3.14.1" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d" +checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", @@ -3546,9 +3822,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb80bb776dbda6e23d705cf0123c3b95df99c4ebeaec6c2599d4a5419902b4a9" +checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" dependencies = [ "serde", "static_assertions", @@ -3557,29 +3833,51 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "synstructure", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -3592,14 +3890,36 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] name = "zvariant" -version = "3.15.0" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44b291bee0d960c53170780af148dca5fa260a63cdd24f1962fa82e03e53338c" +checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" dependencies = [ "byteorder", "enumflags2", @@ -3612,9 +3932,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "3.15.0" +version = "3.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd" +checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index 069b25a..c12752e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,6 @@ tracing-subscriber = "0.3" adw = { version = "0.7", package = "libadwaita", features = ["v1_6"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -capnp = "0.18.0" -capnp-rpc = "0.18.0" anyhow = "1.0.71" chrono = "0.4.26" rand = "0.8.5" diff --git a/README.md b/README.md index a5ec575..562b323 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ https://ntfy.sh client application to receive everyday's notifications. ## Architecture The code is split between the GUI and the underlying ntfy-daemon. +![](./architecture.svg) ## How to run Use gnome-builder to clone and run the project. Note: after clicking the "run" diff --git a/architecture.svg b/architecture.svg new file mode 100644 index 0000000..7af2a84 --- /dev/null +++ b/architecture.svg @@ -0,0 +1,12 @@ +ntfy_daemonntfy_daemonNtfyActorNtfyActorAppAppSubscriptionActorSubscriptionActorSubscription  +(view model)Subscription  +(view model)ListenerActorListenerActorListenerActorListenerActorSubscriptionActorSubscriptionActorSubscription  +(view model)Subscription  +(view model) \ No newline at end of file diff --git a/build-aux/com.ranfdev.Notify.Devel.json b/build-aux/com.ranfdev.Notify.Devel.json index 68e0338..5a85fd9 100644 --- a/build-aux/com.ranfdev.Notify.Devel.json +++ b/build-aux/com.ranfdev.Notify.Devel.json @@ -37,17 +37,6 @@ ] }, "modules": [ - { - "name": "capnp", - "buildsystem": "cmake", - "sources": [ - { - "type": "archive", - "url": "https://capnproto.org/capnproto-c++-0.10.4.tar.gz", - "sha256": "981e7ef6dbe3ac745907e55a78870fbb491c5d23abd4ebc04e20ec235af4458c" - } - ] - }, { "name": "blueprint-compiler", "buildsystem": "meson", @@ -56,7 +45,8 @@ { "type": "git", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler", - "tag": "v0.14.0" + "tag": "v0.14.0", + "commit": "8e10fcf8692108b9d4ab78f41086c5d7773ef864" } ] }, diff --git a/ntfy-daemon/Cargo.toml b/ntfy-daemon/Cargo.toml index 6d548f2..a39976e 100644 --- a/ntfy-daemon/Cargo.toml +++ b/ntfy-daemon/Cargo.toml @@ -5,27 +5,23 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -capnpc = "0.17.2" - [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -capnp = "0.18.0" -capnp-rpc = "0.18.0" futures = "0.3.0" tokio = { version = "1.0.0", features = ["net", "rt", "macros", "parking_lot"]} tokio-util = { version = "0.7.4", features = ["compat", "io"] } clap = { version = "4.3.11", features = ["derive"] } anyhow = "1.0.71" -tokio-stream = { version = "0.1.14", features = ["io-util", "time"] } +tokio-stream = { version = "0.1.14", features = ["io-util", "time", "sync"] } rusqlite = "0.29.0" rand = "0.8.5" -reqwest = { version = "0.11.18", features = ["stream", "rustls-tls-native-roots"]} -url = "2.4.0" -generational-arena = "0.2.9" +reqwest = { version = "0.12.9", features = ["stream", "rustls-tls-native-roots"]} +url = { version = "2.4.0", features = ["serde"] } tracing = "0.1.37" thiserror = "1.0.49" regex = "1.9.6" oo7 = "0.2.1" +async-trait = "0.1.83" +http = "1.1.0" +async-channel = "2.3.1" \ No newline at end of file diff --git a/ntfy-daemon/README.md b/ntfy-daemon/README.md deleted file mode 100644 index 7161820..0000000 --- a/ntfy-daemon/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# ntfy-daemon - -Rust crate providing a capnp-rpc interface to multiple ntfy servers. -Connections to the same server are multiplexed over http2. -Messages are received and stored in a sqlite database for persistance. diff --git a/ntfy-daemon/build.rs b/ntfy-daemon/build.rs deleted file mode 100644 index cd7eb77..0000000 --- a/ntfy-daemon/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - capnpc::CompilerCommand::new() - .file("src/ntfy.capnp") - .run() - .unwrap(); -} diff --git a/ntfy-daemon/src/actor_utils.rs b/ntfy-daemon/src/actor_utils.rs new file mode 100644 index 0000000..bafb58b --- /dev/null +++ b/ntfy-daemon/src/actor_utils.rs @@ -0,0 +1,14 @@ +macro_rules! send_command { + ($self:expr, $command:expr) => {{ + let (resp_tx, resp_rx) = oneshot::channel(); + use anyhow::Context; + $self + .command_tx + .send($command(resp_tx)) + .await + .context("Actor mailbox error")?; + resp_rx.await.context("Actor response error")? + }}; +} + +pub(crate) use send_command; diff --git a/ntfy-daemon/src/credentials.rs b/ntfy-daemon/src/credentials.rs index c79879f..afab621 100644 --- a/ntfy-daemon/src/credentials.rs +++ b/ntfy-daemon/src/credentials.rs @@ -1,6 +1,135 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; +use std::sync::{Arc, RwLock}; + +use async_trait::async_trait; + +#[derive(Clone)] +pub struct KeyringItem { + attributes: HashMap, + // we could zero-out this region of memory + secret: Vec, +} + +impl KeyringItem { + async fn attributes(&self) -> HashMap { + self.attributes.clone() + } + async fn secret(&self) -> &[u8] { + &self.secret[..] + } +} + +#[async_trait] +trait LightKeyring { + async fn search_items( + &self, + attributes: HashMap<&str, &str>, + ) -> anyhow::Result>; + async fn create_item( + &self, + label: &str, + attributes: HashMap<&str, &str>, + secret: &str, + replace: bool, + ) -> anyhow::Result<()>; + async fn delete(&self, attributes: HashMap<&str, &str>) -> anyhow::Result<()>; +} + +struct RealKeyring { + keyring: oo7::Keyring, +} + +#[async_trait] +impl LightKeyring for RealKeyring { + async fn search_items( + &self, + attributes: HashMap<&str, &str>, + ) -> anyhow::Result> { + let items = self.keyring.search_items(attributes).await?; + + let mut out_items = vec![]; + for item in items { + out_items.push(KeyringItem { + attributes: item.attributes().await?, + secret: item.secret().await?.to_vec(), + }); + } + Ok(out_items) + } + + async fn create_item( + &self, + label: &str, + attributes: HashMap<&str, &str>, + secret: &str, + replace: bool, + ) -> anyhow::Result<()> { + self.keyring + .create_item(label, attributes, secret, replace) + .await?; + Ok(()) + } + + async fn delete(&self, attributes: HashMap<&str, &str>) -> anyhow::Result<()> { + self.keyring.delete(attributes).await?; + Ok(()) + } +} + +struct NullableKeyring { + search_response: Vec, +} + +impl NullableKeyring { + pub fn new(search_response: Vec) -> Self { + Self { search_response } + } +} + +#[async_trait] +impl LightKeyring for NullableKeyring { + async fn search_items( + &self, + _attributes: HashMap<&str, &str>, + ) -> anyhow::Result> { + Ok(self.search_response.clone()) + } + + async fn create_item( + &self, + _label: &str, + _attributes: HashMap<&str, &str>, + _secret: &str, + _replace: bool, + ) -> anyhow::Result<()> { + Ok(()) + } + + async fn delete(&self, _attributes: HashMap<&str, &str>) -> anyhow::Result<()> { + Ok(()) + } +} +impl NullableKeyring { + pub fn with_credentials(credentials: Vec) -> Self { + let mut search_response = vec![]; + + for cred in credentials { + let attributes = HashMap::from([ + ("type".to_string(), "password".to_string()), + ("username".to_string(), cred.username.clone()), + ("server".to_string(), cred.password.clone()), + ]); + search_response.push(KeyringItem { + attributes, + secret: cred.password.into_bytes(), + }); + } + + Self { search_response } + } +} #[derive(Debug, Clone)] pub struct Credential { @@ -8,20 +137,28 @@ pub struct Credential { pub password: String, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Credentials { - keyring: Rc, - creds: Rc>>, + keyring: Arc, + creds: Arc>>, } impl Credentials { pub async fn new() -> anyhow::Result { let mut this = Self { - keyring: Rc::new( - oo7::Keyring::new() + keyring: Arc::new(RealKeyring { + keyring: oo7::Keyring::new() .await .expect("Failed to start Secret Service"), - ), + }), + creds: Default::default(), + }; + this.load().await?; + Ok(this) + } + pub async fn new_nullable(credentials: Vec) -> anyhow::Result { + let mut this = Self { + keyring: Arc::new(NullableKeyring::with_credentials(credentials)), creds: Default::default(), }; this.load().await?; @@ -29,37 +166,31 @@ impl Credentials { } pub async fn load(&mut self) -> anyhow::Result<()> { let attrs = HashMap::from([("type", "password")]); - let values = self - .keyring - .search_items(attrs) - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; + let values = self.keyring.search_items(attrs).await?; - self.creds.borrow_mut().clear(); + let mut lock = self.creds.write().unwrap(); + lock.clear(); for item in values { - let attrs = item - .attributes() - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; - self.creds.borrow_mut().insert( + let attrs = item.attributes().await; + lock.insert( attrs["server"].to_string(), Credential { username: attrs["username"].to_string(), - password: std::str::from_utf8(&item.secret().await?)?.to_string(), + password: std::str::from_utf8(&item.secret().await)?.to_string(), }, ); } Ok(()) } pub fn get(&self, server: &str) -> Option { - self.creds.borrow().get(server).cloned() + self.creds.read().unwrap().get(server).cloned() } pub fn list_all(&self) -> HashMap { - self.creds.borrow().clone() + self.creds.read().unwrap().clone() } pub async fn insert(&self, server: &str, username: &str, password: &str) -> anyhow::Result<()> { { - if let Some(cred) = self.creds.borrow().get(server) { + if let Some(cred) = self.creds.read().unwrap().get(server) { if cred.username != username { anyhow::bail!("You can add only one account per server"); } @@ -72,10 +203,9 @@ impl Credentials { ]); self.keyring .create_item("Password", attrs, password, true) - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; + .await?; - self.creds.borrow_mut().insert( + self.creds.write().unwrap().insert( server.to_string(), Credential { username: username.to_string(), @@ -87,7 +217,8 @@ impl Credentials { pub async fn delete(&self, server: &str) -> anyhow::Result<()> { let creds = { self.creds - .borrow() + .read() + .unwrap() .get(server) .ok_or(anyhow::anyhow!("server creds not found"))? .clone() @@ -97,12 +228,10 @@ impl Credentials { ("username", &creds.username), ("server", server), ]); - self.keyring - .delete(attrs) - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; + self.keyring.delete(attrs).await?; self.creds - .borrow_mut() + .write() + .unwrap() .remove(server) .ok_or(anyhow::anyhow!("server creds not found"))?; Ok(()) diff --git a/ntfy-daemon/src/http_client.rs b/ntfy-daemon/src/http_client.rs new file mode 100644 index 0000000..cd58ce8 --- /dev/null +++ b/ntfy-daemon/src/http_client.rs @@ -0,0 +1,324 @@ +use anyhow::Result; +use async_trait::async_trait; +use reqwest::{header::HeaderMap, Client, Request, RequestBuilder, Response, ResponseBuilderExt}; +use serde_json::{json, Value}; +use std::collections::{HashMap, VecDeque}; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; +use tokio::time; + +use crate::models; +use crate::output_tracker::OutputTrackerAsync; + +// Structure to store request information for verification +#[derive(Clone, Debug)] +pub struct RequestInfo { + pub url: String, + pub method: String, + pub headers: HeaderMap, + pub body: Option>, +} + +impl RequestInfo { + fn from_request(request: &Request) -> Self { + RequestInfo { + url: request.url().to_string(), + method: request.method().to_string(), + headers: request.headers().clone(), + body: None, // Note: Request body can't be accessed after it's built + } + } +} + +#[async_trait] +trait LightHttpClient: Send + Sync { + fn get(&self, url: &str) -> RequestBuilder; + fn post(&self, url: &str) -> RequestBuilder; + async fn execute(&self, request: Request) -> Result; +} + +#[async_trait] +impl LightHttpClient for Client { + fn get(&self, url: &str) -> RequestBuilder { + self.get(url) + } + + fn post(&self, url: &str) -> RequestBuilder { + self.post(url) + } + + async fn execute(&self, request: Request) -> Result { + Ok(self.execute(request).await?) + } +} + +#[derive(Clone)] +pub struct HttpClient { + client: Arc, + request_tracker: OutputTrackerAsync, +} + +impl HttpClient { + pub fn new(client: reqwest::Client) -> Self { + Self { + client: Arc::new(client), + request_tracker: Default::default(), + } + } + pub fn new_nullable(client: NullableClient) -> Self { + Self { + client: Arc::new(client), + request_tracker: Default::default(), + } + } + + pub async fn request_tracker(&self) -> OutputTrackerAsync { + self.request_tracker.enable().await; + self.request_tracker.clone() + } + + pub fn get(&self, url: &str) -> RequestBuilder { + self.client.get(url) + } + + pub fn post(&self, url: &str) -> RequestBuilder { + self.client.post(url) + } + + pub async fn execute(&self, request: Request) -> Result { + self.request_tracker + .push(RequestInfo::from_request(&request)) + .await; + + Ok(self.client.execute(request).await?) + } +} + +#[derive(Clone, Default)] +pub struct NullableClient { + responses: Arc>>>, + default_response: Arc Response + Send + Sync + 'static>>>>, +} + +/// Builder for configuring NullableClient +#[derive(Default)] +pub struct NullableClientBuilder { + responses: HashMap>, + default_response: Option Response + Send + Sync + 'static>>, +} + +impl NullableClientBuilder { + pub fn new() -> Self { + Self::default() + } + + /// Add a single response for a specific URL + pub fn response(mut self, url: impl Into, response: Response) -> Self { + self.responses + .entry(url.into()) + .or_default() + .push_back(response); + self + } + + /// Add multiple responses for a specific URL that will be returned in sequence + pub fn responses(mut self, url: impl Into, responses: Vec) -> Self { + self.responses.insert(url.into(), responses.into()); + self + } + + /// Set a default response generator for any unmatched URLs + pub fn default_response( + mut self, + response: impl Fn() -> Response + Send + Sync + 'static, + ) -> Self { + self.default_response = Some(Box::new(response)); + self + } + + /// Helper method to quickly add a JSON response + pub fn json_response( + self, + url: impl Into, + status: u16, + body: impl serde::Serialize, + ) -> Result { + let response = http::response::Builder::new() + .status(status) + .body(serde_json::to_string(&body)?) + .unwrap() + .into(); + Ok(self.response(url, response)) + } + + /// Helper method to quickly add a text response + pub fn text_response( + self, + url: impl Into, + status: u16, + body: impl Into, + ) -> Self { + let response = http::response::Builder::new() + .status(status) + .body(body.into()) + .unwrap() + .into(); + self.response(url, response) + } + + pub fn build(self) -> NullableClient { + NullableClient { + responses: Arc::new(RwLock::new( + self.responses + .into_iter() + .map(|(k, v)| (k, v.into())) + .collect(), + )), + default_response: Arc::new(RwLock::new(self.default_response)), + } + } +} + +impl NullableClient { + pub fn builder() -> NullableClientBuilder { + NullableClientBuilder::new() + } +} + +#[async_trait] +impl LightHttpClient for NullableClient { + fn get(&self, url: &str) -> RequestBuilder { + Client::new().get(url) + } + + fn post(&self, url: &str) -> RequestBuilder { + Client::new().post(url) + } + + async fn execute(&self, request: Request) -> Result { + time::sleep(Duration::from_millis(1)).await; + let url = request.url().to_string(); + let mut responses = self.responses.write().await; + + if let Some(url_responses) = responses.get_mut(&url) { + if let Some(response) = url_responses.pop_front() { + // Remove the URL entry if no more responses + if url_responses.is_empty() { + responses.remove(&url); + } + Ok(response) + } else { + if let Some(default_fn) = &*self.default_response.read().await { + Ok(default_fn()) + } else { + Err(anyhow::anyhow!("no response configured for URL: {}", url)) + } + } + } else if let Some(default_fn) = &*self.default_response.read().await { + Ok(default_fn()) + } else { + Err(anyhow::anyhow!("no response configured for URL: {}", url)) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[tokio::test] + async fn test_nullable_with_builder() -> Result<()> { + // Configure client using builder pattern + let client = NullableClient::builder() + .text_response("https://api.example.com/topic", 200, "ok") + .json_response( + "https://api.example.com/json", + 200, + json!({ "status": "success" }), + )? + .default_response(|| { + http::response::Builder::new() + .status(404) + .body("not found") + .unwrap() + .into() + }) + .build(); + + let http_client = HttpClient::new_nullable(client); + let request_tracker = http_client.request_tracker().await; + + // Test successful text response + let request = http_client.get("https://api.example.com/topic").build()?; + let response = http_client.execute(request).await?; + assert_eq!(response.status(), 200); + assert_eq!(response.text().await?, "ok"); + + // Test successful JSON response + let request = http_client.get("https://api.example.com/json").build()?; + let response = http_client.execute(request).await?; + assert_eq!(response.status(), 200); + assert_eq!(response.text().await?, r#"{"status":"success"}"#); + + // Test default response + let request = http_client.get("https://api.example.com/unknown").build()?; + let response = http_client.execute(request).await?; + assert_eq!(response.status(), 404); + assert_eq!(response.text().await?, "not found"); + + // Verify recorded requests + let requests = request_tracker.items().await; + assert_eq!(requests.len(), 3); + + Ok(()) + } + + #[tokio::test] + async fn test_sequence_of_responses() -> Result<()> { + // Configure client with multiple responses for the same URL + let client = NullableClient::builder() + .responses( + "https://api.example.com/sequence", + vec![ + http::response::Builder::new() + .status(200) + .body("first") + .unwrap() + .into(), + http::response::Builder::new() + .status(200) + .body("second") + .unwrap() + .into(), + ], + ) + .build(); + + let http_client = HttpClient::new_nullable(client); + + // First request gets first response + let request = http_client + .get("https://api.example.com/sequence") + .build()?; + let response = http_client.execute(request).await?; + assert_eq!(response.text().await?, "first"); + + // Second request gets second response + let request = http_client + .get("https://api.example.com/sequence") + .build()?; + let response = http_client.execute(request).await?; + assert_eq!(response.text().await?, "second"); + + // Third request fails (no more responses) + let request = http_client + .get("https://api.example.com/sequence") + .build()?; + let result = http_client.execute(request).await; + assert!(result.is_err()); + + Ok(()) + } +} diff --git a/ntfy-daemon/src/lib.rs b/ntfy-daemon/src/lib.rs index b66af27..f5cf0d7 100644 --- a/ntfy-daemon/src/lib.rs +++ b/ntfy-daemon/src/lib.rs @@ -1,21 +1,28 @@ +mod actor_utils; pub mod credentials; +mod http_client; +mod listener; pub mod message_repo; pub mod models; +mod ntfy; +mod output_tracker; pub mod retry; -pub mod system_client; -pub mod topic_listener; -pub mod ntfy_capnp { - include!(concat!(env!("OUT_DIR"), "/src/ntfy_capnp.rs")); -} +mod subscription; +pub use listener::*; +pub use ntfy::start; +pub use ntfy::NtfyHandle; use std::sync::Arc; +pub use subscription::SubscriptionHandle; + +use http_client::HttpClient; #[derive(Clone)] pub struct SharedEnv { db: message_repo::Db, - proxy: Arc, - http: reqwest::Client, - network: Arc, + notifier: Arc, + http_client: HttpClient, + network_monitor: Arc, credentials: credentials::Credentials, } @@ -25,6 +32,8 @@ pub enum Error { InvalidTopic(String), #[error("invalid server base url {0:?}")] InvalidServer(#[from] url::ParseError), + #[error("multiple errors in subscription model: {0:?}")] + InvalidSubscription(Vec), #[error("duplicate message")] DuplicateMessage, #[error("can't parse the minimum set of required fields from the message {0}")] @@ -36,9 +45,3 @@ pub enum Error { #[error("subscription not found while {0}")] SubscriptionNotFound(String), } - -impl From for capnp::Error { - fn from(value: Error) -> Self { - capnp::Error::failed(format!("{:?}", value)) - } -} diff --git a/ntfy-daemon/src/listener.rs b/ntfy-daemon/src/listener.rs new file mode 100644 index 0000000..49141fa --- /dev/null +++ b/ntfy-daemon/src/listener.rs @@ -0,0 +1,393 @@ +use std::sync::Arc; +use std::time::Duration; + +use futures::{StreamExt, TryStreamExt}; +use serde::{Deserialize, Serialize}; +use tokio::io::AsyncBufReadExt; +use tokio::task::{self, spawn_local, LocalSet}; +use tokio::{ + select, + sync::{mpsc, oneshot}, +}; +use tokio_stream::wrappers::LinesStream; +use tracing::{debug, error, info, warn, Instrument, Span}; + +use crate::credentials::Credentials; +use crate::http_client::HttpClient; +use crate::{models, Error}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "event")] +pub enum ServerEvent { + #[serde(rename = "open")] + Open { + id: String, + time: usize, + expires: Option, + topic: String, + }, + #[serde(rename = "message")] + Message(models::ReceivedMessage), + #[serde(rename = "keepalive")] + KeepAlive { + id: String, + time: usize, + expires: Option, + topic: String, + }, +} + +#[derive(Debug, Clone)] +pub enum ListenerEvent { + Message(models::ReceivedMessage), + ConnectionStateChanged(ConnectionState), +} + +#[derive(Clone)] +pub struct ListenerConfig { + pub(crate) http_client: HttpClient, + pub(crate) credentials: Credentials, + pub(crate) endpoint: String, + pub(crate) topic: String, + pub(crate) since: u64, +} + +#[derive(Debug)] +pub enum ListenerCommand { + Restart, + Shutdown, + GetState(oneshot::Sender), +} + +fn topic_request( + client: &HttpClient, + endpoint: &str, + topic: &str, + since: u64, + username: Option<&str>, + password: Option<&str>, +) -> anyhow::Result { + let url = models::Subscription::build_url(endpoint, topic, since)?; + let mut req = client + .get(url.as_str()) + .header("Content-Type", "application/x-ndjson") + .header("Transfer-Encoding", "chunked"); + if let Some(username) = username { + req = req.basic_auth(username, password); + } + + Ok(req.build()?) +} + +async fn response_lines( + res: impl tokio::io::AsyncBufRead, +) -> Result>, reqwest::Error> { + let lines = LinesStream::new(res.lines()); + Ok(lines) +} + +#[derive(Clone, Debug)] +pub enum ConnectionState { + Unitialized, + Connected, + Reconnecting { + retry_count: u64, + delay: Duration, + error: Option>, + }, +} + +pub struct ListenerActor { + pub event_tx: async_channel::Sender, + pub commands_rx: Option>, + pub config: ListenerConfig, + pub state: ConnectionState, +} + +impl ListenerActor { + pub async fn run_loop(mut self) { + let span = tracing::info_span!("listener_loop", topic = %self.config.topic); + async { + let mut commands_rx = self.commands_rx.take().unwrap(); + loop { + select! { + _ = self.run_supervised_loop() => { + info!("supervised loop ended"); + break; + }, + cmd = commands_rx.recv() => { + match cmd { + Some(ListenerCommand::Restart) => { + info!("restarting listener"); + continue; + } + Some(ListenerCommand::Shutdown) => { + info!("shutting down listener"); + break; + } + Some(ListenerCommand::GetState(tx)) => { + debug!("getting listener state"); + let state = self.state.clone(); + if tx.send(state).is_err() { + warn!("failed to send state - receiver dropped"); + } + } + None => { + error!("command channel closed"); + break; + } + } + } + } + } + } + .instrument(span) + .await; + } + + async fn set_state(&mut self, state: ConnectionState) { + self.state = state.clone(); + self.event_tx + .send(ListenerEvent::ConnectionStateChanged(state)) + .await + .unwrap(); + } + async fn run_supervised_loop(&mut self) { + let span = tracing::info_span!("supervised_loop"); + async { + let retrier = || { + crate::retry::WaitExponentialRandom::builder() + .min(Duration::from_secs(1)) + .max(Duration::from_secs(5 * 60)) + .build() + }; + let mut retry = retrier(); + loop { + let start_time = std::time::Instant::now(); + + if let Err(e) = self.recv_and_forward_loop().await { + let uptime = std::time::Instant::now().duration_since(start_time); + // Reset retry delay to minimum if uptime was decent enough + if uptime > Duration::from_secs(60 * 4) { + debug!("resetting retry delay due to sufficient uptime"); + retry = retrier(); + } + error!(error = ?e, "connection error"); + self.set_state(ConnectionState::Reconnecting { + retry_count: retry.count(), + delay: retry.next_delay(), + error: Some(Arc::new(e)), + }) + .await; + info!(delay = ?retry.next_delay(), "waiting before reconnect attempt"); + retry.wait().await; + } else { + break; + } + } + } + .instrument(span) + .await; + } + + async fn recv_and_forward_loop(&mut self) -> anyhow::Result<()> { + let span = tracing::info_span!("receive_loop", + endpoint = %self.config.endpoint, + topic = %self.config.topic, + since = %self.config.since + ); + async { + let creds = self.config.credentials.get(&self.config.endpoint); + debug!("creating request"); + let req = topic_request( + &self.config.http_client, + &self.config.endpoint, + &self.config.topic, + self.config.since, + creds.as_ref().map(|x| x.username.as_str()), + creds.as_ref().map(|x| x.password.as_str()), + ); + + debug!("executing request"); + let res = self.config.http_client.execute(req?).await?; + let res = res.error_for_status()?; + let reader = tokio_util::io::StreamReader::new( + res.bytes_stream() + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())), + ); + let stream = response_lines(reader).await?; + tokio::pin!(stream); + + self.set_state(ConnectionState::Connected).await; + info!("connection established"); + + info!(topic = %&self.config.topic, "listening"); + while let Some(msg) = stream.next().await { + let msg = msg?; + + let min_msg = serde_json::from_str::(&msg) + .map_err(|e| Error::InvalidMinMessage(msg.to_string(), e))?; + self.config.since = min_msg.time.max(self.config.since); + + let event = serde_json::from_str(&msg) + .map_err(|e| Error::InvalidMessage(msg.to_string(), e))?; + + match event { + ServerEvent::Message(msg) => { + debug!(id = %msg.id, "forwarding message"); + self.event_tx + .send(ListenerEvent::Message(msg)) + .await + .unwrap(); + } + ServerEvent::KeepAlive { id, .. } => { + debug!(id = %id, "received keepalive"); + } + ServerEvent::Open { id, .. } => { + debug!(id = %id, "received open event"); + } + } + } + + Ok(()) + } + .instrument(span) + .await + } +} + +// Reliable listener implementation +#[derive(Clone)] +pub struct ListenerHandle { + pub events: async_channel::Receiver, + pub config: ListenerConfig, + pub commands: mpsc::Sender, +} + +impl ListenerHandle { + pub fn new(config: ListenerConfig) -> ListenerHandle { + let (event_tx, event_rx) = async_channel::bounded(64); + let (commands_tx, commands_rx) = mpsc::channel(1); + + let config_clone = config.clone(); + + // use a new local set to isolate panics + let local_set = LocalSet::new(); + local_set.spawn_local(async move { + let this = ListenerActor { + event_tx, + commands_rx: Some(commands_rx), + config: config_clone, + state: ConnectionState::Unitialized, + }; + + this.run_loop().await; + }); + spawn_local(local_set); + + Self { + events: event_rx, + config, + commands: commands_tx, + } + } + + // the response will be sent as an event in self.events + pub async fn state(&self) -> ConnectionState { + let (tx, rx) = oneshot::channel(); + self.commands + .send(ListenerCommand::GetState(tx)) + .await + .unwrap(); + rx.await.unwrap() + } +} + +#[cfg(test)] +mod tests { + use models::Subscription; + use serde_json::json; + use task::LocalSet; + + use crate::http_client::NullableClient; + + use super::*; + + #[tokio::test] + async fn test_listener_reconnects_on_http_status_500() { + let local_set = LocalSet::new(); + local_set + .spawn_local(async { + let http_client = HttpClient::new_nullable({ + let url = Subscription::build_url("http://localhost", "test", 0).unwrap(); + let nullable = NullableClient::builder() + .text_response(url.clone(), 500, "failed") + .json_response(url, 200, json!({"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"})).unwrap() + .build(); + nullable + }); + let credentials = Credentials::new_nullable(vec![]).await.unwrap(); + + let config = ListenerConfig { + http_client, + credentials, + endpoint: "http://localhost".to_string(), + topic: "test".to_string(), + since: 0, + }; + + let listener = ListenerHandle::new(config.clone()); + let items: Vec<_> = listener.events.take(3).collect().await; + + dbg!(&items); + assert!(matches!( + &items[..], + &[ + ListenerEvent::ConnectionStateChanged(ConnectionState::Unitialized), + ListenerEvent::ConnectionStateChanged(ConnectionState::Reconnecting { .. }), + ListenerEvent::ConnectionStateChanged(ConnectionState::Connected { .. }), + ] + )); + }); + local_set.await; + } + + #[tokio::test] + async fn test_listener_reconnects_on_invalid_message() { + let local_set = LocalSet::new(); + local_set + .spawn_local(async { + let http_client = HttpClient::new_nullable({ + let url = Subscription::build_url("http://localhost", "test", 0).unwrap(); + let nullable = NullableClient::builder() + .text_response(url.clone(), 200, "invalid message") + .json_response(url, 200, json!({"id":"SLiKI64DOt","time":1635528757,"event":"open","topic":"mytopic"})).unwrap() + .build(); + nullable + }); + let credentials = Credentials::new_nullable(vec![]).await.unwrap(); + + let config = ListenerConfig { + http_client, + credentials, + endpoint: "http://localhost".to_string(), + topic: "test".to_string(), + since: 0, + }; + + let listener = ListenerHandle::new(config.clone()); + let items: Vec<_> = listener.events.take(3).collect().await; + + dbg!(&items); + assert!(matches!( + &items[..], + &[ + ListenerEvent::ConnectionStateChanged(ConnectionState::Unitialized), + ListenerEvent::ConnectionStateChanged(ConnectionState::Reconnecting { .. }), + ListenerEvent::ConnectionStateChanged(ConnectionState::Connected { .. }), + ] + )); + }); + local_set.await; + } +} diff --git a/ntfy-daemon/src/message_repo/mod.rs b/ntfy-daemon/src/message_repo/mod.rs index fe23474..866689a 100644 --- a/ntfy-daemon/src/message_repo/mod.rs +++ b/ntfy-daemon/src/message_repo/mod.rs @@ -1,3 +1,4 @@ +use std::sync::{Arc, RwLock}; use std::{cell::RefCell, rc::Rc}; use rusqlite::{params, Connection, Result}; @@ -8,16 +9,16 @@ use crate::Error; #[derive(Clone, Debug)] pub struct Db { - conn: Rc>, + conn: Arc>, } impl Db { pub fn connect(path: &str) -> Result { let mut this = Self { - conn: Rc::new(RefCell::new(Connection::open(path)?)), + conn: Arc::new(RwLock::new(Connection::open(path)?)), }; { - this.conn.borrow().execute_batch( + this.conn.read().unwrap().execute_batch( "PRAGMA foreign_keys = ON; PRAGMA journal_mode = wal;", )?; @@ -27,12 +28,13 @@ impl Db { } fn migrate(&mut self) -> Result<()> { self.conn - .borrow() + .read() + .unwrap() .execute_batch(include_str!("./migrations/00.sql"))?; Ok(()) } fn get_or_insert_server(&mut self, server: &str) -> Result { - let mut conn = self.conn.borrow_mut(); + let mut conn = self.conn.write().unwrap(); let tx = conn.transaction()?; let mut res = tx.query_row( "SELECT id @@ -56,7 +58,7 @@ impl Db { } pub fn insert_message(&mut self, server: &str, json_data: &str) -> Result<(), Error> { let server_id = self.get_or_insert_server(server)?; - let res = self.conn.borrow().execute( + let res = self.conn.read().unwrap().execute( "INSERT INTO message (server, data) VALUES (?1, ?2)", params![server_id, json_data], ); @@ -76,7 +78,7 @@ impl Db { topic: &str, since: u64, ) -> Result, rusqlite::Error> { - let conn = self.conn.borrow(); + let conn = self.conn.read().unwrap(); let mut stmt = conn.prepare( " SELECT data @@ -94,7 +96,7 @@ impl Db { } pub fn insert_subscription(&mut self, sub: models::Subscription) -> Result<(), Error> { let server_id = self.get_or_insert_server(&sub.server)?; - self.conn.borrow().execute( + self.conn.read().unwrap().execute( "INSERT INTO subscription (server, topic, display_name, reserved, muted, archived) VALUES (?1, ?2, ?3, ?4, ?5, ?6)", params![ server_id, @@ -109,7 +111,7 @@ impl Db { } pub fn remove_subscription(&mut self, server: &str, topic: &str) -> Result<(), Error> { let server_id = self.get_or_insert_server(server)?; - let res = self.conn.borrow().execute( + let res = self.conn.read().unwrap().execute( "DELETE FROM subscription WHERE server = ?1 AND topic = ?2", params![server_id, topic], @@ -120,7 +122,7 @@ impl Db { Ok(()) } pub fn list_subscriptions(&mut self) -> Result, Error> { - let conn = self.conn.borrow(); + let conn = self.conn.read().unwrap(); let mut stmt = conn.prepare( "SELECT server.endpoint, sub.topic, sub.display_name, sub.reserved, sub.muted, sub.archived, sub.symbolic_icon, sub.read_until FROM subscription sub @@ -146,7 +148,7 @@ impl Db { pub fn update_subscription(&mut self, sub: models::Subscription) -> Result<(), Error> { let server_id = self.get_or_insert_server(&sub.server)?; - let res = self.conn.borrow().execute( + let res = self.conn.read().unwrap().execute( "UPDATE subscription SET display_name = ?1, reserved = ?2, muted = ?3, archived = ?4, read_until = ?5 WHERE server = ?6 AND topic = ?7", @@ -174,7 +176,7 @@ impl Db { value: u64, ) -> Result<(), Error> { let server_id = self.get_or_insert_server(server).unwrap(); - let conn = self.conn.borrow(); + let conn = self.conn.read().unwrap(); let res = conn.execute( "UPDATE subscription SET read_until = ?3 @@ -189,7 +191,7 @@ impl Db { } pub fn delete_messages(&mut self, server: &str, topic: &str) -> Result<(), Error> { let server_id = self.get_or_insert_server(server).unwrap(); - let conn = self.conn.borrow(); + let conn = self.conn.read().unwrap(); let res = conn.execute( "DELETE FROM message WHERE topic = ?2 AND server = ?1 diff --git a/ntfy-daemon/src/models.rs b/ntfy-daemon/src/models.rs index 46de3e1..1b01765 100644 --- a/ntfy-daemon/src/models.rs +++ b/ntfy-daemon/src/models.rs @@ -27,8 +27,10 @@ pub fn validate_topic(topic: &str) -> Result<&str, Error> { } #[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct Message { +pub struct ReceivedMessage { + pub id: String, pub topic: String, + pub expires: Option, pub message: Option, #[serde(default = "Default::default")] pub time: u64, @@ -57,7 +59,7 @@ pub struct Message { pub actions: Vec, } -impl Message { +impl ReceivedMessage { fn extend_with_emojis(&self, text: &mut String) { // Add emojis for t in &self.tags { @@ -105,6 +107,37 @@ impl Message { } } +#[derive(Default, Clone, Debug, Serialize, Deserialize)] +pub struct OutgoingMessage { + pub topic: String, + pub message: Option, + #[serde(default = "Default::default")] + pub time: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + pub tags: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub priority: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub attachment: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub icon: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub filename: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delay: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub call: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Vec::is_empty")] + pub actions: Vec, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MinMessage { pub id: String, @@ -165,7 +198,7 @@ impl Subscription { .push("auth"); Ok(url) } - pub fn validate(self) -> Result> { + pub fn validate(self) -> Result { let mut errs = vec![]; if let Err(e) = validate_topic(&self.topic) { errs.push(e); @@ -174,7 +207,7 @@ impl Subscription { errs.push(e); }; if !errs.is_empty() { - return Err(errs); + return Err(Error::InvalidSubscription(errs)); } Ok(self) } @@ -237,7 +270,7 @@ impl SubscriptionBuilder { self } - pub fn build(self) -> Result> { + pub fn build(self) -> Result { let res = Subscription { server: self.server, topic: self.topic, @@ -318,6 +351,12 @@ impl From for u8 { } } +#[derive(Clone, Debug)] +pub struct Account { + pub server: String, + pub username: String, +} + pub struct Notification { pub title: String, pub body: String, @@ -331,3 +370,30 @@ pub trait NotificationProxy: Sync + Send { pub trait NetworkMonitorProxy: Sync + Send { fn listen(&self) -> Pin>>; } + +pub struct NullNotifier {} + +impl NullNotifier { + pub fn new() -> Self { + Self {} + } +} +impl NotificationProxy for NullNotifier { + fn send(&self, n: Notification) -> anyhow::Result<()> { + Ok(()) + } +} + +pub struct NullNetworkMonitor {} + +impl NullNetworkMonitor { + pub fn new() -> Self { + Self {} + } +} + +impl NetworkMonitorProxy for NullNetworkMonitor { + fn listen(&self) -> Pin>> { + Box::pin(futures::stream::empty()) + } +} diff --git a/ntfy-daemon/src/ntfy.capnp b/ntfy-daemon/src/ntfy.capnp deleted file mode 100644 index 6f3d202..0000000 --- a/ntfy-daemon/src/ntfy.capnp +++ /dev/null @@ -1,49 +0,0 @@ -@0x9663f4dd604afa35; - -enum Status { - down @0; - degraded @1; - up @2; -} - -interface WatchHandle {} - -interface OutputChannel { - sendMessage @0 (message: Text); - sendStatus @1 (status: Status); - done @2 (); -} - -struct SubscriptionInfo { - server @0 :Text; - topic @1 :Text; - displayName @2 :Text; - muted @3 :Bool; - readUntil @4 :UInt64; -} - -interface Subscription { - watch @0 (watcher: OutputChannel, since: UInt64) -> (handle: WatchHandle); - publish @1 (message: Text); - - getInfo @2 () -> SubscriptionInfo; - updateInfo @3 (value: SubscriptionInfo); - updateReadUntil @4 (value: UInt64); - - clearNotifications @5 (); - refresh @6 (); -} - -struct Account { - server @0 :Text; - username @1 :Text; -} - -interface SystemNotifier { - subscribe @0 (server: Text, topic: Text) -> (subscription: Subscription); - unsubscribe @1 (server: Text, topic: Text); - listSubscriptions @2 () -> (list: List(Subscription)); - addAccount @3 (account: Account, password: Text); - removeAccount @4 (account: Account); - listAccounts @5 () -> (list: List(Account)); -} diff --git a/ntfy-daemon/src/ntfy.rs b/ntfy-daemon/src/ntfy.rs new file mode 100644 index 0000000..7407f2f --- /dev/null +++ b/ntfy-daemon/src/ntfy.rs @@ -0,0 +1,450 @@ +use crate::actor_utils::send_command; +use crate::models::NullNetworkMonitor; +use crate::models::NullNotifier; +use anyhow::{anyhow, Context}; +use futures::future::join_all; +use futures::StreamExt; +use std::{collections::HashMap, future::Future, sync::Arc}; +use tokio::select; +use tokio::{ + sync::{broadcast, mpsc, oneshot, RwLock}, + task::{spawn_local, LocalSet}, +}; +use tracing::{error, info}; + +use crate::{ + http_client::HttpClient, + message_repo::Db, + models::{self, Account}, + ListenerActor, ListenerCommand, ListenerConfig, ListenerHandle, SharedEnv, SubscriptionHandle, +}; + +const CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15); +const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(240); // 4 minutes + +pub fn build_client() -> anyhow::Result { + Ok(reqwest::Client::builder() + .connect_timeout(CONNECT_TIMEOUT) + .pool_idle_timeout(TIMEOUT) + // rustls is used because HTTP 2 isn't discovered with native-tls. + // HTTP 2 is required to multiplex multiple requests over a single connection. + // You can check that the app is using a single connection to a server by doing + // ``` + // ping ntfy.sh # to get the ip address + // netstat | grep $ip + // ``` + .use_rustls_tls() + .build()?) +} + +// Message types for the actor +#[derive()] +pub enum NtfyCommand { + Subscribe { + server: String, + topic: String, + resp_tx: oneshot::Sender>, + }, + Unsubscribe { + server: String, + topic: String, + resp_tx: oneshot::Sender>, + }, + RefreshAll { + resp_tx: oneshot::Sender>, + }, + ListSubscriptions { + resp_tx: oneshot::Sender>>, + }, + ListAccounts { + resp_tx: oneshot::Sender>>, + }, + WatchSubscribed { + resp_tx: oneshot::Sender>, + }, + AddAccount { + server: String, + username: String, + password: String, + resp_tx: oneshot::Sender>, + }, + RemoveAccount { + server: String, + resp_tx: oneshot::Sender>, + }, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct WatchKey { + server: String, + topic: String, +} + +pub struct NtfyActor { + listener_handles: Arc>>, + env: SharedEnv, + command_rx: mpsc::Receiver, +} + +#[derive(Clone)] +pub struct NtfyHandle { + command_tx: mpsc::Sender, +} + +impl NtfyActor { + pub fn new(env: SharedEnv) -> (Self, NtfyHandle) { + let (command_tx, command_rx) = mpsc::channel(32); + + let actor = Self { + listener_handles: Default::default(), + env, + command_rx, + }; + + let handle = NtfyHandle { command_tx }; + + (actor, handle) + } + + async fn handle_subscribe( + &self, + server: String, + topic: String, + ) -> Result { + let subscription = models::Subscription::builder(topic.clone()) + .server(server.clone()) + .build()?; + + let mut db = self.env.db.clone(); + db.insert_subscription(subscription.clone())?; + + self.listen(subscription).await + } + + async fn handle_unsubscribe(&mut self, server: String, topic: String) -> anyhow::Result<()> { + let subscription = self.listener_handles.write().await.remove(&WatchKey { + server: server.clone(), + topic: topic.clone(), + }); + + if let Some(sub) = subscription { + sub.shutdown().await?; + } + + self.env.db.remove_subscription(&server, &topic)?; + info!(server, topic, "Unsubscribed"); + Ok(()) + } + + pub async fn run(&mut self) { + let mut network_change_stream = self.env.network_monitor.listen(); + loop { + select! { + Some(_) = network_change_stream.next() => { + let _ = self.refresh_all().await; + }, + Some(command) = self.command_rx.recv() => self.handle_command(command).await, + }; + } + } + + async fn handle_command(&mut self, command: NtfyCommand) { + match command { + NtfyCommand::Subscribe { + server, + topic, + resp_tx, + } => { + let result = self.handle_subscribe(server, topic).await; + let _ = resp_tx.send(result); + } + + NtfyCommand::Unsubscribe { + server, + topic, + resp_tx, + } => { + let result = self.handle_unsubscribe(server, topic).await; + let _ = resp_tx.send(result); + } + + NtfyCommand::RefreshAll { resp_tx } => { + let res = self.refresh_all().await; + let _ = resp_tx.send(res); + } + + NtfyCommand::ListSubscriptions { resp_tx } => { + let subs = self + .listener_handles + .read() + .await + .values() + .cloned() + .collect(); + let _ = resp_tx.send(Ok(subs)); + } + + NtfyCommand::ListAccounts { resp_tx } => { + let accounts = self + .env + .credentials + .list_all() + .into_iter() + .map(|(server, credential)| Account { + server, + username: credential.username, + }) + .collect(); + let _ = resp_tx.send(Ok(accounts)); + } + + NtfyCommand::WatchSubscribed { resp_tx } => { + let result = self.handle_watch_subscribed().await; + let _ = resp_tx.send(result); + } + + NtfyCommand::AddAccount { + server, + username, + password, + resp_tx, + } => { + let result = self + .env + .credentials + .insert(&server, &username, &password) + .await; + let _ = resp_tx.send(result); + } + + NtfyCommand::RemoveAccount { server, resp_tx } => { + let result = self.env.credentials.delete(&server).await; + let _ = resp_tx.send(result); + } + } + } + + async fn handle_watch_subscribed(&mut self) -> anyhow::Result<()> { + let f: Vec<_> = self + .env + .db + .list_subscriptions()? + .into_iter() + .map(|m| self.listen(m)) + .collect(); + + join_all(f.into_iter().map(|x| async move { + if let Err(e) = x.await { + error!(error = ?e, "Can't rewatch subscribed topic"); + } + })) + .await; + + Ok(()) + } + + fn listen( + &self, + sub: models::Subscription, + ) -> impl Future> { + let server = sub.server.clone(); + let topic = sub.topic.clone(); + let listener = ListenerHandle::new(ListenerConfig { + http_client: self.env.http_client.clone(), + credentials: self.env.credentials.clone(), + endpoint: server.clone(), + topic: topic.clone(), + since: sub.read_until, + }); + let listener_handles = self.listener_handles.clone(); + let sub = SubscriptionHandle::new(listener.clone(), sub, &self.env); + + async move { + listener_handles + .write() + .await + .insert(WatchKey { server, topic }, sub.clone()); + Ok(sub) + } + } + + async fn refresh_all(&self) -> anyhow::Result<()> { + let mut res = Ok(()); + for sub in self.listener_handles.read().await.values() { + res = sub.restart().await; + if res.is_err() { + break; + } + } + res + } +} + +impl NtfyHandle { + pub async fn subscribe( + &self, + server: &str, + topic: &str, + ) -> Result { + send_command!(self, |resp_tx| NtfyCommand::Subscribe { + server: server.to_string(), + topic: topic.to_string(), + resp_tx, + }) + } + + pub async fn unsubscribe(&self, server: &str, topic: &str) -> anyhow::Result<()> { + send_command!(self, |resp_tx| NtfyCommand::Unsubscribe { + server: server.to_string(), + topic: topic.to_string(), + resp_tx, + }) + } + + pub async fn refresh_all(&self) -> anyhow::Result<()> { + send_command!(self, |resp_tx| NtfyCommand::RefreshAll { resp_tx }) + } + + pub async fn list_subscriptions(&self) -> anyhow::Result> { + send_command!(self, |resp_tx| NtfyCommand::ListSubscriptions { resp_tx }) + } + + pub async fn list_accounts(&self) -> anyhow::Result> { + send_command!(self, |resp_tx| NtfyCommand::ListAccounts { resp_tx }) + } + + pub async fn watch_subscribed(&self) -> anyhow::Result<()> { + send_command!(self, |resp_tx| NtfyCommand::WatchSubscribed { resp_tx }) + } + + pub async fn add_account( + &self, + server: &str, + username: &str, + password: &str, + ) -> anyhow::Result<()> { + send_command!(self, |resp_tx| NtfyCommand::AddAccount { + server: server.to_string(), + username: username.to_string(), + password: password.to_string(), + resp_tx, + }) + } + + pub async fn remove_account(&self, server: &str) -> anyhow::Result<()> { + send_command!(self, |resp_tx| NtfyCommand::RemoveAccount { + server: server.to_string(), + resp_tx, + }) + } +} + +pub fn start( + dbpath: &str, + notification_proxy: Arc, + network_proxy: Arc, +) -> anyhow::Result { + let dbpath = dbpath.to_owned(); + + // Create a channel to receive the handle from the spawned thread + let (handle_tx, handle_rx) = oneshot::channel(); + + std::thread::spawn(move || { + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + // Create everything inside the new thread's runtime + let credentials = + rt.block_on(async move { crate::credentials::Credentials::new().await.unwrap() }); + + let env = SharedEnv { + db: Db::connect(&dbpath).unwrap(), + notifier: notification_proxy, + http_client: HttpClient::new(build_client().unwrap()), + network_monitor: network_proxy, + credentials, + }; + + let (mut actor, handle) = NtfyActor::new(env); + let handle_clone = handle.clone(); + + // Send the handle back to the calling thread + handle_tx.send(handle.clone()); + + rt.block_on({ + let local_set = LocalSet::new(); + // Spawn the watch_subscribed task + local_set.spawn_local(async move { + if let Err(e) = handle_clone.watch_subscribed().await { + error!(error = ?e, "Failed to watch subscribed topics"); + } + }); + + // Run the actor + local_set.spawn_local(async move { + actor.run().await; + }); + local_set + }) + }); + + // Wait for the handle from the spawned thread + Ok(handle_rx + .blocking_recv() + .map_err(|_| anyhow!("Failed to receive actor handle"))?) +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + + use models::{OutgoingMessage, ReceivedMessage}; + use tokio::time::sleep; + + use crate::ListenerEvent; + + use super::*; + + #[test] + fn test_subscribe_and_publish() { + let notification_proxy = Arc::new(NullNotifier::new()); + let network_proxy = Arc::new(NullNetworkMonitor::new()); + let dbpath = ":memory:"; + + let handle = start(dbpath, notification_proxy, network_proxy).unwrap(); + + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .unwrap(); + + rt.block_on(async move { + let server = "http://localhost:8000"; + let topic = "test_topic"; + + // Subscribe to the topic + let subscription_handle = handle.subscribe(server, topic).await.unwrap(); + + // Publish a message + let message = serde_json::to_string(&OutgoingMessage { + topic: topic.to_string(), + ..Default::default() + }) + .unwrap(); + let result = subscription_handle.publish(message).await; + assert!(result.is_ok()); + + sleep(Duration::from_millis(250)).await; + + // Attach to the subscription and check if the message is received and stored + let (events, receiver) = subscription_handle.attach().await; + dbg!(&events); + assert!(events.iter().any(|event| match event { + ListenerEvent::Message(msg) => msg.topic == topic, + _ => false, + })); + }); + } +} diff --git a/ntfy-daemon/src/output_tracker.rs b/ntfy-daemon/src/output_tracker.rs new file mode 100644 index 0000000..00423f7 --- /dev/null +++ b/ntfy-daemon/src/output_tracker.rs @@ -0,0 +1,71 @@ +use std::{cell::RefCell, rc::Rc, sync::Arc}; + +use tokio::sync::RwLock; + +#[derive(Clone)] +pub struct OutputTracker { + store: Rc>>>, +} + +impl Default for OutputTracker { + fn default() -> Self { + Self { + store: Default::default(), + } + } +} + +impl OutputTracker { + pub fn enable(&self) { + let mut inner = self.store.borrow_mut(); + if inner.is_none() { + *inner = Some(vec![]); + } + } + pub fn push(&self, item: T) { + if let Some(v) = &mut *self.store.borrow_mut() { + v.push(item); + } + } + pub fn items(&self) -> Vec { + if let Some(v) = &*self.store.borrow() { + v.clone() + } else { + vec![] + } + } +} + +#[derive(Clone)] +pub struct OutputTrackerAsync { + store: Arc>>>, +} + +impl Default for OutputTrackerAsync { + fn default() -> Self { + Self { + store: Default::default(), + } + } +} + +impl OutputTrackerAsync { + pub async fn enable(&self) { + let mut inner = self.store.write().await; + if inner.is_none() { + *inner = Some(vec![]); + } + } + pub async fn push(&self, item: T) { + if let Some(v) = &mut *self.store.write().await { + v.push(item); + } + } + pub async fn items(&self) -> Vec { + if let Some(v) = &*self.store.read().await { + v.clone() + } else { + vec![] + } + } +} diff --git a/ntfy-daemon/src/retry.rs b/ntfy-daemon/src/retry.rs index 0126a35..8c98360 100644 --- a/ntfy-daemon/src/retry.rs +++ b/ntfy-daemon/src/retry.rs @@ -53,4 +53,8 @@ impl WaitExponentialRandom { sleep(self.next_delay()).await; self.i += 1; } + + pub fn count(&self) -> u64 { + self.i + } } diff --git a/ntfy-daemon/src/subscription.rs b/ntfy-daemon/src/subscription.rs new file mode 100644 index 0000000..9643443 --- /dev/null +++ b/ntfy-daemon/src/subscription.rs @@ -0,0 +1,276 @@ +use crate::listener::{ListenerEvent, ListenerHandle}; +use crate::models::{self, ReceivedMessage}; +use crate::{Error, SharedEnv}; +use tokio::select; +use tokio::sync::{broadcast, mpsc, oneshot}; +use tokio::task::spawn_local; +use tracing::{debug, error, info, trace, warn}; + +#[derive(Debug)] +enum SubscriptionCommand { + GetModel { + resp_tx: oneshot::Sender, + }, + UpdateInfo { + new_model: models::Subscription, + resp_tx: oneshot::Sender>, + }, + Attach { + resp_tx: oneshot::Sender<(Vec, broadcast::Receiver)>, + }, + Publish { + msg: String, + resp_tx: oneshot::Sender>, + }, + ClearNotifications { + resp_tx: oneshot::Sender>, + }, + UpdateReadUntil { + timestamp: u64, + resp_tx: oneshot::Sender>, + }, +} + +#[derive(Clone)] +pub struct SubscriptionHandle { + command_tx: mpsc::Sender, + listener: ListenerHandle, +} + +impl SubscriptionHandle { + pub fn new(listener: ListenerHandle, model: models::Subscription, env: &SharedEnv) -> Self { + let (command_tx, command_rx) = mpsc::channel(32); + let broadcast_tx = broadcast::channel(8).0; + let actor = SubscriptionActor { + listener: listener.clone(), + model, + command_rx, + env: env.clone(), + broadcast_tx: broadcast_tx.clone(), + }; + spawn_local(actor.run()); + Self { + command_tx, + listener, + } + } + + pub async fn model(&self) -> models::Subscription { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::GetModel { resp_tx }) + .await + .unwrap(); + resp_rx.await.unwrap() + } + + pub async fn update_info(&self, new_model: models::Subscription) -> anyhow::Result<()> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::UpdateInfo { new_model, resp_tx }) + .await?; + resp_rx.await.unwrap() + } + + pub async fn restart(&self) -> anyhow::Result<()> { + self.listener + .commands + .send(crate::ListenerCommand::Restart) + .await?; + Ok(()) + } + + pub async fn shutdown(&self) -> anyhow::Result<()> { + self.listener + .commands + .send(crate::ListenerCommand::Shutdown) + .await?; + Ok(()) + } + + // returns a vector containing all the past messages stored in the database and the current connection state. + // The first vector is useful to get a summary of what happened before. + // The `ListenerHandle` is returned to receive new events. + pub async fn attach(&self) -> (Vec, broadcast::Receiver) { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::Attach { resp_tx }) + .await + .unwrap(); + resp_rx.await.unwrap() + } + + pub async fn publish(&self, msg: String) -> anyhow::Result<()> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::Publish { msg, resp_tx }) + .await + .unwrap(); + resp_rx.await.unwrap() + } + + pub async fn clear_notifications(&self) -> anyhow::Result<()> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::ClearNotifications { resp_tx }) + .await + .unwrap(); + resp_rx.await.unwrap() + } + + pub async fn update_read_until(&self, timestamp: u64) -> anyhow::Result<()> { + let (resp_tx, resp_rx) = oneshot::channel(); + self.command_tx + .send(SubscriptionCommand::UpdateReadUntil { timestamp, resp_tx }) + .await + .unwrap(); + resp_rx.await.unwrap() + } +} + +struct SubscriptionActor { + listener: ListenerHandle, + model: models::Subscription, + command_rx: mpsc::Receiver, + env: SharedEnv, + broadcast_tx: broadcast::Sender, +} + +impl SubscriptionActor { + async fn run(mut self) { + loop { + select! { + Ok(event) = self.listener.events.recv() => { + debug!(?event, "received listener event"); + match event { + ListenerEvent::Message(msg) => self.handle_msg_event(msg), + other => { + let _ = self.broadcast_tx.send(other); + } + } + } + Some(command) = self.command_rx.recv() => { + trace!(?command, "processing subscription command"); + match command { + SubscriptionCommand::GetModel { resp_tx } => { + debug!("getting subscription model"); + let _ = resp_tx.send(self.model.clone()); + } + SubscriptionCommand::UpdateInfo { + mut new_model, + resp_tx, + } => { + debug!(server=?new_model.server, topic=?new_model.topic, "updating subscription info"); + new_model.server = self.model.server.clone(); + new_model.topic = self.model.topic.clone(); + let res = self.env.db.update_subscription(new_model.clone()); + if let Ok(_) = res { + self.model = new_model; + } + let _ = resp_tx.send(res.map_err(|e| e.into())); + } + SubscriptionCommand::Publish {msg, resp_tx} => { + debug!(topic=?self.model.topic, "publishing message"); + let _ = resp_tx.send(self.publish(msg).await); + } + SubscriptionCommand::Attach { resp_tx } => { + debug!(topic=?self.model.topic, "attaching new listener"); + let messages = self + .env + .db + .list_messages(&self.model.server, &self.model.topic, 0) + .unwrap_or_default(); + let mut previous_events: Vec = messages + .into_iter() + .filter_map(|msg| { + let msg = serde_json::from_str(&msg); + match msg { + Err(e) => { + error!(error = ?e, "error parsing stored message"); + None + } + Ok(msg) => Some(msg), + } + }) + .map(ListenerEvent::Message) + .collect(); + previous_events.push(ListenerEvent::ConnectionStateChanged(self.listener.state().await)); + let _ = resp_tx.send((previous_events, self.broadcast_tx.subscribe())); + } + SubscriptionCommand::ClearNotifications {resp_tx} => { + debug!(topic=?self.model.topic, "clearing notifications"); + let _ = resp_tx.send(self.env.db.delete_messages(&self.model.server, &self.model.topic).map_err(|e| anyhow::anyhow!(e))); + } + SubscriptionCommand::UpdateReadUntil { timestamp, resp_tx } => { + debug!(topic=?self.model.topic, timestamp=timestamp, "updating read until timestamp"); + let res = self.env.db.update_read_until(&self.model.server, &self.model.topic, timestamp); + let _ = resp_tx.send(res.map_err(|e| anyhow::anyhow!(e))); + } + } + } + } + } + } + + async fn publish(&self, msg: String) -> anyhow::Result<()> { + let server = &self.model.server; + debug!(server=?server, "preparing to publish message"); + let creds = self.env.credentials.get(server); + let mut req = self.env.http_client.post(server); + if let Some(creds) = creds { + req = req.basic_auth(creds.username, Some(creds.password)); + } + + info!(server=?server, "sending message"); + let res = req.body(msg).send().await?; + res.error_for_status()?; + debug!(server=?server, "message published successfully"); + Ok(()) + } + fn handle_msg_event(&mut self, msg: ReceivedMessage) { + debug!(topic=?self.model.topic, "handling new message"); + // Store in database + let already_stored: bool = { + let json_ev = &serde_json::to_string(&msg).unwrap(); + match self.env.db.insert_message(&self.model.server, json_ev) { + Err(Error::DuplicateMessage) => { + warn!(topic=?self.model.topic, "received duplicate message"); + true + } + Err(e) => { + error!(error=?e, topic=?self.model.topic, "can't store the message"); + false + } + _ => { + debug!(topic=?self.model.topic, "message stored successfully"); + false + } + } + }; + + if !already_stored { + debug!(topic=?self.model.topic, muted=?self.model.muted, "checking if notification should be shown"); + // Show notification. If this fails, panic + if !{ self.model.muted } { + let notifier = self.env.notifier.clone(); + + let title = { msg.notification_title(&self.model) }; + + let n = models::Notification { + title, + body: msg.display_message().as_deref().unwrap_or("").to_string(), + actions: msg.actions.clone(), + }; + + info!(topic=?self.model.topic, "showing notification"); + notifier.send(n).unwrap(); + } else { + debug!(topic=?self.model.topic, "notification muted, skipping"); + } + + // Forward to app + debug!(topic=?self.model.topic, "forwarding message to app"); + let _ = self.broadcast_tx.send(ListenerEvent::Message(msg)); + } + } +} diff --git a/ntfy-daemon/src/system_client.rs b/ntfy-daemon/src/system_client.rs deleted file mode 100644 index 4927583..0000000 --- a/ntfy-daemon/src/system_client.rs +++ /dev/null @@ -1,619 +0,0 @@ -use std::cell::{Cell, RefCell}; -use std::ops::ControlFlow; -use std::rc::{Rc, Weak}; -use std::sync::Arc; -use std::time::Duration; -use std::{collections::HashMap, hash::Hash}; - -use capnp::capability::Promise; -use capnp_rpc::{pry, rpc_twoparty_capnp, twoparty, RpcSystem}; -use futures::future::join_all; -use futures::prelude::*; -use generational_arena::Arena; -use tokio::net::UnixListener; -use tokio::sync::mpsc; -use tracing::{error, info, warn}; - -use crate::models::Message; -use crate::Error; -use crate::SharedEnv; -use crate::{ - message_repo::Db, - models::{self, MinMessage}, - ntfy_capnp::{output_channel, subscription, system_notifier, watch_handle, Status}, - topic_listener::{build_client, TopicListener}, -}; - -const MESSAGE_THROTTLE: Duration = Duration::from_millis(150); - -pub struct NotifyForwarder { - model: Rc>, - env: SharedEnv, - watching: Weak>>, - status: Rc>, -} -impl NotifyForwarder { - pub fn new( - model: Rc>, - env: SharedEnv, - watching: Weak>>, - status: Rc>, - ) -> Self { - Self { - model, - env, - watching, - status, - } - } -} - -impl output_channel::Server for NotifyForwarder { - // Stores the message, sends a system notification, forwards the message to watching clients - fn send_message( - &mut self, - params: output_channel::SendMessageParams, - _results: output_channel::SendMessageResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let request = pry!(params.get()); - let message = pry!(pry!(request.get_message()).to_str()); - - // Store in database - let already_stored: bool = { - // If this fails parsing, the message is not valid at all. - // The server is probably misbehaving. - let min_message: MinMessage = pry!(serde_json::from_str(message) - .map_err(|e| Error::InvalidMinMessage(message.to_string(), e))); - let model = self.model.borrow(); - match self.env.db.insert_message(&model.server, message) { - Err(Error::DuplicateMessage) => { - warn!(min_message = ?min_message, "Received duplicate message"); - true - } - Err(e) => { - error!(min_message = ?min_message, error = ?e, "Can't store the message"); - false - } - _ => false, - } - }; - - if !already_stored { - // Show notification - // Our priority is to show notifications. If anything fails, panic. - if !{ self.model.borrow().muted } { - let msg: Message = pry!(serde_json::from_str(message) - .map_err(|e| Error::InvalidMessage(message.to_string(), e))); - let np = self.env.proxy.clone(); - - let title = { msg.notification_title(&self.model.borrow()) }; - - let n = models::Notification { - title, - body: msg.display_message().as_deref().unwrap_or("").to_string(), - actions: msg.actions, - }; - - info!("Showing notification"); - np.send(n).unwrap(); - } - - // Forward - if let Some(watching) = self.watching.upgrade() { - let watching = watching.borrow(); - let futs = watching.iter().map(|(_id, w)| { - let mut req = w.send_message_request(); - req.get().set_message(message.into()); - async move { - if let Err(e) = req.send().promise.await { - error!(error = ?e, "Error forwarding"); - } - } - }); - tokio::task::spawn_local(join_all(futs)); - } - } - - Promise::from_future(async move { - // some backpressure - tokio::time::sleep(MESSAGE_THROTTLE).await; - Ok(()) - }) - } - - fn send_status( - &mut self, - params: output_channel::SendStatusParams, - _: output_channel::SendStatusResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let status = pry!(pry!(params.get()).get_status()); - if let Some(watching) = self.watching.upgrade() { - for (_, w) in watching.borrow().iter() { - let mut req = w.send_status_request(); - req.get().set_status(status); - tokio::task::spawn_local(async move { - req.send().promise.await.unwrap(); - }); - } - } - self.status.set(status); - Promise::ok(()) - } -} - -struct WatcherImpl { - id: generational_arena::Index, - watchers: Weak>>, -} - -impl watch_handle::Server for WatcherImpl {} - -impl Drop for WatcherImpl { - fn drop(&mut self) { - if let Some(w) = self.watchers.upgrade() { - w.borrow_mut().remove(self.id); - } - } -} - -pub struct SubscriptionImpl { - model: Rc>, - env: SharedEnv, - watchers: Rc>>, - status: Rc>, - topic_listener: mpsc::Sender>, -} - -impl Drop for SubscriptionImpl { - fn drop(&mut self) { - let t = self.topic_listener.clone(); - tokio::task::spawn_local(async move { - t.send(ControlFlow::Break(())).await.unwrap(); - }); - } -} - -impl SubscriptionImpl { - fn new(model: models::Subscription, env: SharedEnv) -> Self { - let status = Rc::new(Cell::new(Status::Down)); - let watchers = Default::default(); - let rc_model = Rc::new(RefCell::new(model.clone())); - let output_channel = NotifyForwarder::new( - rc_model.clone(), - env.clone(), - Rc::downgrade(&watchers), - status.clone(), - ); - let topic_listener = TopicListener::new( - env.clone(), - model.server.clone(), - model.topic.clone(), - model.read_until, - capnp_rpc::new_client(output_channel), - ); - Self { - model: rc_model, - env, - watchers, - status, - topic_listener, - } - } - - fn _publish<'a>(&'a mut self, msg: &'a str) -> impl Future> { - let msg = msg.to_owned(); - let server = &self.model.borrow().server; - let creds = self.env.credentials.get(server); - let mut req = self.env.http.post(server); - if let Some(creds) = creds { - req = req.basic_auth(creds.username, Some(creds.password)); - } - - async move { - info!("sending message"); - let res = req.body(msg).send().await; - match res { - Err(e) => Err(capnp::Error::failed(e.to_string())), - Ok(res) => { - res.error_for_status() - .map_err(|e| capnp::Error::failed(e.to_string()))?; - Ok(()) - } - } - } - } -} - -impl subscription::Server for SubscriptionImpl { - fn watch( - &mut self, - params: subscription::WatchParams, - mut results: subscription::WatchResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let watcher = pry!(pry!(params.get()).get_watcher()); - let since = pry!(params.get()).get_since(); - - // Send old messages - let msgs = { - let model = self.model.borrow(); - pry!(self - .env - .db - .list_messages(&model.server, &model.topic, since) - .map_err(Error::Db)) - }; - - let futs = msgs.into_iter().map(move |msg| { - let mut req = watcher.send_message_request(); - req.get().set_message(msg.as_str().into()); - req.send().promise - }); - - let watcher = pry!(pry!(params.get()).get_watcher()); - let mut req = watcher.send_status_request(); - req.get().set_status(self.status.get()); - - let id = { self.watchers.borrow_mut().insert(watcher) }; - - results.get().set_handle(capnp_rpc::new_client(WatcherImpl { - id, - watchers: Rc::downgrade(&self.watchers), - })); - - Promise::from_future(async move { - futures::future::try_join_all(futs).await?; - req.send().promise.await?; - Ok(()) - }) - } - - fn publish( - &mut self, - params: subscription::PublishParams, - _results: subscription::PublishResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let msg = pry!(pry!(pry!(params.get()).get_message()).to_str()); - let fut = self._publish(msg); - - Promise::from_future(async move { - fut.await?; - Ok(()) - }) - } - fn get_info( - &mut self, - _: subscription::GetInfoParams, - mut results: subscription::GetInfoResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let mut res = results.get(); - let model = self.model.borrow(); - res.set_server(model.server.as_str().into()); - res.set_display_name(model.display_name.as_str().into()); - res.set_topic(model.topic.as_str().into()); - res.set_muted(model.muted); - res.set_read_until(model.read_until); - Promise::ok(()) - } - fn update_info( - &mut self, - params: subscription::UpdateInfoParams, - _results: subscription::UpdateInfoResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let info = pry!(pry!(params.get()).get_value()); - let mut model = self.model.borrow_mut(); - model.display_name = pry!(pry!(info.get_display_name()).to_string()); - model.muted = info.get_muted(); - model.read_until = info.get_read_until(); - pry!(self.env.db.update_subscription(model.clone())); - Promise::ok(()) - } - fn clear_notifications( - &mut self, - _params: subscription::ClearNotificationsParams, - _results: subscription::ClearNotificationsResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let model = self.model.borrow_mut(); - pry!(self.env.db.delete_messages(&model.server, &model.topic)); - Promise::ok(()) - } - - fn update_read_until( - &mut self, - params: subscription::UpdateReadUntilParams, - _: subscription::UpdateReadUntilResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let value = pry!(params.get()).get_value(); - let mut model = self.model.borrow_mut(); - pry!(self - .env - .db - .update_read_until(&model.server, &model.topic, value)); - model.read_until = value; - Promise::ok(()) - } - fn refresh( - &mut self, - _: subscription::RefreshParams, - _: subscription::RefreshResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let sender = self.topic_listener.clone(); - Promise::from_future(async move { - sender - .send(ControlFlow::Continue(())) - .await - .map_err(|e| capnp::Error::failed(format!("{:?}", e)))?; - Ok(()) - }) - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct WatchKey { - server: String, - topic: String, -} -pub struct SystemNotifier { - watching: Rc>>, - env: SharedEnv, -} - -impl SystemNotifier { - pub fn new( - dbpath: &str, - notification_proxy: Arc, - network: Arc, - credentials: crate::credentials::Credentials, - ) -> Self { - Self { - watching: Rc::new(RefCell::new(HashMap::new())), - env: SharedEnv { - db: Db::connect(dbpath).unwrap(), - proxy: notification_proxy, - http: build_client().unwrap(), - network, - credentials, - }, - } - } - fn watch(&mut self, sub: models::Subscription) -> Promise { - let subscription = SubscriptionImpl::new(sub.clone(), self.env.clone()); - - let watching = self.watching.clone(); - let subc: subscription::Client = capnp_rpc::new_client(subscription); - - Promise::from_future(async move { - watching.borrow_mut().insert( - WatchKey { - server: sub.server.to_owned(), - topic: sub.topic.to_owned(), - }, - subc.clone(), - ); - Ok(subc) - }) - } - pub fn watch_subscribed(&mut self) -> Promise<(), capnp::Error> { - let f: Vec<_> = pry!(self.env.db.list_subscriptions()) - .into_iter() - .map(|m| self.watch(m)) - .collect(); - Promise::from_future(async move { - join_all(f.into_iter().map(|x| async move { - if let Err(e) = x.await { - error!(error = ?e, "Can't rewatch subscribed topic"); - } - })) - .await; - Ok(()) - }) - } - pub fn refresh_all(&mut self) -> Promise<(), capnp::Error> { - let watching = self.watching.clone(); - Promise::from_future(async move { - let reqs: Vec<_> = watching - .borrow() - .values() - .map(|w| w.refresh_request()) - .collect(); - join_all(reqs.into_iter().map(|x| x.send().promise)).await; - Ok(()) - }) - } -} - -impl system_notifier::Server for SystemNotifier { - fn subscribe( - &mut self, - params: system_notifier::SubscribeParams, - mut results: system_notifier::SubscribeResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let topic = pry!(pry!(pry!(params.get()).get_topic()).to_str()); - let server: &str = pry!(pry!(pry!(params.get()).get_server()).to_str()); - - let subscription = pry!(models::Subscription::builder(topic.to_owned()) - .server(server.to_string()) - .build() - .map_err(|e| capnp::Error::failed(format!("{:?}", e)))); - let sub: Promise = self.watch(subscription.clone()); - - let mut db = self.env.db.clone(); - Promise::from_future(async move { - results.get().set_subscription(sub.await?); - - db.insert_subscription(subscription).map_err(|e| { - capnp::Error::failed(format!("could not insert subscription: {}", e)) - })?; - Ok(()) - }) - } - fn unsubscribe( - &mut self, - params: system_notifier::UnsubscribeParams, - _results: system_notifier::UnsubscribeResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let topic = pry!(pry!(pry!(params.get()).get_topic()).to_str()); - let server = pry!(pry!(pry!(params.get()).get_server()).to_str()); - { - self.watching.borrow_mut().remove(&WatchKey { - server: server.to_string(), - topic: topic.to_string(), - }); - pry!(self - .env - .db - .remove_subscription(server, topic) - .map_err(|e| capnp::Error::failed(e.to_string()))); - info!(server, topic, "Unsubscribed"); - } - Promise::ok(()) - } - fn list_subscriptions( - &mut self, - _: system_notifier::ListSubscriptionsParams, - mut results: system_notifier::ListSubscriptionsResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let req = results.get(); - let values = self.watching.borrow().values().cloned().collect::>(); - let mut list = req.init_list(values.len() as u32); - - for (i, v) in values.iter().enumerate() { - use capnp::capability::FromClientHook; - list.set(i as u32, v.clone().clone().into_client_hook()); - } - - Promise::ok(()) - } - fn list_accounts( - &mut self, - _: system_notifier::ListAccountsParams, - mut results: system_notifier::ListAccountsResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let values = self.env.credentials.list_all(); - - Promise::from_future(async move { - let mut list = results.get().init_list(values.len() as u32); - for (i, item) in values.into_iter().enumerate() { - let mut acc = list.reborrow().get(i as u32); - acc.set_server(item.0[..].into()); - acc.set_username(item.1.username[..].into()); - } - Ok(()) - }) - } - fn add_account( - &mut self, - params: system_notifier::AddAccountParams, - _: system_notifier::AddAccountResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let credentials = self.env.credentials.clone(); - let http = self.env.http.clone(); - let refresh = self.refresh_all(); - Promise::from_future(async move { - let account = params.get()?.get_account()?; - let username = account.get_username()?.to_str()?; - let server = account.get_server()?.to_str()?; - let password = params.get()?.get_password()?.to_str()?; - - info!("validating account"); - let url = models::Subscription::build_auth_url(server, "stats")?; - - http.get(url) - .basic_auth(username, Some(password)) - .send() - .await - .map_err(|e| capnp::Error::failed(e.to_string()))? - .error_for_status() - .map_err(|e| capnp::Error::failed(e.to_string()))?; - - credentials - .insert(server, username, password) - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; - refresh.await?; - - info!(server = %server, username = %username, "added account"); - - Ok(()) - }) - } - fn remove_account( - &mut self, - params: system_notifier::RemoveAccountParams, - _: system_notifier::RemoveAccountResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - let credentials = self.env.credentials.clone(); - Promise::from_future(async move { - let account = params.get()?.get_account()?; - let username = account.get_username()?.to_str()?; - let server = account.get_server()?.to_str()?; - - credentials - .delete(server) - .await - .map_err(|e| capnp::Error::failed(e.to_string()))?; - - info!(server = %server, username = %username, "removed account"); - - Ok(()) - }) - } -} - -pub fn start( - socket_path: std::path::PathBuf, - dbpath: &str, - notification_proxy: Arc, - network_proxy: Arc, -) -> anyhow::Result<()> { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build()?; - - let listener = rt.block_on(async move { - let _ = std::fs::remove_file(&socket_path); - UnixListener::bind(&socket_path).unwrap() - }); - - let dbpath = dbpath.to_owned(); - let f = move || { - let credentials = - rt.block_on(async { crate::credentials::Credentials::new().await.unwrap() }); - let local = tokio::task::LocalSet::new(); - let mut system_notifier = - SystemNotifier::new(&dbpath, notification_proxy, network_proxy, credentials); - local.spawn_local(async move { - system_notifier.watch_subscribed().await.unwrap(); - let system_client: system_notifier::Client = capnp_rpc::new_client(system_notifier); - - loop { - match listener.accept().await { - Ok((stream, _addr)) => { - info!("client connected"); - let (reader, writer) = - tokio_util::compat::TokioAsyncReadCompatExt::compat(stream).split(); - let network = twoparty::VatNetwork::new( - reader, - writer, - rpc_twoparty_capnp::Side::Server, - Default::default(), - ); - - let rpc_system = - RpcSystem::new(Box::new(network), Some(system_client.clone().client)); - - tokio::task::spawn_local(rpc_system); - } - Err(e) => { - error!(error=%e); - } - } - } - }); - rt.block_on(local); - }; - std::thread::spawn(move || { - f(); - }); - - Ok(()) -} diff --git a/ntfy-daemon/src/topic_listener.rs b/ntfy-daemon/src/topic_listener.rs deleted file mode 100644 index c1041c8..0000000 --- a/ntfy-daemon/src/topic_listener.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::ops::ControlFlow; -use std::sync::Arc; -use std::time::Duration; - -use futures::prelude::*; -use serde::{Deserialize, Serialize}; -use tokio::io::AsyncBufReadExt; -use tokio::sync::mpsc; -use tokio_stream::wrappers::LinesStream; -use tracing::warn; -use tracing::{debug, error, info, instrument, Instrument}; - -use crate::{ - models, - ntfy_capnp::{output_channel, Status}, - Error, SharedEnv, -}; - -const CONNECT_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(15); -const TIMEOUT: std::time::Duration = std::time::Duration::from_secs(240); // 4 minutes - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "event")] -pub enum Event { - #[serde(rename = "open")] - Open { - id: String, - time: usize, - expires: Option, - topic: String, - }, - #[serde(rename = "message")] - Message { - id: String, - expires: Option, - #[serde(flatten)] - message: models::Message, - }, - #[serde(rename = "keepalive")] - KeepAlive { - id: String, - time: usize, - expires: Option, - topic: String, - }, -} - -pub fn build_client() -> anyhow::Result { - Ok(reqwest::Client::builder() - .connect_timeout(CONNECT_TIMEOUT) - .pool_idle_timeout(TIMEOUT) - // rustls is used because HTTP 2 isn't discovered with native-tls. - // HTTP 2 is required to multiplex multiple requests over a single connection. - // You can check that the app is using a single connection to a server by doing - // ``` - // ping ntfy.sh # to get the ip address - // netstat | grep $ip - // ``` - .use_rustls_tls() - .build()?) -} - -fn topic_request( - client: &reqwest::Client, - endpoint: &str, - topic: &str, - since: u64, - username: Option<&str>, - password: Option<&str>, -) -> anyhow::Result { - let url = models::Subscription::build_url(endpoint, topic, since)?; - let mut req = client - .get(url) - .header("Content-Type", "application/x-ndjson") - .header("Transfer-Encoding", "chunked"); - if let Some(username) = username { - req = req.basic_auth(username, password); - } - - Ok(req.build()?) -} - -async fn response_lines( - res: impl tokio::io::AsyncBufRead, -) -> Result>, reqwest::Error> { - let lines = LinesStream::new(res.lines()); - Ok(lines) -} - -pub enum BroadcasterEvent { - Stop, - Restart, -} - -pub struct TopicListener { - env: crate::SharedEnv, - endpoint: String, - topic: String, - status: Status, - output_channel: output_channel::Client, - since: u64, -} - -impl TopicListener { - pub fn new( - env: SharedEnv, - endpoint: String, - topic: String, - since: u64, - output_channel: output_channel::Client, - ) -> mpsc::Sender> { - let (tx, mut rx) = mpsc::channel(8); - let network = env.network.clone(); - let mut this = Self { - env, - endpoint, - topic, - status: Status::Down, - output_channel, - since, - }; - - tokio::task::spawn_local(async move { - loop { - tokio::select! { - _ = this.run_supervised_loop().instrument(tracing::debug_span!("run_supervised_loop")) => {}, - res = rx.recv() => match res { - Some(ControlFlow::Continue(_)) => { - info!("Refreshed"); - } - None | Some(ControlFlow::Break(_)) => { - break; - } - } - } - } - }); - - let tx_clone = tx.clone(); - tokio::task::spawn_local(async move { - if let Err(e) = Self::reload_on_network_change(network, tx_clone.clone()).await { - warn!(error = %e, "watching network failed") - } - }); - - tx - } - - async fn reload_on_network_change( - monitor: Arc, - tx: mpsc::Sender>, - ) -> anyhow::Result<()> { - let mut m = monitor.listen(); - while let Some(_) = m.next().await { - tx.send(ControlFlow::Continue(())).await?; - } - Ok(()) - } - - fn send_current_status(&mut self) -> impl Future> { - let mut req = self.output_channel.send_status_request(); - req.get().set_status(self.status); - async move { - req.send().promise.await?; - Ok(()) - } - } - - #[instrument(skip_all)] - async fn recv_and_forward(&mut self) -> anyhow::Result<()> { - let creds = self.env.credentials.get(&self.endpoint); - let req = topic_request( - &self.env.http, - &self.endpoint, - &self.topic, - self.since, - creds.as_ref().map(|x| x.username.as_str()), - creds.as_ref().map(|x| x.password.as_str()), - ); - let res = self.env.http.execute(req?).await?; - let reader = tokio_util::io::StreamReader::new( - res.bytes_stream() - .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())), - ); - let stream = response_lines(reader).await?; - tokio::pin!(stream); - self.status = Status::Up; - self.send_current_status().await.unwrap(); - info!(topic = %&self.topic, "listening"); - while let Some(msg) = stream.next().await { - let msg = msg?; - - let min_msg = serde_json::from_str::(&msg) - .map_err(|e| Error::InvalidMinMessage(msg.to_string(), e))?; - self.since = min_msg.time.max(self.since); - - let event = serde_json::from_str(&msg) - .map_err(|e| Error::InvalidMessage(msg.to_string(), e))?; - - match event { - Event::Message { .. } => { - debug!("message event"); - let mut req = self.output_channel.send_message_request(); - req.get().set_message(msg.as_str().into()); - req.send().promise.await?; - } - Event::KeepAlive { .. } => { - debug!("keepalive event"); - } - Event::Open { .. } => { - debug!("open event"); - } - } - } - - Ok(()) - } - async fn run_supervised_loop(&mut self) { - let retrier = || { - crate::retry::WaitExponentialRandom::builder() - .min(Duration::from_secs(1)) - .max(Duration::from_secs(5 * 60)) - .build() - }; - let mut retry = retrier(); - loop { - let start_time = std::time::Instant::now(); - if let Err(e) = self.recv_and_forward().await { - let uptime = std::time::Instant::now().duration_since(start_time); - // Reset retry delay to minimum if uptime was decent enough - if uptime > Duration::from_secs(60 * 4) { - retry = retrier(); - } - error!(error = ?e); - self.status = Status::Degraded; - self.send_current_status().await.unwrap(); - info!(delay = ?retry.next_delay(), "restarting"); - retry.wait().await; - } else { - break; - } - } - } -} diff --git a/src/application.rs b/src/application.rs index a669a11..ee47cc4 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,19 +1,13 @@ use std::cell::Cell; -use std::path::Path; -use std::path::PathBuf; use std::pin::Pin; use std::rc::Rc; use adw::prelude::*; use adw::subclass::prelude::*; -use capnp_rpc::{rpc_twoparty_capnp, twoparty, RpcSystem}; use futures::stream::Stream; -use futures::AsyncReadExt; -use gio::SocketClient; -use gio::UnixSocketAddress; use gtk::{gdk, gio, glib}; use ntfy_daemon::models; -use ntfy_daemon::ntfy_capnp::system_notifier; +use ntfy_daemon::NtfyHandle; use tracing::{debug, error, info, warn}; use crate::config::{APP_ID, PKGDATADIR, PROFILE, VERSION}; @@ -30,8 +24,8 @@ mod imp { #[derive(Default)] pub struct NotifyApplication { pub window: RefCell>, - pub socket_path: RefCell, pub hold_guard: OnceCell, + pub ntfy: OnceCell, } #[glib::object_subclass] @@ -58,8 +52,6 @@ mod imp { // Set icons for shell gtk::Window::set_default_icon_name(APP_ID); - let socket_path = glib::user_data_dir().join("com.ranfdev.Notify.socket"); - self.socket_path.replace(socket_path); app.setup_css(); app.setup_gactions(); app.setup_accels(); @@ -71,7 +63,7 @@ mod imp { let app = self.obj(); if self.hold_guard.get().is_none() { - app.ensure_rpc_running(&self.socket_path.borrow()); + app.ensure_rpc_running(); } glib::MainContext::default().spawn_local(async move { @@ -108,7 +100,7 @@ impl NotifyApplication { return; } } - self.build_window(&self.imp().socket_path.borrow()); + self.build_window(); self.main_window().present(); } @@ -253,7 +245,7 @@ impl NotifyApplication { Ok(()) } - fn ensure_rpc_running(&self, socket_path: &Path) { + fn ensure_rpc_running(&self) { let dbpath = glib::user_data_dir().join("com.ranfdev.Notify.sqlite"); info!(database_path = %dbpath.display()); @@ -317,42 +309,19 @@ impl NotifyApplication { } } let proxies = std::sync::Arc::new(Proxies { notification: s }); - ntfy_daemon::system_client::start( - socket_path.to_owned(), - dbpath.to_str().unwrap(), - proxies.clone(), - proxies, - ) - .unwrap(); + let ntfy = ntfy_daemon::start(dbpath.to_str().unwrap(), proxies.clone(), proxies).unwrap(); + self.imp() + .ntfy + .set(ntfy) + .or(Err(anyhow::anyhow!("failed setting ntfy"))) + .unwrap(); self.imp().hold_guard.set(self.hold()).unwrap(); } - fn build_window(&self, socket_path: &Path) { - let address = UnixSocketAddress::new(socket_path); - let client = SocketClient::new(); - let connection = - SocketClientExt::connect(&client, &address, gio::Cancellable::NONE).unwrap(); - - let rw = connection.into_async_read_write().unwrap(); - let (reader, writer) = rw.split(); - - let rpc_network = Box::new(twoparty::VatNetwork::new( - reader, - writer, - rpc_twoparty_capnp::Side::Client, - Default::default(), - )); - let mut rpc_system = RpcSystem::new(rpc_network, None); - let client: system_notifier::Client = - rpc_system.bootstrap(rpc_twoparty_capnp::Side::Server); - - glib::MainContext::default().spawn_local(async move { - debug!("rpc_system started"); - rpc_system.await.unwrap(); - debug!("rpc_system stopped"); - }); + fn build_window(&self) { + let ntfy = self.imp().ntfy.get().unwrap(); - let window = NotifyWindow::new(self, client); + let window = NotifyWindow::new(self, ntfy.clone()); *self.imp().window.borrow_mut() = window.downgrade(); } } diff --git a/src/subscription.rs b/src/subscription.rs index e161e6d..dcb68c4 100644 --- a/src/subscription.rs +++ b/src/subscription.rs @@ -1,56 +1,36 @@ use std::cell::{Cell, OnceCell, RefCell}; +use std::future::Future; use std::rc::Rc; use adw::prelude::*; -use capnp::capability::Promise; -use capnp_rpc::pry; use glib::subclass::prelude::*; use glib::Properties; use gtk::{gio, glib}; -use ntfy_daemon::models; -use ntfy_daemon::ntfy_capnp::{output_channel, subscription, watch_handle, Status}; -use tracing::{debug, error, instrument}; +use ntfy_daemon::{models, ConnectionState, ListenerEvent}; +use tracing::{error, instrument}; -struct TopicWatcher { - sub: glib::WeakRef, +#[repr(u16)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Status { + Down = 0, + Degraded = 1, + Up = 2, } -impl output_channel::Server for TopicWatcher { - fn send_message( - &mut self, - params: output_channel::SendMessageParams, - _results: output_channel::SendMessageResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - if let Some(sub) = self.sub.upgrade() { - let request = pry!(params.get()); - let message = pry!(pry!(request.get_message()).to_str()); - let msg: models::Message = serde_json::from_str(message).unwrap(); - sub.imp().messages.append(&glib::BoxedAnyObject::new(msg)); - sub.update_unread_count(); - Promise::ok(()) - } else { - Promise::err(capnp::Error::failed("dead channel".to_string())) - } - } - fn send_status( - &mut self, - params: output_channel::SendStatusParams, - _: output_channel::SendStatusResults, - ) -> capnp::capability::Promise<(), capnp::Error> { - if let Some(sub) = self.sub.upgrade() { - let status = pry!(pry!(params.get()).get_status()); - sub.imp().status.set(status); - sub.notify_status(); - Promise::ok(()) - } else { - Promise::err(capnp::Error::failed("dead channel".to_string())) +impl From for Status { + fn from(value: u16) -> Self { + match value { + 0 => Status::Down, + 1 => Status::Degraded, + 2 => Status::Up, + _ => panic!("Invalid value for Status"), } } } -impl Drop for TopicWatcher { - fn drop(&mut self) { - debug!("Dropped topic watcher"); +impl From for u16 { + fn from(status: Status) -> Self { + status as u16 } } @@ -76,8 +56,7 @@ mod imp { pub unread_count: Cell, pub read_until: Cell, pub messages: gio::ListStore, - pub client: OnceCell, - pub remote_handle: RefCell>, + pub client: OnceCell, } impl Subscription { @@ -100,7 +79,6 @@ mod imp { client: Default::default(), unread_count: Default::default(), read_until: Default::default(), - remote_handle: Default::default(), } } } @@ -120,7 +98,7 @@ glib::wrapper! { } impl Subscription { - pub fn new(client: subscription::Client) -> Self { + pub fn new(client: ntfy_daemon::SubscriptionHandle) -> Self { let this: Self = glib::Object::builder().build(); let imp = this.imp(); if let Err(_) = imp.client.set(client) { @@ -159,34 +137,54 @@ impl Subscription { self._set_display_name(display_name.to_string()); } - fn load(&self) -> Promise<(), capnp::Error> { - let imp = self.imp(); - let req_info = imp.client.get().unwrap().get_info_request(); - let req_messages = { - let mut req = imp.client.get().unwrap().watch_request(); - req.get().set_watcher(capnp_rpc::new_client(TopicWatcher { - sub: self.downgrade(), - })); - req - }; - + fn load(&self) -> impl Future> { let this = self.clone(); - Promise::from_future(async move { - let info = req_info.send().promise.await?; - let info = info.get()?; + async move { + let remote_subscription = this.imp().client.get().unwrap(); + let model = remote_subscription.model().await; + this.init_info( - info.get_topic()?.to_str()?, - info.get_server()?.to_str()?, - info.get_muted(), - info.get_read_until(), - info.get_display_name()?.to_str()?, + &model.topic, + &model.server, + model.muted, + model.read_until, + &model.display_name, ); - let message_stream = req_messages.send().promise.await?; - let handle = message_stream.get()?.get_handle()?; - this.imp().remote_handle.replace(Some(handle)); + let (prev_msgs, mut rx) = remote_subscription.attach().await; + + for msg in prev_msgs { + this.handle_event(msg); + } + + while let Ok(ev) = rx.recv().await { + this.handle_event(ev); + } Ok(()) - }) + } + } + + fn handle_event(&self, ev: ListenerEvent) { + match ev { + ListenerEvent::Message(msg) => { + self.imp().messages.append(&glib::BoxedAnyObject::new(msg)); + self.update_unread_count(); + } + ListenerEvent::ConnectionStateChanged(connection_state) => { + self.set_connection_state(connection_state); + } + } + } + + fn set_connection_state(&self, state: ConnectionState) { + let status = match state { + ConnectionState::Unitialized => Status::Degraded, + ConnectionState::Connected => Status::Up, + ConnectionState::Reconnecting { .. } => Status::Degraded, + }; + self.imp().status.set(status); + dbg!(status); + self.notify_status(); } fn _set_display_name(&self, value: String) { @@ -200,34 +198,36 @@ impl Subscription { self.notify_display_name(); } #[instrument(skip_all)] - pub fn set_display_name(&self, value: String) -> Promise<(), anyhow::Error> { + pub fn set_display_name(&self, value: String) -> impl Future> { let this = self.clone(); - Promise::from_future(async move { + async move { this._set_display_name(value); this.send_updated_info().await?; Ok(()) - }) + } } - fn send_updated_info(&self) -> Promise<(), anyhow::Error> { + async fn send_updated_info(&self) -> anyhow::Result<()> { let imp = self.imp(); - let mut req = imp.client.get().unwrap().update_info_request(); - let mut val = pry!(req.get().get_value()); - val.set_muted(imp.muted.get()); - val.set_display_name(imp.display_name.borrow().as_str().into()); - val.set_read_until(imp.read_until.get()); - Promise::from_future(async move { - debug!("sending update_info"); - req.send().promise.await?; - Ok(()) - }) + imp.client + .get() + .unwrap() + .update_info( + models::Subscription::builder(self.topic()) + .display_name((imp.display_name.borrow().to_string())) + .muted(imp.muted.get()) + .build() + .map_err(|e| anyhow::anyhow!("invalid subscription data {:?}", e))?, + ) + .await?; + Ok(()) } - fn last_message(list: &gio::ListStore) -> Option { + fn last_message(list: &gio::ListStore) -> Option { let n = list.n_items(); let last = list .item(n.checked_sub(1)?) .and_downcast::()?; - let last = last.borrow::(); + let last = last.borrow::(); Some(last.clone()) } fn update_unread_count(&self) { @@ -240,60 +240,52 @@ impl Subscription { self.notify_unread_count(); } - pub fn set_muted(&self, value: bool) -> Promise<(), anyhow::Error> { + pub fn set_muted(&self, value: bool) -> impl Future> { let this = self.clone(); - Promise::from_future(async move { + async move { this.imp().muted.replace(value); this.notify_muted(); this.send_updated_info().await?; Ok(()) - }) + } } - pub fn flag_all_as_read(&self) -> Promise<(), anyhow::Error> { + pub async fn flag_all_as_read(&self) -> anyhow::Result<()> { let imp = self.imp(); let Some(value) = Self::last_message(&imp.messages) .map(|last| last.time) .filter(|time| *time > self.imp().read_until.get()) else { - return Promise::ok(()); + return Ok(()); }; let this = self.clone(); - Promise::from_future(async move { - let mut req = this.imp().client.get().unwrap().update_read_until_request(); - req.get().set_value(value); - req.send().promise.await?; - this.imp().read_until.set(value); - this.update_unread_count(); - Ok(()) - }) + this.imp() + .client + .get() + .unwrap() + .update_read_until(value) + .await?; + this.imp().read_until.set(value); + this.update_unread_count(); + + Ok(()) } - pub fn publish_msg(&self, mut msg: models::Message) -> Promise<(), anyhow::Error> { + pub async fn publish_msg(&self, mut msg: models::OutgoingMessage) -> anyhow::Result<()> { let imp = self.imp(); let json = { msg.topic = self.topic(); - serde_json::to_string(&msg) + serde_json::to_string(&msg)? }; - let mut req = imp.client.get().unwrap().publish_request(); - req.get().set_message(pry!(json).as_str().into()); - - Promise::from_future(async move { - debug!("sending publish"); - req.send().promise.await?; - Ok(()) - }) + imp.client.get().unwrap().publish(json).await?; + Ok(()) } #[instrument(skip_all)] - pub fn clear_notifications(&self) -> Promise<(), anyhow::Error> { + pub async fn clear_notifications(&self) -> anyhow::Result<()> { let imp = self.imp(); - let req = imp.client.get().unwrap().clear_notifications_request(); - let this = self.clone(); - Promise::from_future(async move { - debug!("sending clear_notifications"); - req.send().promise.await?; - this.imp().messages.remove_all(); - Ok(()) - }) + imp.client.get().unwrap().clear_notifications().await?; + self.imp().messages.remove_all(); + + Ok(()) } pub fn nice_status(&self) -> Status { diff --git a/src/widgets/add_subscription_dialog.rs b/src/widgets/add_subscription_dialog.rs index 004b176..a29a3b2 100644 --- a/src/widgets/add_subscription_dialog.rs +++ b/src/widgets/add_subscription_dialog.rs @@ -166,7 +166,7 @@ impl AddSubscriptionDialog { obj.set_content_width(480); obj.set_child(Some(&toolbar_view)); } - pub fn subscription(&self) -> Result> { + pub fn subscription(&self) -> Result { let w = { self.imp().widgets.borrow().clone() }; let mut sub = models::Subscription::builder(w.topic_entry.text().to_string()); if w.server_expander.enables_expansion() { @@ -183,7 +183,7 @@ impl AddSubscriptionDialog { w.topic_entry.remove_css_class("error"); w.sub_btn.set_sensitive(true); - if let Err(errs) = sub { + if let Err(ntfy_daemon::Error::InvalidSubscription(errs)) = sub { w.sub_btn.set_sensitive(false); for e in errs { match e { diff --git a/src/widgets/advanced_message_dialog.rs b/src/widgets/advanced_message_dialog.rs index d219dbd..d181eb0 100644 --- a/src/widgets/advanced_message_dialog.rs +++ b/src/widgets/advanced_message_dialog.rs @@ -182,7 +182,7 @@ impl AdvancedMessageDialog { &mut buffer.start_iter(), &mut buffer.end_iter(), true, - )).map_err(|e| capnp::Error::failed(e.to_string()))?; + ))?; thisc.imp().subscription.get().unwrap() .publish_msg(msg).await }; diff --git a/src/widgets/message_row.rs b/src/widgets/message_row.rs index 8f30606..e2c375f 100644 --- a/src/widgets/message_row.rs +++ b/src/widgets/message_row.rs @@ -34,12 +34,12 @@ glib::wrapper! { } impl MessageRow { - pub fn new(msg: models::Message) -> Self { + pub fn new(msg: models::ReceivedMessage) -> Self { let this: Self = glib::Object::new(); this.build_ui(msg); this } - fn build_ui(&self, msg: models::Message) { + fn build_ui(&self, msg: models::ReceivedMessage) { self.set_margin_top(8); self.set_margin_bottom(8); self.set_margin_start(8); diff --git a/src/widgets/preferences.rs b/src/widgets/preferences.rs index 2cd9540..16ead84 100644 --- a/src/widgets/preferences.rs +++ b/src/widgets/preferences.rs @@ -3,11 +3,12 @@ use std::cell::OnceCell; use adw::prelude::*; use adw::subclass::prelude::*; use gtk::{gio, glib}; -use ntfy_daemon::ntfy_capnp::system_notifier; use crate::error::*; mod imp { + use ntfy_daemon::NtfyHandle; + use super::*; #[derive(gtk::CompositeTemplate)] @@ -25,7 +26,7 @@ mod imp { pub added_accounts: TemplateChild, #[template_child] pub added_accounts_group: TemplateChild, - pub notifier: OnceCell, + pub notifier: OnceCell, } impl Default for NotifyPreferences { @@ -77,7 +78,7 @@ glib::wrapper! { } impl NotifyPreferences { - pub fn new(notifier: system_notifier::Client) -> Self { + pub fn new(notifier: ntfy_daemon::NtfyHandle) -> Self { let obj: Self = glib::Object::builder().build(); obj.imp() .notifier @@ -100,21 +101,15 @@ impl NotifyPreferences { pub async fn show_accounts(&self) -> anyhow::Result<()> { let imp = self.imp(); - let req = imp.notifier.get().unwrap().list_accounts_request(); - let res = req.send().promise.await?; - - let accounts = res.get()?.get_list()?; + let accounts = imp.notifier.get().unwrap().list_accounts().await?; imp.added_accounts_group.set_visible(!accounts.is_empty()); imp.added_accounts.remove_all(); for a in accounts { - let server = a.get_server()?.to_string()?; - let username = a.get_username()?.to_string()?; - let row = adw::ActionRow::builder() - .title(&server) - .subtitle(&username) + .title(&a.server) + .subtitle(&a.username) .build(); row.add_css_class("property"); row.add_suffix(&{ @@ -125,10 +120,9 @@ impl NotifyPreferences { let this = self.clone(); btn.connect_clicked(move |btn| { let this = this.clone(); - let username = username.clone(); - let server = server.clone(); + let a = a.clone(); btn.error_boundary() - .spawn(async move { this.remove_account(&server, &username).await }); + .spawn(async move { this.remove_account(&a.server).await }); }); btn }); @@ -142,29 +136,23 @@ impl NotifyPreferences { let server = imp.server_entry.text(); let username = imp.username_entry.text(); - let mut req = imp.notifier.get().unwrap().add_account_request(); - let mut acc = req.get().get_account()?; - acc.set_username(username[..].into()); - acc.set_server(server[..].into()); - req.get().set_password(password[..].into()); - - req.send().promise.await?; - + imp.notifier + .get() + .unwrap() + .add_account(&server, &username, &password) + .await?; self.show_accounts().await?; Ok(()) } - pub async fn remove_account(&self, server: &str, username: &str) -> anyhow::Result<()> { - let mut req = self.imp().notifier.get().unwrap().remove_account_request(); - let mut acc = req.get().get_account()?; - - acc.set_username(username[..].into()); - acc.set_server(server[..].into()); - - req.send().promise.await?; - + pub async fn remove_account(&self, server: &str) -> anyhow::Result<()> { + self.imp() + .notifier + .get() + .unwrap() + .remove_account(server) + .await?; self.show_accounts().await?; - Ok(()) } } diff --git a/src/widgets/window.rs b/src/widgets/window.rs index 67c1e14..dfa50c2 100644 --- a/src/widgets/window.rs +++ b/src/widgets/window.rs @@ -3,15 +3,15 @@ use std::cell::OnceCell; use adw::prelude::*; use adw::subclass::prelude::*; -use futures::prelude::*; use gtk::{gio, glib}; use ntfy_daemon::models; -use ntfy_daemon::ntfy_capnp::{system_notifier, Status}; +use ntfy_daemon::NtfyHandle; use tracing::warn; use crate::application::NotifyApplication; use crate::config::{APP_ID, PROFILE}; use crate::error::*; +use crate::subscription::Status; use crate::subscription::Subscription; use crate::widgets::*; @@ -52,7 +52,7 @@ mod imp { pub send_btn: TemplateChild, #[template_child] pub code_btn: TemplateChild, - pub notifier: OnceCell, + pub notifier: OnceCell, pub conn: OnceCell, pub settings: gio::Settings, pub banner_binding: Cell>, @@ -138,7 +138,8 @@ mod imp { }); klass.install_action("win.clear-notifications", None, |this, _, _| { this.selected_subscription().map(|sub| { - this.error_boundary().spawn(sub.clear_notifications()); + this.error_boundary() + .spawn(async move { sub.clear_notifications().await }); }); }); //klass.bind_template_instance_callbacks(); @@ -190,7 +191,7 @@ glib::wrapper! { } impl NotifyWindow { - pub fn new(app: &NotifyApplication, notifier: system_notifier::Client) -> Self { + pub fn new(app: &NotifyApplication, notifier: NtfyHandle) -> Self { let obj: Self = glib::Object::builder().property("application", app).build(); if let Err(_) = obj.imp().notifier.set(notifier) { @@ -211,24 +212,25 @@ impl NotifyWindow { fn connect_entry_and_send_btn(&self) { let imp = self.imp(); let this = self.clone(); - let entry = imp.entry.clone(); - let publish = move || { - let p = this - .selected_subscription() + + imp.entry.connect_activate(move |_| this.publish_msg()); + let this = self.clone(); + imp.send_btn.connect_clicked(move |_| this.publish_msg()); + } + fn publish_msg(&self) { + let entry = self.imp().entry.clone(); + let this = self.clone(); + + entry.error_boundary().spawn(async move { + this.selected_subscription() .unwrap() - .publish_msg(models::Message { + .publish_msg(models::OutgoingMessage { message: Some(entry.text().as_str().to_string()), - ..models::Message::default() - }); - - entry.error_boundary().spawn(async move { - p.await?; - Ok(()) - }); - }; - let publishc = publish.clone(); - imp.entry.connect_activate(move |_| publishc()); - imp.send_btn.connect_clicked(move |_| publish()); + ..models::OutgoingMessage::default() + }) + .await?; + Ok(()) + }); } fn connect_code_btn(&self) { let imp = self.imp(); @@ -260,19 +262,14 @@ impl NotifyWindow { } fn add_subscription(&self, sub: models::Subscription) { - let mut req = self.notifier().subscribe_request(); - - req.get().set_server(sub.server.as_str().into()); - req.get().set_topic(sub.topic.as_str().into()); - let res = req.send(); let this = self.clone(); self.error_boundary().spawn(async move { + let sub = this.notifier().subscribe(&sub.server, &sub.topic).await?; let imp = this.imp(); // Subscription::new will use the pipelined client to retrieve info about the subscription - let subscription = Subscription::new(res.pipeline.get_subscription()); + let subscription = Subscription::new(sub); // We want to still check if there were any errors adding the subscription. - res.promise.await?; imp.subscription_list_model.append(&subscription); let i = imp.subscription_list_model.n_items() - 1; @@ -283,26 +280,22 @@ impl NotifyWindow { } fn unsubscribe(&self) { - let mut req = self.notifier().unsubscribe_request(); let sub = self.selected_subscription().unwrap(); - req.get().set_server(sub.server().as_str().into()); - req.get().set_topic(sub.topic().as_str().into()); - - let res = req.send(); let this = self.clone(); - self.error_boundary().spawn(async move { - let imp = this.imp(); - res.promise.await?; + this.notifier() + .unsubscribe(sub.server().as_str(), sub.topic().as_str()) + .await?; + let imp = this.imp(); if let Some(i) = imp.subscription_list_model.find(&sub) { imp.subscription_list_model.remove(i); } Ok(()) }); } - fn notifier(&self) -> &system_notifier::Client { + fn notifier(&self) -> &NtfyHandle { self.imp().notifier.get().unwrap() } fn selected_subscription(&self) -> Option { @@ -328,14 +321,13 @@ impl NotifyWindow { }); let this = self.clone(); - let req = self.notifier().list_subscriptions_request(); - let res = req.send(); self.error_boundary().spawn(async move { - let list = res.promise.await?; - let list = list.get()?.get_list()?; - let imp = this.imp(); + glib::timeout_future_seconds(1).await; + let list = this.notifier().list_subscriptions().await?; for sub in list { - imp.subscription_list_model.append(&Subscription::new(sub?)); + this.imp() + .subscription_list_model + .append(&Subscription::new(sub)); } Ok(()) }); @@ -371,7 +363,7 @@ impl NotifyWindow { imp.message_list .bind_model(Some(&sub.imp().messages), move |obj| { let b = obj.downcast_ref::().unwrap(); - let msg = b.borrow::(); + let msg = b.borrow::(); MessageRow::new(msg.clone()).upcast() }); @@ -402,7 +394,7 @@ impl NotifyWindow { { self.selected_subscription().map(|sub| { self.error_boundary() - .spawn(sub.flag_all_as_read().map_err(|e| e.into())); + .spawn(async move { sub.flag_all_as_read().await }); }); } }