commit 551f4ec3ab257d3208250f00432c573e9204f3be Author: Pakin Date: Fri Feb 27 14:09:21 2026 +0700 init: v1 Signed-off-by: Pakin diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3948cc8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +*.json +*.txt +.env +target \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a074a28 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.json +.env +target +*.txt \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5d955e5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3220 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arcstr" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" + +[[package]] +name = "async-compression" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68650b7df54f0293fd061972a0fb05aaf4fc0879d3b3d21a638a182c5c543b9f" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "base64", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-streams" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49debcb7f7400a7f2c96d590795c12372f4db7ad0f518d5a02286d8c8b8c691d" +dependencies = [ + "axum", + "bytes", + "futures", + "http", + "http-body", + "serde", + "serde_json", + "tokio-stream", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "celes" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" +dependencies = [ + "phf 0.11.3", + "serde", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "chrono-tz" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" +dependencies = [ + "chrono", + "phf 0.12.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "brotli", + "compression-core", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "croner" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aa42bcd3d846ebf66e15bd528d1087f75d1c6c1c66ebff626178a106353c576" +dependencies = [ + "chrono", + "derive_builder", + "strum", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "git2" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "openssl-probe 0.1.6", + "openssl-sys", + "url", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libgit2-sys" +version = "0.18.3+1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +dependencies = [ + "cc", + "libc", + "libssh2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "libssh2-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" +dependencies = [ + "cc", + "libc", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libtbr" +version = "0.1.1" +source = "git+https://pakin-inspiron-15-3530.tail110d9.ts.net/pakin/libtbr.git#4aa9ab3cfb904fa592dbb3e6943db5c8b8c752ae" +dependencies = [ + "chrono", + "flate2", + "git2", + "log", + "rand 0.9.2", + "rayon", + "serde", + "serde_json", + "tar", + "walkdir", + "zip", +] + +[[package]] +name = "libz-sys" +version = "1.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" +dependencies = [ + "phf_shared 0.12.1", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.17", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.17", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redis" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfe20977fe93830c0e9817a16fbf1ed1cfd8d4bba366087a1841d2c6033c251" +dependencies = [ + "arcstr", + "bytes", + "cfg-if", + "combine", + "futures-util", + "itoa", + "num-bigint", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2", + "tokio", + "tokio-util", + "url", + "xxhash-rust", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "server-mark2-dev" +version = "0.1.0" +dependencies = [ + "async-compression", + "axum", + "axum-streams", + "celes", + "chrono", + "crossbeam-queue", + "dotenv", + "futures", + "libtbr", + "rayon", + "redis", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-cron-scheduler", + "tokio-stream", + "uuid", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[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 = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-cron-scheduler" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f50e41f200fd8ed426489bd356910ede4f053e30cebfbd59ef0f856f0d7432a" +dependencies = [ + "chrono", + "chrono-tz", + "croner", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "sha1", + "thiserror 2.0.17", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +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", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f852905151ac8d4d06fdca66520a661c09730a74c6d4e2b0f27b436b382e532" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7fc0931 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "server-mark2-dev" +version = "0.1.0" +edition = "2024" + +[dependencies] +async-compression = { version = "0.4.39", features = ["tokio", "brotli"] } +axum = { version = "0.8.8", features = ["ws"] } +axum-streams = { version = "0.24.0", features = ["json"] } +celes = "2.6.0" +chrono = "0.4.43" +crossbeam-queue = "0.3.12" +dotenv = "0.15.0" +futures = "0.3.32" +libtbr = { git = "https://pakin-inspiron-15-3530.tail110d9.ts.net/pakin/libtbr.git", version = "0.1.1" } +rayon = "1.11.0" +redis = { version = "1.0.2", features = ["tokio-comp"] } +reqwest = "0.13.1" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +tokio = { version = "1.49.0", features = ["full"] } +tokio-cron-scheduler = "0.15.1" +tokio-stream = "0.1.18" +uuid = { version = "1.20.0", features = ["v4"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..078c811 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM rust:latest AS builder + +RUN apt-get update && apt-get install -y \ + pkg-config \ + libssl-dev + +WORKDIR /app + +ENV CARGO_BUILD_JOBS=1 + +COPY Cargo.toml Cargo.lock ./ + +RUN mkdir src && \ + echo "fn main(){}" > src/main.rs && \ + cargo build --release -j 1&& \ + rm -rf src + +COPY src ./src + +RUN cargo build --release -j 1 + +FROM gcr.io/distroless/cc-debian12 + +COPY --from=builder /app/target/release/server-mark2-dev /usr/local/bin/ + + +EXPOSE 36579 + +CMD [ "server-mark2-dev" ] diff --git a/src/cold_start.rs b/src/cold_start.rs new file mode 100644 index 0000000..0d38b7e --- /dev/null +++ b/src/cold_start.rs @@ -0,0 +1,345 @@ +use std::{collections::HashMap, fs::File}; + +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; + +use celes::Country; +use redis::TypedCommands; + +use crate::AppState; + +#[allow(non_snake_case)] +#[derive(Serialize, Deserialize, Clone)] +pub struct CountryInfo { + image: String, + Brand: String, + Country: String, + VendingClass: String, + Machinecompatible: String, + MateriallistProfile: Vec, + #[serde(flatten)] + extra: HashMap, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CountryInfoProfileDetail { + json: String, + img: String, + desc: String, + #[serde(flatten)] + extra: HashMap, +} + +impl CountryInfo { + pub fn new(country_code: String, brand: Option) -> CountryInfo { + let country = match Country::from_alpha3(&country_code.clone()) { + Ok(c) => c, + Err(_) => { + if country_code.eq("dubai") { + Country::the_united_arab_emirates() + } else { + Country::thailand() + } + } + }; + + CountryInfo { + image: format!("taobin_project/logo/{country_code}_plate.png"), + Brand: brand.unwrap_or("".to_string()), + Country: country.long_name.to_string(), + VendingClass: String::from("coffeethai02"), + Machinecompatible: String::from("GEN2 and GEN32"), + MateriallistProfile: vec![CountryInfoProfileDetail { + json: String::from("vending_setting_and_profile_v1.json"), + img: String::from("vending_setting_and_profile_v1.png"), + desc: String::new(), + extra: HashMap::default(), + }], + extra: HashMap::default(), + } + } +} + +async fn get_root_files(state: AppState) -> Result> { + let api_header = state.get_cfg().get_api_header(); + let mut ret_result = serde_json::Value::Null; + + let client = reqwest::Client::new(); + let res = client + .get("http://localhost:36584/checkout?path=") + .header(api_header.0, api_header.1) + .send() + .await; + + match res { + Ok(res) => { + if let Some(ct) = res.headers().get("content-type") + && ct.eq("application/json") + { + let raw = res.text().await; + + if let Ok(raw) = raw { + let result: serde_json::Value = + serde_json::from_str(&raw).unwrap_or(serde_json::Value::Null); + + let mut redis_client = state.clone().redis_cli; + let _ = redis_client.set("root_repo", result.to_string()); + ret_result = result.clone(); + + println!("setup next"); + tokio::spawn(async move { + let s1 = setup_after_get_root(state.clone(), result.clone()) + .await + .ok(); + println!("checkpoint 1: {}", s1.is_some()); + if let Some((country_with_version, country_mapping)) = s1 { + println!("entries: {}", country_with_version.len()); + let _ = get_all_file_path_of_legit_country( + state.clone(), + country_with_version, + country_mapping, + ) + .await; + } + }); + } + } + } + Err(e) => { + println!("Error on root fetch: {e}"); + } + } + + Ok(ret_result) +} + +async fn setup_after_get_root( + state: AppState, + roots: serde_json::Value, +) -> Result<(Vec, HashMap), Box> { + let mut legit_country_with_version = Vec::new(); + let mut country_version_mapping = HashMap::new(); + + if let Some(map) = roots.as_object() + && let Some(res) = map.get("result") + { + let fds: Vec = res + .as_array() + .unwrap_or(&Vec::new()) + .iter() + .map(|x| x.as_str().unwrap_or("").to_string()) + .collect(); + + println!("pre_loop: {fds:?}"); + + // TODO: build in pattern `/version` + // if get response ok, save + // NOTE: filter country + + let api_header = state.get_cfg().get_api_header(); + for fd in fds { + println!("checking {fd}"); + // try GET + let client = reqwest::Client::new(); + let res = client + .get(format!("http://localhost:36584/checkout?path={fd}/version")) + .header(api_header.clone().0, api_header.clone().1) + .send() + .await; + + if let Ok(r) = res + && let Some(ct) = r.headers().get("content-type") + && r.status().eq(&reqwest::StatusCode::OK) + && ct.eq("application/json") + && let Ok(txt) = r.text().await + { + println!("{fd}.version = {txt}"); + + // + let vres: HashMap = serde_json::from_str(&txt).unwrap(); + + let vv = vres + .get("result") + .map(|x| x.to_string()) + .unwrap_or("".to_string()); + + // get version of country + let mut rcli = state.clone().redis_cli; + let _ = rcli.set(format!("{fd}.version"), vv.clone()); + + // generate all file paths + legit_country_with_version.push(fd.clone()); + country_version_mapping.insert(fd.clone(), vv.clone()); + } + } + } + + Ok((legit_country_with_version, country_version_mapping)) +} + +async fn get_all_file_path_of_legit_country( + state: AppState, + legit_countries: Vec, + country_mapping: HashMap, +) -> Result<(), Box> { + let api_header = state.get_cfg().get_api_header(); + + // save all entries of each country + for country in legit_countries { + let client = reqwest::Client::new(); + let res = client + .get(format!("http://localhost:36584/checkout?path={country}")) + .header(api_header.clone().0, api_header.clone().1) + .send() + .await; + + if let Ok(r) = res + && let Some(ct) = r.headers().get("content-type") + && r.status().eq(&reqwest::StatusCode::OK) + && ct.eq("application/json") + && let Ok(txt) = r.text().await + { + // get version of country & persist save + let mut rcli = state.clone().redis_cli; + let _ = rcli.set(country.clone(), txt.clone()); + + // generate all file paths + println!("{country} ready!"); + + let files: HashMap> = + serde_json::from_str(&txt.clone()).unwrap_or(HashMap::new()); + + // stream content + let _ = rcli.publish( + "recipe_files_by_country", + json!({country.clone() : files}).to_string(), + ); + + if let Some(fl) = files.get("result") { + let has_info = fl.contains(&".info.json".to_string()); + println!("{country} has info: {has_info}"); + + // read version + let current_latest_version = country_mapping + .get(&country) + .map(|x| x.to_string()) + .unwrap_or("unknown".to_string()); + + let latest_version_file: Vec = fl + .iter() + .filter(|x| x.contains(¤t_latest_version)) + .map(|x| x.to_string()) + .collect(); + + if !has_info { + // generate info for country + let _ = generate_country_info_default(state.clone(), country.clone()).await; + } else { + let _ = fetch_country_info(state.clone(), country.clone()).await; + } + + // do fetch latest version into redis + if let Some(single) = latest_version_file.first() { + let res_c = client + .get(format!( + "http://localhost:36584/checkout?path={}/{single}", + country.clone() + )) + .header(api_header.clone().0, api_header.clone().1) + .send() + .await; + + if let Ok(latest_raw) = res_c + && let Ok(latest_raw_txt) = latest_raw.text().await + { + println!("cached {single}"); + let _ = rcli.set( + format!("{}/{}", country.clone(), single.clone()), + latest_raw_txt, + ); + } + } + } + } + } + + Ok(()) +} + +async fn generate_country_info_default( + state: AppState, + cc: String, +) -> Result<(), Box> { + let country_info = match cc.as_str() { + "sgp" | "dubai" => CountryInfo::new(cc.clone(), Some("WhatTheCup".to_string())), + "gbr" | "aus" | "hkg" | "rou" | "lva" | "est" | "etu" => { + CountryInfo::new(cc.clone(), Some("Flying Turtle".to_string())) + } + _ => CountryInfo::new(cc.clone(), Some("Taobin".to_string())), + }; + + // save country info + let mut rcli = state.clone().redis_cli; + let _ = rcli.set(format!("{cc}.info"), serde_json::to_string(&country_info)?); + + // save local + let json = serde_json::to_string(&country_info)?; + let json2: serde_json::Value = serde_json::from_str(&json)?; + let writer = File::create(format!(".info.{cc}.json")).unwrap(); + let _ = serde_json::to_writer_pretty(writer, &json2); + + Ok(()) +} + +async fn fetch_country_info(state: AppState, cc: String) -> Result<(), Box> { + let api_header = state.get_cfg().get_api_header(); + let client = reqwest::Client::new(); + let res = client + .get( + state + .get_cfg() + .get_file_from_recipe_repo(format!("{cc}/.info.json")), + ) + .header(api_header.clone().0, api_header.clone().1) + .send() + .await; + + if let Ok(r) = res + && let Some(ct) = r.headers().get("content-type") + && r.status().eq(&reqwest::StatusCode::OK) + && ct.eq("application/json") + && let Ok(txt) = r.text().await + { + let mut rcli = state.clone().redis_cli; + let info: CountryInfo = + serde_json::from_str(&txt.clone()).unwrap_or(CountryInfo::new(cc.clone(), None)); + + let _ = rcli.set(format!("{cc}.info"), serde_json::to_string(&info)?); + + let json = serde_json::to_string(&info)?; + let json2: serde_json::Value = serde_json::from_str(&json)?; + let writer = File::create(format!(".info.{cc}.json")).unwrap(); + let _ = serde_json::to_writer_pretty(writer, &json2); + } + + Ok(()) +} + +pub async fn cold_start_process(state: AppState) -> Result<(), Box> { + let ostate = state.clone(); + println!("starting cold process"); + let _ = tokio::spawn(async move { + match get_root_files(ostate).await { + Ok(res) => { + println!("cold start ok, {}", res); + } + + Err(e) => { + println!("cold start error: {e}"); + } + } + }) + .await; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..efc0ac7 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,852 @@ +use std::{ + collections::VecDeque, env, fs::File, io::Read, path::PathBuf, sync::Arc, time::Duration, +}; + +use async_compression::tokio::bufread::BrotliDecoder; +use axum::{ + Json, Router, + extract::{ + State, WebSocketUpgrade, + ws::{CloseFrame, Message, WebSocket}, + }, + response::IntoResponse, + routing::{get, post}, +}; +use futures::{ + SinkExt, StreamExt, + stream::{SplitSink, SplitStream}, +}; +use libtbr::models::recipe::{MaterialSetting, Recipe, Recipe01}; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use redis::{TypedCommands, cmd}; +use serde::{Deserialize, Serialize}; + +use tokio::{ + io::{AsyncReadExt, BufReader}, + sync::{ + Mutex, + mpsc::{self, Receiver, Sender}, + }, +}; +use uuid::Uuid; + +use crate::{ + stream::model::{ + IntoStreamMessage, StreamDataChunk, StreamDataEnd, StreamDataExtra, StreamDataStart, + }, + tx::handler::create_tx_patcher_route, +}; + +// mod cold_start; +mod stream; +mod tx; + +// features +// - get result from recipe_repo +// - store in redis +// - cron job fetch update + +#[derive(Clone)] +pub struct DevConfig { + api_key: String, + api_domain: String, + api_recipe_service: String, + api_redis_url: String, +} + +impl DevConfig { + pub fn new( + key: String, + domain: String, + rp_service: String, + api_redis_url: String, + ) -> DevConfig { + DevConfig { + api_key: key, + api_domain: domain, + api_recipe_service: rp_service, + api_redis_url, + } + } + + pub fn get_recipe_url(&self) -> String { + format!("{}{}", self.api_domain, self.api_recipe_service) + } + + pub fn get_file_from_recipe_repo(&self, path: String) -> String { + format!("{}/checkout?path={}", self.get_recipe_url(), path) + } + + pub fn get_api_header(&self) -> (String, String) { + ("X-API-Key".to_string(), self.api_key.clone()) + } +} + +// async fn test_send(dev_cfg: DevConfig) -> Result<(), Box> { + +// let api_header = dev_cfg.get_api_header(); + +// let client = reqwest::Client::new(); +// let res = client.get(dev_cfg.get_file_from_recipe_repo(String::new())).header(api_header.0, api_header.1).send().await?; + +// println!("headers: {:?}", res.headers()); + +// if let Some(ct) = res.headers().get("content-type") && ct.eq("application/json"){ +// let raw = res.text().await?; +// let result: serde_json::Value = serde_json::from_str(&raw)?; +// println!("raw response: {result:?}"); + +// //redis-cli -h 100.120.136.127 -p 6379 +// let mut redis_client = redis::Client::open("redis://100.120.136.127:6379")?; + +// let _ = redis_client.set("root_repo", result.to_string()); +// } + +// Ok(()) +// } + +pub async fn create_recipe_repo_router() -> Router> { + Router::new() + // .route("/", get(get_root_files)) + .route("/ws", get(websocket_handler)) + // .route("/edit", post()) + // .route("/{country}/", method_router) +} + +#[derive(Debug, Serialize, Deserialize)] +struct SysMessage { + #[serde(rename = "type")] + stype: String, + payload: serde_json::Value, +} + +async fn post_from_other_system( + State(mut state): State>, + Json(msg): Json, +) -> impl IntoResponse { + let sys_payload = match serde_json::to_value(&msg) { + Ok(s) => s, + Err(_) => { + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "cannot create payload", + ) + .into_response(); + } + }; + match state.system_tx.send(sys_payload) { + Ok(_) => (axum::http::StatusCode::OK, "").into_response(), + Err(_) => (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "send fail").into_response(), + } +} + +async fn websocket_handler( + State(mut state): State>, + ws: WebSocketUpgrade, +) -> impl IntoResponse { + let state_clone = Arc::clone(&state); + ws.on_failed_upgrade(|error| println!("Error upgrading websocket: {}", error)) + .on_upgrade(async |s| handle_socket(s, state_clone).await.unwrap_or(())) +} + +async fn handle_socket( + mut socket: WebSocket, + mut state: Arc, +) -> Result<(), Box> { + let (mut sender, mut receiver) = socket.split(); + // internal channel + let (tx, mut rx) = mpsc::channel::(2); + let user_sys_rx = state.system_tx.subscribe(); + + tokio::spawn(write(sender, rx)); + tokio::spawn(read(state, receiver, tx.clone(), user_sys_rx)); + // NOTE: + + // while let Some(msg) = socket.recv().await { + // if let Ok(msg) = msg { + // match msg { + // Message::Text(utf8_bytes) => { + // println!("text recv: {utf8_bytes}"); + + // let req = utf8_bytes.clone().as_str().to_string(); + // let req_struct: serde_json::Value = match serde_json::from_str(&req) { + // Ok(r) => r, + // Err(_) => serde_json::Value::Null, + // }; + + // // response + // let n_state = state.clone(); + // let response = + // handle_recipe_websocket_message(req_struct, n_state, &mut socket).await?; + // let response_next = match serde_json::to_string(&response) { + // Ok(p) => p, + // Err(_) => "{'result': 'error'}".to_string(), + // }; + + // let result = socket.send(Message::Text(response_next.into())).await; + // if let Err(error) = result { + // println!("Error sending: {error}"); + // send_close_message(socket, 1011, &format!("Error occured: {error}")).await; + // break; + // } + // } + // Message::Binary(bytes) => { + // println!("recv bytes len: {}", bytes.len()); + // let result = socket + // .send(Message::Text( + // format!("recv bytes len: {}", bytes.len()).into(), + // )) + // .await; + // if let Err(error) = result { + // println!("Error sending: {error}"); + // send_close_message(socket, 1011, &format!("Error occured: {error}")).await; + // break; + // } + // } + // _ => {} + // } + // } else { + // let error = msg.err().unwrap(); + // println!("Error while receiving message: {error:?}"); + // send_close_message(socket, 1011, &format!("Error occured: {error}")).await; + // break; + // } + // } + + Ok(()) +} + +async fn send_close_message(mut socket: WebSocket, code: u16, reason: &str) { + _ = socket + .send(Message::Close(Some(CloseFrame { + code, + reason: reason.into(), + }))) + .await; +} + +async fn fetch_content_from_redis(redis: redis::Client, key: &str) -> Result { + let mut rcli = redis.clone(); + match rcli.get(key) { + Ok(s) => { + if let Some(res) = s { + Ok(res) + } else { + Err(format!("result error from key: {key}")) + } + } + Err(e) => Err(format!("redis get failed: {e}")), + } +} + +async fn fetch_content_from_redis_byte(redis: redis::Client, key: &str) -> Result, String> { + let mut conn = match redis.get_connection() { + Ok(cnn) => cnn, + Err(e) => { + println!("get connection fail, {e}"); + return Ok(vec![]); + } + }; + + let res = cmd("GET").arg(key).query::>(&mut conn); + + match res { + Ok(res) => Ok(res), + Err(e) => { + println!("get fail, {e}"); + return Ok(vec![]); + } + } + + // match res { + // Ok(s) => { + // if let Some(res) = s { + // Ok(res.as_bytes().to_vec()) + // } else { + // Err(format!("result error from key: {key}")) + // } + // } + // Err(e) => Err(format!("redis get failed: {e}")), + // } + // +} + +#[derive(Serialize, Deserialize)] +struct WebsocketMessageRequest { + #[serde(rename = "type")] + type_w: String, + payload: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct RecipeRequestPayload { + /// For validate request is acceptable + auth: String, + /// Only grep partial of file, will be sent with json patch + partial: bool, + /// Country of recipe + country: String, + /// version of recipe + version: i64, + /// Extended infos, required parameters or unimplemented fields in the current struct. Expected pattern `=,=,...` + parameters: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct CommandRequestPayload { + /// User info expect at least id, token, name + user_info: serde_json::Value, + /// Target service + srv_name: String, + /// Values + values: serde_json::Value, +} + +fn convert_ack_command(cmd_req: &serde_json::Value) -> Option { + match serde_json::from_value(cmd_req.clone()) { + Ok(req) => Some(req), + Err(_) => None, + } +} + +// /// Result to send to client +// #[derive(Serialize, Deserialize)] +// struct RecipeOverview { +// productCode: String, +// name: Option, +// description: Option, +// tags: String, +// status: MenuStatus, +// } + +// #[derive(Serialize, Deserialize)] +// enum MenuStatus { +// #[serde(rename = "ready")] +// Ready, +// #[serde(rename = "obsolete")] +// Obsolete, +// #[serde(rename = "drafted")] +// Drafted, +// Status(String), // additional status +// } + +async fn read( + // redis: redis::Client, + mut state: Arc, + mut receiver: SplitStream, + tx: Sender, + mut system_rx: tokio::sync::broadcast::Receiver, + // cmd_atom: crossbeam_queue::ArrayQueue, +) -> Result<(), Box> { + let redis = state.redis_cli.clone(); + let tx_to_client = tx.clone(); + + tokio::spawn(async move { + // Send back to client from services + while let Ok(s_msg) = system_rx.recv().await { + if convert_ack_command(&s_msg).is_none() + && let Some(err) = tx_to_client.send(s_msg).await.err() + { + println!("[SYS] failed to send back to client: {err}"); + } + } + }); + + while let Some(Ok(msg)) = receiver.next().await { + match msg { + Message::Text(t) => { + let req: WebsocketMessageRequest = serde_json::from_str(t.as_str())?; + match req.type_w.as_str() { + "recipe" if req.payload.is_some() => { + // guard expect value + let p = req.payload.unwrap(); + let recipe_param: RecipeRequestPayload = serde_json::from_value(p)?; + + // get actual version + // + let latest_key = + format!("master:{country}/version", country = recipe_param.country); + + println!("latest key: {latest_key}"); + let latest_version = + match fetch_content_from_redis_byte(redis.clone(), &latest_key).await { + Ok(x) => { + // decode brotli + let mut sbuf = String::new(); + + let mut decoder = BrotliDecoder::new(x.as_slice()); + + match decoder.read_to_string(&mut sbuf).await { + Ok(_) => sbuf.replace('"', ""), + Err(e) => { + println!("decode fail: {e}"); + "".to_string() + } + } + } + Err(e) => { + println!("get latest fail: {e}"); + "".to_string() + } + }; + + let req_file = if is_req_patch(&recipe_param) { + format!( + "stx_{country}_{version}.json", + country = recipe_param.country, + version = latest_version + ) + } else { + format!( + "result.{country}.{version}.json", + country = recipe_param.country, + version = latest_version + ) + }; + + let mut retry_cnt = 0; + let sid = Uuid::new_v4(); + println!("init req: {req_file}"); + + match get_local_file(req_file) { + Ok(mut f) => { + let mut file_content = String::new(); + f.read_to_string(&mut file_content)?; + + // split send + let recipe: Recipe = serde_json::from_str(&file_content)?; + + // concat all recipes including subs + let r01s: Vec = recipe + .Recipe01 + .par_iter() + .flat_map(|x| { + let mut v = Vec::new(); + v.push(x.clone()); + + if let Some(sub) = x.clone().SubMenu { + v.extend(sub); + } + + v + }) + .collect(); + + let matset: Vec = recipe.MaterialSetting.clone(); + + let ss = StreamDataStart::new( + r01s.len(), + 100, + Some("local".to_string()), + ); + let sid = ss.get_id(); + + if let Some(err) = tx.send(ss.as_msg()).await.err() { + println!("ERR: send tx error, {err:?}"); + } + + for (index, chunk) in r01s.chunks(100).enumerate() { + let sda = + StreamDataChunk::new(&sid, index * 100, chunk.to_vec()); + + // no validate + if let Some(err) = tx.send(sda.as_msg()).await.err() { + println!("ERR: send tx error, {err:?}"); + } + } + + let mat_exid = sid.clone(); + let extp = "matset"; + for (index, chunk) in matset.chunks(100).enumerate() { + let curr_ch_id = format!("{mat_exid}_{index}"); + + let extra_matset = + StreamDataExtra::new(&curr_ch_id, &extp, chunk.to_vec()); + + if let Some(err) = tx.send(extra_matset.as_msg()).await.err() { + println!("ERR: send tx extra error: {err:?}"); + } + } + + let end_msg = StreamDataEnd::new(&sid); + + if let Some(err) = tx.send(end_msg.as_msg()).await.err() { + println!("ERR: send tx error, {err:?}"); + } + } + Err(_) => { + // savable sid means it could find data but not guarantee if sendable + let mut success_sid = String::new(); + // concurrent fetch + for i in 1..5 { + retry_cnt = i; + // retry #1: get from redis + let r1_key = get_key_cache( + recipe_param.country.clone(), + latest_version.to_string(), + is_req_patch(&recipe_param), + retry_cnt, + ); + + println!("curr key: {r1_key}"); + + if retry_cnt < 3 { + match fetch_content_from_redis_byte(redis.clone(), &r1_key) + .await + { + Ok(res) => { + let buf = BufReader::new(res.as_slice()); + let mut sbuf = String::new(); + + let mut decoder = BrotliDecoder::new(buf); + + if let Ok(_) = + decoder.read_to_string(&mut sbuf).await + { + let recipe: Recipe = + serde_json::from_str(&sbuf)?; + + let r01s: Vec = recipe + .Recipe01 + .par_iter() + .flat_map(|x| { + let mut v = Vec::new(); + v.push(x.clone()); + + if let Some(sub) = x.clone().SubMenu { + v.extend(sub); + } + + v + }) + .collect(); + + let matset: Vec = + recipe.MaterialSetting.clone(); + + // test stream start model + let ss = StreamDataStart::new( + r01s.len(), + 100, + Some("redis".to_string()), + ); + + let sid = ss.get_id(); + success_sid = sid.clone(); + + if let Some(err) = + tx.send(ss.as_msg()).await.err() + { + println!("ERR: send tx error, {err:?}"); + } + + // split send + + for (index, chunk) in + r01s.chunks(100).enumerate() + { + let sda = StreamDataChunk::new( + &sid, + index * 100, + chunk.to_vec(), + ); + + // no validate + if let Some(err) = + tx.send(sda.as_msg()).await.err() + { + println!("ERR: send tx error, {err:?}"); + } + } + + let mat_exid = sid.clone(); + let extp = "matset"; + for (index, chunk) in + matset.chunks(100).enumerate() + { + let curr_ch_id = + format!("{mat_exid}_{index}"); + + let extra_matset = StreamDataExtra::new( + &curr_ch_id, + &extp, + chunk.to_vec(), + ); + + if let Some(err) = tx + .send(extra_matset.as_msg()) + .await + .err() + { + println!( + "ERR: send tx extra error: {err:?}" + ); + } + } + + let rp_clone = recipe.clone(); + tokio::task::spawn(async move { + rp_clone.export_to_json_file(Some( + format!( + "result.{country}.{version}.json", + country = recipe_param.country, + version = latest_version + ), + )); + }); + + break; + } + } + Err(_) => {} + } + } + } + + let end_msg = StreamDataEnd::new(&success_sid); + + if let Some(err) = tx.send(end_msg.as_msg()).await.err() { + println!("ERR: send tx error, {err:?}"); + } + } + } + } + "command" if req.payload.is_some() => { + // do command send to other services + // // guard expect value + let p = req.payload.unwrap(); + + // TODO + // - Queue requests + // - Send if service available + if let Some(_) = state.system_tx.send(p).err() { + let _ = tx + .send(serde_json::json!({ + "type": "notify", + "payload": { + "from": "system_tx", + "level": "error", + "msg": "send request fail", + "to": "" + } + })) + .await; + } + } + _ => { + // not implemented + } + } + } + _ => { + // unhanled, ignore + } + } + } + Ok(()) +} + +async fn write( + mut sender: SplitSink, + mut rx: Receiver, +) -> Result<(), Box> { + while let Some(res) = rx.recv().await { + // no check + // println!("sending {res:?}"); + let _ = sender.send(res.to_string().into()).await; + let _ = tokio::time::sleep(Duration::from_millis(100)).await; + } + Ok(()) +} + +fn is_req_patch(param: &RecipeRequestPayload) -> bool { + param.version != -1 && param.partial +} + +fn get_local_file(filename: String) -> Result { + File::open(PathBuf::from(filename)) +} + +fn get_key_cache(country: String, version: String, is_patch: bool, retry_cnt: i32) -> String { + if is_patch { + format!("stx_{country}_{version}.json") + } else { + match retry_cnt { + 1 => { + format!("master:{country}/coffeethai02_{version}_{country}.json") + } + 2 => { + format!("master:{country}/coffeethai02_{version}.json") + } + 3 => { + // do checkout + format!("{country}/coffeethai02_{country}_{version}.json") + } + 4 => { + // do checkout + format!("{country}/coffeethai02_{version}.json") + } + _ => "".to_string(), + } + } +} + +pub struct AppState { + dev_config: DevConfig, + redis_cli: redis::Client, + system_tx: tokio::sync::broadcast::Sender, +} + +impl AppState { + pub fn get_cfg(&self) -> DevConfig { + self.dev_config.clone() + } + + pub async fn new( + dev_config: DevConfig, + redis_cli: redis::Client, + system_tx: tokio::sync::broadcast::Sender, + mut system_rx: tokio::sync::broadcast::Receiver, + ) -> Arc { + let redis_cli_clone = redis_cli.clone(); + let tx_new = system_tx.clone(); + let result = Arc::new(AppState { + dev_config, + redis_cli, + system_tx, + }); + + tokio::spawn(async move { + let mut lredis = redis_cli_clone.clone(); + let current_queue: crossbeam_queue::ArrayQueue = + crossbeam_queue::ArrayQueue::new(1); + let mut pending_command: VecDeque = VecDeque::new(); + let mut check_available_path = String::new(); + + loop { + if let Ok(rmsg) = system_rx.recv().await { + // add queue process + let command_req: CommandRequestPayload = match serde_json::from_value(rmsg) { + Ok(cmd) => cmd, + Err(_) => return, // reject + }; + + if let Err(fail_payload) = current_queue.push(command_req.clone()) { + if pending_command.len() < 10 { + pending_command.push_back(fail_payload) + } else { + let user_name = fail_payload.user_info.get("name").unwrap_or_default(); + + let _ = tx_new.send(serde_json::json!({ + "type": "notify", + "payload": { + "from": "system_tx", + "msg": "request queue full, try again later", + "level": "ERR", + "to": user_name, + } + })); + } + } else { + // set check to latest push to queue ok + check_available_path = format!("{}/status", command_req.srv_name); + } + } + + // send process + if let Ok(Some(status)) = lredis.get(&check_available_path) { + match status.as_str() { + "ok" | "OK" | "Ok" => { + // + if current_queue.is_full() + && let Some(cmd) = current_queue.pop() + { + // get one + let channel = format!("{}/job", cmd.srv_name); + let _ = lredis.publish( + channel, + serde_json::to_string(&cmd).unwrap_or("{}".to_string()), + ); + + // queue next + if let Some(next_cmd) = pending_command.pop_front() { + check_available_path = format!("{}/status", next_cmd.srv_name); + // ignore result + let _ = current_queue.push(next_cmd); + } else { + check_available_path = String::new(); + } + } else if current_queue.is_empty() { + check_available_path = String::new(); + } + } + _ => {} + } + } else if current_queue.is_empty() + && let Some(next_cmd) = pending_command.pop_front() + { + // case empty queue, fetch next + check_available_path = format!("{}/status", next_cmd.srv_name); + // ignore result + let _ = current_queue.push(next_cmd); + } + // else if current_queue.is_full() && pending_command.len() >= 10 { + // // suspect too many request, redis may be disconnected then cancel all + // println!("too many requests to wait"); + // let _ = current_queue.pop(); + // pending_command.clear(); + + // let _ = tx_new.send(value); + // } + } + }); + + result + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("Hello, world!"); + dotenv::dotenv().ok(); + // send req to repo service + + let server_port = env::var("PORT").unwrap_or("36579".to_string()); + + let api_key = env::var("DEV_API_KEY").expect("no api key"); + let api_domain = env::var("DEV_API_DOMAIN").expect("no domain"); + let api_recipe_service = env::var("DEV_API_RECIPE_SERVICE").expect("no service"); + + let api_redis = env::var("DEV_API_REDIS").unwrap_or("0.0.0.0".to_string()); + let api_redis_port = env::var("DEV_API_REDIS_PORT").unwrap_or("6379".to_string()); + + let dev_cfg = DevConfig::new( + api_key, + api_domain, + api_recipe_service, + format!("redis://{api_redis}:{api_redis_port}"), + ); + + // test_send(dev_cfg).await?; + // + let redis_cli = redis::Client::open(dev_cfg.api_redis_url.clone())?; + + let (sys_tx, mut sys_rx) = tokio::sync::broadcast::channel::(16); + + let app_state = AppState::new(dev_cfg, redis_cli, sys_tx, sys_rx).await; + + let rp_router = create_recipe_repo_router().await; + let doc_router = create_tx_patcher_route().await; + + let app = Router::new() + // .route("/sessionLogin", post(session_login)) + .route("/syscb", post(post_from_other_system)) + .nest("/recipe", rp_router) + .nest("/docs", doc_router) + .with_state(app_state); + + let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{server_port}")).await?; + axum::serve(listener, app).await?; + + Ok(()) +} diff --git a/src/stream/mod.rs b/src/stream/mod.rs new file mode 100644 index 0000000..65880be --- /dev/null +++ b/src/stream/mod.rs @@ -0,0 +1 @@ +pub mod model; diff --git a/src/stream/model.rs b/src/stream/model.rs new file mode 100644 index 0000000..0f86d6b --- /dev/null +++ b/src/stream/model.rs @@ -0,0 +1,179 @@ +use rayon::iter::Either; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +pub trait IntoStreamMessage { + const MSG_NAME: &str; + fn build(&self) -> serde_json::Value; + fn get_id(&self) -> String; +} + +/// Pre-flight metadata for streaming +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamDataStart { + /// Uuid v4, client must mapping later values with this stream id + pub stream_id: String, + /// Total amount items known + pub total_size: usize, + /// + pub chunk_size: usize, + /// referer for checking steps in debugging + #[serde(rename = "ref")] + #[serde(skip_serializing_if = "Option::is_none")] + pub stream_ref: Option, +} + +impl IntoStreamMessage for StreamDataStart { + const MSG_NAME: &str = "stream_data_start"; + + fn build(&self) -> serde_json::Value { + serde_json::json!({ + "type": StreamDataStart::MSG_NAME, + "payload": self.clone() + }) + } + + fn get_id(&self) -> String { + self.stream_id.clone() + } +} + +impl StreamDataStart { + pub fn new(total_size: usize, chunk_size: usize, stream_ref: Option) -> Self { + Self { + stream_id: Uuid::new_v4().to_string(), + total_size, + chunk_size, + stream_ref, + } + } + + pub fn as_msg(&self) -> serde_json::Value { + self.build() + } +} + +/// Data splited into chunks not exceeding expected size +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamDataChunk { + /// Uuid v4, client must mapping later values with this stream id + pub stream_id: String, + /// Actual index of first item in this chunk from full data + pub start_idx: usize, + /// Chunked data which splited into N items per chunk + pub data: Vec, +} + +impl IntoStreamMessage for StreamDataChunk +where + T: Serialize, +{ + const MSG_NAME: &str = "stream_data_chunk"; + + fn build(&self) -> serde_json::Value { + serde_json::json!({ + "type": StreamDataChunk::::MSG_NAME, + "payload": self + }) + } + + fn get_id(&self) -> String { + self.stream_id.clone() + } +} + +impl StreamDataChunk +where + T: Serialize, +{ + pub fn new(sid: &str, start_idx: usize, data: Vec) -> Self { + Self { + stream_id: sid.to_string(), + start_idx, + data, + } + } + + pub fn as_msg(&self) -> serde_json::Value { + self.build() + } +} + +/// Close message for signaling end of streaming +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamDataEnd { + /// Uuid v4, client must mapping later values with this stream id + pub stream_id: String, +} + +impl IntoStreamMessage for StreamDataEnd { + const MSG_NAME: &str = "stream_data_end"; + + fn build(&self) -> serde_json::Value { + serde_json::json!({ + "type": StreamDataEnd::MSG_NAME, + "payload": self.clone() + }) + } + + fn get_id(&self) -> String { + self.stream_id.clone() + } +} + +impl StreamDataEnd { + pub fn new(sid: &str) -> Self { + Self { + stream_id: sid.to_string(), + } + } + + pub fn as_msg(&self) -> serde_json::Value { + self.build() + } +} + +/// Extra data send along with previous sent chunks, +/// must send before end msg. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct StreamDataExtra { + /// Uuid v4, client must mapping later values with this stream id + pub exid: String, + pub extp: String, + pub payload: Vec, +} + +impl IntoStreamMessage for StreamDataExtra +where + T: Serialize, +{ + const MSG_NAME: &str = "stream_data_extra"; + + fn build(&self) -> serde_json::Value { + serde_json::json!({ + "type": StreamDataExtra::::MSG_NAME, + "payload": self + }) + } + + fn get_id(&self) -> String { + self.exid.clone() + } +} + +impl StreamDataExtra +where + T: Serialize + Clone, +{ + pub fn new(exid: &str, extp: &str, data: Vec) -> Self { + Self { + exid: exid.to_string(), + extp: extp.to_string(), + payload: data.to_vec(), + } + } + + pub fn as_msg(&self) -> serde_json::Value { + self.build() + } +} diff --git a/src/tx/handler.rs b/src/tx/handler.rs new file mode 100644 index 0000000..22de9f4 --- /dev/null +++ b/src/tx/handler.rs @@ -0,0 +1,328 @@ +use std::sync::Arc; + +use axum::{ + Json, Router, + extract::{Path, Query, State}, + response::IntoResponse, + routing::{get, post}, +}; +use redis::{AsyncTypedCommands, TypedCommands, aio::MultiplexedConnection}; +use reqwest::StatusCode; +use serde::Deserialize; +use serde_json::{Value, json}; +use uuid::Uuid; + +use crate::{ + AppState, + tx::{ + helpers::*, + patcher::apply_ops, + types::{PatchOp, Reservation, ReserveReq, ReserveRes, TxCommitReq, TxCommitRes}, + }, +}; + +async fn reserve_tx( + State(state): State>, + Path(doc_id): Path, + Json(req): Json, +) -> impl IntoResponse { + let rsv_id = Uuid::new_v4().to_string(); + let ttl_secs: i64 = 30; // ttl reserve 30s + let expires_at = now_unix() + ttl_secs; + + let mut rcli = state.redis_cli.clone(); + + // check version + let ver_key = doc_ver_key(&doc_id); + let base_version: u64 = match rcli.get(ver_key) { + Ok(res) => { + if let Some(r) = res { + r.parse().ok().unwrap() + } else { + 0 + } + } + Err(_) => 0, + }; + + let rsv = Reservation { + reservation_id: rsv_id.clone(), + doc_id: doc_id.clone(), + author: req.author, + base_version, + expires_at, + }; + + let key = rsv_key(&doc_id, &rsv_id); + let payload = serde_json::to_string(&rsv).unwrap(); + + let _: () = rcli.set_ex(key, payload, ttl_secs as u64).unwrap(); + + ( + StatusCode::OK, + Json(ReserveRes { + reservation_id: rsv_id, + base_version, + expires_at, + }), + ) +} + +async fn commit_tx( + State(state): State>, + Path(doc_id): Path, + Json(req): Json, +) -> impl IntoResponse { + if req.tx_id.is_empty() || req.reservation_id.is_empty() { + return ( + StatusCode::BAD_REQUEST, + "missing tx_id or reservation_id ".into_response(), + ); + } + + let mut con = match state.redis_cli.get_multiplexed_async_connection().await { + Ok(conn) => conn, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("{}", e.to_string()).into_response(), + ); + } + }; + + // lock commit + let lock_key = commit_lock_key(&doc_id); + if let Err(_) = wait_lock(&mut con, &lock_key, 1200, 20).await { + return (StatusCode::CONFLICT, "doc busy, retry".into_response()); + } + + let txid_k = txid_key(&doc_id, &req.tx_id); + if let Ok(Some(v)) = con.get(&txid_k).await { + if let Ok(committed_version) = v.parse::() { + let res = TxCommitRes { + doc_id, + tx_id: req.tx_id, + committed_version, + }; + return (StatusCode::OK, Json(res).into_response()); + } + } + + let rsv_k = rsv_key(&doc_id, &req.reservation_id); + let rsv_raw: Option = con.get(&rsv_k).await.unwrap_or(None); + let rsv_raw = match rsv_raw { + Some(x) => x, + None => { + return ( + StatusCode::CONFLICT, + "reservation missing/expired".into_response(), + ); + } + }; + + let rsv: Reservation = match serde_json::from_str(&rsv_raw) { + Ok(x) => x, + Err(_) => return (StatusCode::CONFLICT, "bad reservation".into_response()), + }; + + if rsv.doc_id != doc_id || rsv.author != req.author || rsv.base_version != req.base_version { + return (StatusCode::CONFLICT, "reservation mismatch".into_response()); + } + + if now_unix() > rsv.expires_at { + return (StatusCode::CONFLICT, "reservation expired".into_response()); + } + + let curr_ver: u64 = con + .get(doc_ver_key(&doc_id)) + .await + .ok() + .flatten() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + + if req.base_version != curr_ver { + let body = json!({ + "error": "version_conflict", + "server_version": curr_ver, + }); + return (StatusCode::CONFLICT, Json(body).into_response()); + } + + let next_ver = curr_ver + 1; + + let stream_key = doc_tx_stream_key(&doc_id); + + let tx_event = json!({ + "doc_id": doc_id, + "tx_id": req.tx_id, + "version": next_ver, + "base_version": req.base_version, + "author": req.author, + "ts": now_unix(), + "ops": req.ops + }); + + let tx_event_str = tx_event.to_string(); + let _: String = redis::cmd("XADD") + .arg(&stream_key) + .arg("*") + .arg("data") + .arg(&tx_event_str) + .query_async(&mut con) + .await + .unwrap(); + + let _: () = con + .set(doc_ver_key(&doc_id), next_ver.to_string()) + .await + .unwrap(); + let _: () = con.set(&txid_k, next_ver.to_string()).await.unwrap(); + let _: usize = con.del(&rsv_k).await.unwrap(); + + let snapshot_every: u64 = 50; + if next_ver % snapshot_every == 0 { + if let Ok(state) = build_state_at(&mut con, &doc_id, next_ver).await { + let _: () = con + .set(doc_snap_key(&doc_id), state.to_string()) + .await + .unwrap(); + let _: () = con + .set(doc_snapver_key(&doc_id), next_ver.to_string()) + .await + .unwrap(); + } + } + + ( + StatusCode::OK, + Json(TxCommitRes { + doc_id, + tx_id: req.tx_id, + committed_version: next_ver, + }) + .into_response(), + ) +} + +async fn build_state_at( + con: &mut MultiplexedConnection, + doc_id: &str, + target_version: u64, +) -> Result { + let snap_ver: u64 = con + .get(doc_snap_key(&doc_id)) + .await + .ok() + .flatten() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + + let snap_json: Value = if snap_ver > 0 { + let raw: Option = con.get(doc_snap_key(&doc_id)).await.unwrap_or(None); + raw.and_then(|s| serde_json::from_str(&s).ok()) + .unwrap_or_else(|| json!({})) + } else { + json!({}) + }; + + let stream_key = doc_tx_stream_key(doc_id); + + let start_id = "0-0"; + let entries: Vec<(String, Vec<(String, String)>)> = redis::cmd("XRANGE") + .arg(&stream_key) + .arg(start_id) + .arg("+") + .query_async(con) + .await + .map_err(|e| e.to_string())?; + + let mut ops_list: Vec = Vec::new(); + for (_id, fields) in entries { + let mut data_opts = None; + for (k, v) in fields { + if k == "data" { + data_opts = Some(v); + break; + } + } + let data = match data_opts { + Some(x) => x, + None => continue, + }; + + let ev: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; + let ver = ev.get("version").and_then(|v| v.as_u64()).unwrap_or(0); + if ver == 0 || ver > target_version { + continue; + } + + if ver <= snap_ver { + continue; + } + + let ops: Vec = + serde_json::from_value(ev["ops"].clone()).map_err(|e| e.to_string())?; + ops_list.extend(ops); + } + + apply_ops(snap_json, &ops_list) +} + +#[derive(Deserialize)] +struct StateQuery { + at_version: Option, +} + +async fn get_state( + State(state): State>, + Path(doc_id): Path, + Query(q): Query, +) -> impl IntoResponse { + let rcli = state.redis_cli.clone(); + let mut con = match rcli.get_multiplexed_async_connection().await { + Ok(sc) => sc, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "cannot get connection".into_response(), + ); + } + }; + + let cur_ver: u64 = con + .get(doc_ver_key(&doc_id)) + .await + .ok() + .flatten() + .and_then(|s| s.parse().ok()) + .unwrap_or(0); + + let target = q.at_version.unwrap_or(cur_ver).min(cur_ver); + + match build_state_at(&mut con, &doc_id, target).await { + Ok(state) => ( + StatusCode::OK, + Json(json!({ + "doc_id": doc_id, + "version": target, + "state": state + })) + .into_response(), + ), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(json!({ + "error": e + })) + .into_response(), + ), + } +} + +pub async fn create_tx_patcher_route() -> Router> { + Router::new() + .route("/{id}/tx-reserve", post(reserve_tx)) + .route("/{id}/tx-commit", post(commit_tx)) + .route("/{id}/state", get(get_state)) +} diff --git a/src/tx/helpers.rs b/src/tx/helpers.rs new file mode 100644 index 0000000..5bc3c09 --- /dev/null +++ b/src/tx/helpers.rs @@ -0,0 +1,67 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use redis::{RedisResult, aio::MultiplexedConnection}; +use tokio::time::sleep; +use uuid::Uuid; + +pub fn now_unix() -> i64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() as i64 +} + +pub fn doc_ver_key(doc: &str) -> String { + format!("doc:{doc}:ver") +} +pub fn doc_tx_stream_key(doc: &str) -> String { + format!("doc:{doc}:tx") +} +pub fn doc_snap_key(doc: &str) -> String { + format!("doc:{doc}:snap") +} +pub fn doc_snapver_key(doc: &str) -> String { + format!("doc:{doc}:snapver") +} +pub fn rsv_key(doc: &str, rsv: &str) -> String { + format!("doc:{doc}:rsv:{rsv}") +} +pub fn txid_key(doc: &str, tx_id: &str) -> String { + format!("doc:{doc}:txid:{tx_id}") +} +pub fn commit_lock_key(doc: &str) -> String { + format!("doc:{doc}:lock") +} + +pub async fn acquire_lock( + con: &mut MultiplexedConnection, + key: &str, + ttl_ms: u64, +) -> RedisResult { + let token = Uuid::new_v4().to_string(); + let ok: Option = redis::cmd("SET") + .arg(key) + .arg(token) + .arg("NX") + .arg("PX") + .arg(ttl_ms) + .query_async(con) + .await?; + Ok(ok.is_some()) +} + +pub async fn wait_lock( + con: &mut MultiplexedConnection, + key: &str, + ttl_ms: u64, + attempts: u32, +) -> RedisResult<()> { + for _ in 0..attempts { + if acquire_lock(con, key, ttl_ms).await? { + return Ok(()); + } + + sleep(Duration::from_millis(30)).await; + } + Err(redis::RedisError::from((redis::ErrorKind::Io, "lock busy"))) +} diff --git a/src/tx/mod.rs b/src/tx/mod.rs new file mode 100644 index 0000000..70bb117 --- /dev/null +++ b/src/tx/mod.rs @@ -0,0 +1,4 @@ +pub mod handler; +pub mod helpers; +pub mod patcher; +pub mod types; diff --git a/src/tx/patcher.rs b/src/tx/patcher.rs new file mode 100644 index 0000000..d96121b --- /dev/null +++ b/src/tx/patcher.rs @@ -0,0 +1,119 @@ +use serde_json::Value; + +use crate::tx::types::PatchOp; + +pub fn json_pointer_parent<'a>( + root: &'a mut Value, + path: &str, +) -> Result<(&'a mut Value, String), String> { + if !path.starts_with('/') && path != "" { + return Err("path must be JSON pointer starting with '/'".into()); + } + + let part: Vec = path + .split('/') + .skip(1) + .map(|x| x.replace("~1", "/").replace("~0", "~")) + .collect(); + if path.is_empty() { + return Ok((root, "".into())); + } + + let last = part.last().unwrap().clone(); + + let mut cur = root; + + for key in &part[..part.len() - 1] { + match cur { + Value::Object(map) => { + cur = map + .get_mut(key) + .ok_or_else(|| format!("missing object key {key}"))?; + } + Value::Array(arr) => { + let idx: usize = key.parse().map_err(|_| format!("bad array idx {key}"))?; + cur = arr + .get_mut(idx) + .ok_or_else(|| format!("array index out of bounds {idx}"))?; + } + _ => return Err("cannot traverse non-container".into()), + } + } + + Ok((cur, last)) +} + +pub fn apply_ops(mut state: Value, ops: &[PatchOp]) -> Result { + for op in ops { + match op { + PatchOp::Replace { path, value } => { + if path == "" || path == "/" { + state = value.clone(); + continue; + } + let (parent, last) = json_pointer_parent(&mut state, path)?; + match parent { + Value::Object(map) => { + if !map.contains_key(&last) { + return Err(format!("replace target missing key {last}")); + } + map.insert(last, value.clone()); + } + Value::Array(arr) => { + let idx: usize = last + .parse() + .map_err(|_| format!("bad array index {last}"))?; + if idx >= arr.len() { + return Err(format!("replace index out of bounds {idx}")); + } + arr[idx] = value.clone(); + } + _ => return Err("replace parent not container".into()), + } + } + PatchOp::Add { path, value } => { + let (parent, last) = json_pointer_parent(&mut state, path)?; + match parent { + Value::Object(map) => { + map.insert(last, value.clone()); + } + Value::Array(arr) => { + if last == "-" { + arr.push(value.clone()); + } else { + let idx: usize = last + .parse() + .map_err(|_| format!("bad array index {last}"))?; + if idx > arr.len() { + return Err(format!("add index out of bounds {idx}")); + } + arr.insert(idx, value.clone()); + } + } + _ => return Err("add parent not container".into()), + } + } + PatchOp::Remove { path } => { + let (parent, last) = json_pointer_parent(&mut state, path)?; + match parent { + Value::Object(map) => { + map.remove(&last) + .ok_or_else(|| format!("remove missing key {last}"))?; + } + Value::Array(arr) => { + let idx: usize = last + .parse() + .map_err(|_| format!("bad array index {last}"))?; + if idx > arr.len() { + return Err(format!("remove index out of bounds {idx}")); + } + arr.remove(idx); + } + _ => return Err("remove parent not container".into()), + } + } + } + } + + Ok(state) +} diff --git a/src/tx/types.rs b/src/tx/types.rs new file mode 100644 index 0000000..661ad90 --- /dev/null +++ b/src/tx/types.rs @@ -0,0 +1,51 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Serialize, Deserialize)] +pub struct ReserveReq { + pub author: String, + hint_paths: Option>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ReserveRes { + pub reservation_id: String, + pub base_version: u64, + pub expires_at: i64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TxCommitReq { + pub reservation_id: String, + pub tx_id: String, + pub base_version: u64, + pub author: String, + pub ops: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TxCommitRes { + pub doc_id: String, + pub tx_id: String, + pub committed_version: u64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(tag = "op")] +pub enum PatchOp { + #[serde(rename = "replace")] + Replace { path: String, value: Value }, + #[serde(rename = "add")] + Add { path: String, value: Value }, + #[serde(rename = "remove")] + Remove { path: String }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Reservation { + pub reservation_id: String, + pub doc_id: String, + pub author: String, + pub base_version: u64, + pub expires_at: i64, +}