From ab02e9a2a43dfb23a9a880eae683d437426c239e Mon Sep 17 00:00:00 2001 From: gvsafronov Date: Mon, 21 Jul 2025 20:44:54 +0300 Subject: [PATCH] first commit --- Cargo.lock | 2338 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 + WHAT'S NEW.txt | 11 + clif/Cargo.toml | 13 + clif/src/main.rs | 468 +++++++++ futriix.config.toml | 17 + futriix/Cargo.toml | 22 + futriix/src/main.rs | 77 ++ futriix/src/server.rs | 657 ++++++++++++ 9 files changed, 3609 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 WHAT'S NEW.txt create mode 100644 clif/Cargo.toml create mode 100644 clif/src/main.rs create mode 100644 futriix.config.toml create mode 100644 futriix/Cargo.toml create mode 100644 futriix/src/main.rs create mode 100644 futriix/src/server.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e23880f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2338 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[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 = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "clif" +version = "0.2.0" +dependencies = [ + "colored", + "rmp-serde", + "rustyline", + "serde", + "serde_json", + "tokio", + "toml", +] + +[[package]] +name = "clipboard-win" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + +[[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 = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix 0.30.1", + "windows-sys 0.59.0", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[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 = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fd-lock" +version = "3.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +dependencies = [ + "cfg-if", + "rustix 0.38.44", + "windows-sys 0.48.0", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futriix" +version = "0.2.0" +dependencies = [ + "chrono", + "colored", + "crossbeam", + "ctrlc", + "futures", + "log", + "parking_lot", + "rlua", + "rmp-serde", + "serde", + "serde_json", + "simplelog", + "tokio", + "toml", + "warp", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "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", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "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 = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +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.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.1", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.12+a4f56a4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "mlua" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +dependencies = [ + "bstr", + "mlua-sys", + "mlua_derive", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09697a6cec88e7f58a02c7ab5c18c611c6907c8654613df9cc0192658a4fb859" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.103", +] + +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 0.2.12", + "httparse", + "log", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[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-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rlua" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43f01c44a4ea0d2c7faadac231a0fd906a438723aa4ee26599e71fa7b915abd" +dependencies = [ + "mlua", +] + +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "rustyline" +version = "10.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e83c32c3f3c33b08496e0d1df9ea8c64d39adb8eb36a1ebb1440c690697aef" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix 0.25.1", + "radix_trie", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[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 = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "simplelog" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" +dependencies = [ + "log", + "termcolor", + "time", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[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 2.0.103", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[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.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +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.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[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 = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "warp" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http 0.2.12", + "hyper", + "log", + "mime", + "mime_guess", + "multer", + "percent-encoding", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.103", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix 1.0.8", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[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.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[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", + "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_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[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 2.0.103", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3d19ef1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +resolver = "2" # Резолвер "3" для полного соответствия edition 2024 +members = [ + "futriix", + "clif", +] diff --git a/WHAT'S NEW.txt b/WHAT'S NEW.txt new file mode 100644 index 0000000..596f658 --- /dev/null +++ b/WHAT'S NEW.txt @@ -0,0 +1,11 @@ + +1. Логи не удаляются при каждом запуске сервера +2. Клиент не запускается, если сервер выключен +3. Убраны лишние пустые строки в логах, которые отображаются в терминале при запуске сервера + +4. Добавлена поддержка MVCC-транзакций +5. Добавлена поддержка резервного копирования, чтобы из/в субд можно было бы выгружать/загружать данные командами: futload-загружать данные, futunload-выгружать данные. +6. Добавлена поддержка http-API +7. Переписана репликация с асинхронной мастер-мастер на синхронную мастер-мастер +8. Исправлена ошибка даты времени +9. Добавлен lua-интерпретатор в эксперементальном режиме diff --git a/clif/Cargo.toml b/clif/Cargo.toml new file mode 100644 index 0000000..88e79f7 --- /dev/null +++ b/clif/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "clif" +version = "0.2.0" +edition = "2024" + +[dependencies] +tokio = { version = "1.0", features = ["full"] } +rmp-serde = "0.15" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +toml = "0.7" +colored = "2.0" +rustyline = "10.0" diff --git a/clif/src/main.rs b/clif/src/main.rs new file mode 100644 index 0000000..8d47780 --- /dev/null +++ b/clif/src/main.rs @@ -0,0 +1,468 @@ +// clif/main.rs +use colored::Colorize; +use rustyline::Editor; +use rustyline::error::ReadlineError; +use serde_json; +use std::fs; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpStream; +use toml::Value; + +mod server { + use serde::{Deserialize, Serialize}; + use serde_json; + + #[derive(Debug, Serialize, Deserialize)] + pub enum Command { + Insert { key: String, value: serde_json::Value }, + Get { key: String, version: Option }, + Update { key: String, value: serde_json::Value }, + Delete { key: String }, + BeginTransaction, + CommitTransaction(u64), + RollbackTransaction(u64), + CreateIndex { field: String }, + DropIndex { field: String }, + SysExec { script_name: String }, + ReplicationEnable, + ReplicationDisable, + ReplicationAddPeer { addr: String }, + ReplicationRemovePeer { addr: String }, + ReplicationStatus, + BackupCreate { path: String }, + BackupRestore { path: String }, + LuaExec { code: String }, + Halt, + } + + #[derive(Debug, Serialize, Deserialize)] + pub enum Response { + Success(Option), + Error(String), + ReplicationStatus { + enabled: bool, + peers: Vec, + last_sync: u128, + }, + BackupStatus { success: bool, message: String }, + LuaOutput(String), + } +} + +use server::{Command, Response}; + +async fn send_command(cmd: Command) -> Result> { + let config_content = fs::read_to_string("futriix.config.toml")?; + let config: Value = toml::from_str(&config_content)?; + + let ip = config["client"]["ip"].as_str().unwrap_or("127.0.0.1"); + let port = config["client"]["port"].as_integer().unwrap_or(8080) as u16; + let addr = format!("{}:{}", ip, port); + + let mut stream = TcpStream::connect(&addr).await?; + + let cmd_bytes = rmp_serde::to_vec(&cmd)?; + let len = cmd_bytes.len() as u32; + stream.write_all(&len.to_be_bytes()).await?; + stream.write_all(&cmd_bytes).await?; + + let mut len_buf = [0u8; 4]; + stream.read_exact(&mut len_buf).await?; + let len = u32::from_be_bytes(len_buf) as usize; + + let mut buf = vec![0u8; len]; + stream.read_exact(&mut buf).await?; + + Ok(rmp_serde::from_slice(&buf)?) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let config_content = fs::read_to_string("futriix.config.toml")?; + let config: Value = toml::from_str(&config_content)?; + + let ip = config["server"]["ip"].as_str().unwrap_or("127.0.0.1"); + let port = config["server"]["port"].as_integer().unwrap_or(8080) as u16; + let addr = format!("{}:{}", ip, port); + + match TcpStream::connect(&addr).await { + Ok(_) => { + println!("\n{}", "Futriix CLI Client".truecolor(12, 149, 193)); + println!("Connected to server at {}", addr.green()); + println!("Type 'help' for available commands\n"); + }, + Err(e) => { + eprintln!("{}: Failed to connect to server at {}: {}", + "Error".red(), addr, e); + eprintln!("Please make sure futriix-server is running"); + std::process::exit(1); + } + } + + let mut rl = Editor::<()>::new()?; + if rl.load_history("futriix-history.txt").is_err() { + println!("No previous history."); + println!(); + } + + let mut current_tx_id: Option = None; + let mut lua_mode = false; + + loop { + let prompt = if lua_mode { + "lua> ".bright_yellow() + } else if let Some(tx_id) = current_tx_id { + format!("futriix:tx{}:~> ", tx_id).bright_cyan() + } else { + "futriix:~> ".bright_cyan() + }; + + let readline = rl.readline(&prompt); + match readline { + Ok(line) => { + rl.add_history_entry(&line); + + if line.trim().is_empty() { + continue; + } + + if lua_mode { + if line.trim() == "exit" { + lua_mode = false; + continue; + } + + match send_command(Command::LuaExec { code: line }).await { + Ok(Response::LuaOutput(output)) => { + println!("{}", output); + }, + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + continue; + } + + let parts: Vec<&str> = line.split_whitespace().collect(); + match parts[0].to_lowercase().as_str() { + "insert" | "i" => { + if parts.len() < 3 { + println!("{}", "Usage: insert ".red()); + continue; + } + let key = parts[1].to_string(); + let value_str = parts[2..].join(" "); + match serde_json::from_str(&value_str) { + Ok(value) => match send_command(Command::Insert { key, value }).await { + Ok(Response::Success(_)) => { + println!("{}", "Insert successful".bright_green()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + }, + Err(e) => println!("{}: {}", "Invalid JSON".red(), e), + } + }, + "get" | "g" => { + if parts.len() < 2 { + println!("{}", "Usage: get [version]".red()); + continue; + } + let key = parts[1].to_string(); + let version = if parts.len() > 2 { + parts[2].parse().ok() + } else { + None + }; + match send_command(Command::Get { key, version }).await { + Ok(Response::Success(Some(value))) => { + println!("{}", serde_json::to_string_pretty(&value)?); + println!(); + }, + Ok(Response::Success(None)) => { + println!("{}", "Key found but no value returned".yellow()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "update" | "u" => { + if parts.len() < 3 { + println!("{}", "Usage: update ".red()); + continue; + } + let key = parts[1].to_string(); + let value_str = parts[2..].join(" "); + match serde_json::from_str(&value_str) { + Ok(value) => match send_command(Command::Update { key, value }).await { + Ok(Response::Success(_)) => { + println!("{}", "Update successful".bright_green()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + }, + Err(e) => println!("{}: {}", "Invalid JSON".red(), e), + } + }, + "delete" | "d" => { + if parts.len() < 2 { + println!("{}", "Usage: delete ".red()); + continue; + } + let key = parts[1].to_string(); + match send_command(Command::Delete { key }).await { + Ok(Response::Success(_)) => { + println!("{}", "Delete successful".bright_green()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "begin" | "transaction" | "tx" => { + match send_command(Command::BeginTransaction).await { + Ok(Response::Success(Some(tx_id))) => { + if let Some(id) = tx_id.as_u64() { + current_tx_id = Some(id); + println!("{}", format!("Transaction {} started", id).bright_green()); + println!(); + } else { + println!("{}", "Invalid transaction ID received".red()); + } + }, + Ok(Response::Success(None)) => println!("{}", "No transaction ID received".red()), + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "commit" => { + if let Some(tx_id) = current_tx_id { + match send_command(Command::CommitTransaction(tx_id)).await { + Ok(Response::Success(_)) => { + println!("{}", format!("Transaction {} committed", tx_id).bright_green()); + println!(); + current_tx_id = None; + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + } else { + println!("{}", "No active transaction to commit".red()); + } + }, + "rollback" => { + if let Some(tx_id) = current_tx_id { + match send_command(Command::RollbackTransaction(tx_id)).await { + Ok(Response::Success(_)) => { + println!("{}", format!("Transaction {} rolled back", tx_id).bright_green()); + println!(); + current_tx_id = None; + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + } else { + println!("{}", "No active transaction to rollback".red()); + } + }, + "createindex" => { + if parts.len() < 2 { + println!("{}", "Usage: createindex ".red()); + continue; + } + let field = parts[1].to_string(); + match send_command(Command::CreateIndex { field: field.clone() }).await { + Ok(Response::Success(_)) => { + println!("{}", format!("Index created on field '{}'", field).bright_green()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "dropindex" => { + if parts.len() < 2 { + println!("{}", "Usage: dropindex ".red()); + continue; + } + let field = parts[1].to_string(); + match send_command(Command::DropIndex { field }).await { + Ok(Response::Success(_)) => { + println!("{}", "Index dropped".bright_green()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "sysexec" => { + if parts.len() < 2 { + println!("{}", "Usage: sysexec ".red()); + continue; + } + let script_name = parts[1].to_string(); + match send_command(Command::SysExec { script_name }).await { + Ok(Response::Success(Some(output))) => { + println!("{}", "Script output:".bright_green()); + println!("{}", output); + println!(); + }, + Ok(Response::Success(None)) => { + println!("{}", "Script executed but no output".yellow()); + println!(); + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "futexec" => { + println!("{}", "Entering Lua interactive mode. Type 'exit' to quit.".bright_yellow()); + lua_mode = true; + }, + "replication" => { + if parts.len() < 2 { + println!("{}", "Usage: replication ".red()); + continue; + } + match parts[1].to_lowercase().as_str() { + "on" => { + match send_command(Command::ReplicationEnable).await { + Ok(Response::Success(_)) => println!("{}", "Replication enabled".bright_green()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + } + "off" => { + match send_command(Command::ReplicationDisable).await { + Ok(Response::Success(_)) => println!("{}", "Replication disabled".bright_green()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + } + "status" => { + match send_command(Command::ReplicationStatus).await { + Ok(Response::ReplicationStatus { enabled, peers, last_sync }) => { + println!("Status:"); + println!(" Enabled: {}", enabled); + println!(" Peers: {:?}", peers); + println!(" Last sync: {}", last_sync); + } + Ok(Response::Success(_)) => println!("{}", "Unexpected success response".red()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + } + "add-peer" => { + if parts.len() < 3 { + println!("{}", "Usage: replication add-peer ".red()); + continue; + } + match send_command(Command::ReplicationAddPeer { addr: parts[2].to_string() }).await { + Ok(Response::Success(_)) => println!("{}", "Peer added".bright_green()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + } + "remove-peer" => { + if parts.len() < 3 { + println!("{}", "Usage: replication remove-peer ".red()); + continue; + } + match send_command(Command::ReplicationRemovePeer { addr: parts[2].to_string() }).await { + Ok(Response::Success(_)) => println!("{}", "Peer removed".bright_green()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + } + _ => println!("{}", "Unknown replication command".red()), + } + }, + "futload" => { + if parts.len() < 2 { + println!("{}", "Usage: futload ".red()); + continue; + } + match send_command(Command::BackupRestore { path: parts[1].to_string() }).await { + Ok(Response::BackupStatus { success, message }) => { + if success { + println!("{}", message.green()); + } else { + println!("{}", message.red()); + } + } + Ok(Response::Success(_)) => println!("{}", "Unexpected success response".red()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "futunload" => { + if parts.len() < 2 { + println!("{}", "Usage: futunload ".red()); + continue; + } + match send_command(Command::BackupCreate { path: parts[1].to_string() }).await { + Ok(Response::BackupStatus { success, message }) => { + if success { + println!("{}", message.green()); + } else { + println!("{}", message.red()); + } + } + Ok(Response::Success(_)) => println!("{}", "Unexpected success response".red()), + Ok(Response::Error(e)) => println!("{}", e.red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "halt" => { + match send_command(Command::Halt).await { + Ok(Response::Success(_)) => { + println!("{}", "Server shutdown initiated".bright_green()); + break; + }, + Ok(Response::Error(e)) => println!("{}", e.bold().red()), + _ => println!("{}", "Unexpected response type".red()), + } + }, + "help" => { + println!(); + println!("{}", "Available commands:".bold()); + println!(" insert (or i) - Insert data"); + println!(" get [version] (or g) - Get data (optionally specify version)"); + println!(" update (or u) - Update data"); + println!(" delete (or d) - Delete data"); + println!(" begin (or transaction, tx) - Start transaction"); + println!(" commit - Commit current transaction"); + println!(" rollback - Rollback current transaction"); + println!(" createindex - Create index"); + println!(" dropindex - Drop index"); + println!(" sysexec - Execute system script"); + println!(" futexec - Enter Lua interactive mode"); + println!(" replication on/off - Enable/disable replication"); + println!(" replication status - Show replication status"); + println!(" replication add-peer - Add replication peer"); + println!(" replication remove-peer - Remove replication peer"); + println!(" futload - Load data from backup file"); + println!(" futunload - Save data to backup file"); + println!(" halt - Gracefully shutdown the server"); + println!(" exit (or quit) - Exit client"); + println!(" help - Show this help"); + println!(); + }, + "exit" | "quit" => break, + _ => println!("{}: Unknown command. Type 'help' for available commands.", "Error".red()), + } + }, + Err(ReadlineError::Interrupted) => break, + Err(ReadlineError::Eof) => break, + Err(err) => { + println!("{}: {:?}", "Error".red(), err); + break; + } + } + } + + rl.save_history("futriix-history.txt")?; + Ok(()) +} diff --git a/futriix.config.toml b/futriix.config.toml new file mode 100644 index 0000000..6fa0d2f --- /dev/null +++ b/futriix.config.toml @@ -0,0 +1,17 @@ +[server] +ip = "127.0.0.1" # IP-адрес сервера (0.0.0.0 для доступа из сети) +port = 8080 # Порт для TCP-сервера +log_path = "futriix.log" # Путь к лог-файлу + +[client] +ip = "127.0.0.1" # IP для клиента (обычно localhost) +port = 8080 # Порт клиента (должен совпадать с серверным) + +[replication] +enabled = false # Включена ли репликация +peer_nodes = [] # Список узлов для репликации (например ["192.168.1.2:8080"]) +sync_interval = 1000 # Интервал синхронизации в мс + +[http_api] +enabled = true # Включить HTTP API +port = 8081 # Порт для HTTP API (обычно на 1 больше основного) diff --git a/futriix/Cargo.toml b/futriix/Cargo.toml new file mode 100644 index 0000000..95a8bfb --- /dev/null +++ b/futriix/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "futriix" +version = "0.2.0" +edition = "2024" + +[dependencies] +tokio = { version = "1.0", features = ["full"] } +rmp-serde = "0.15" # MessagePack +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +toml = "0.7" +colored = "2.0" +log = "0.4" +#simplelog = { version = "0.12.2", features = ["termcolor", "std"] } +simplelog = { version = "0.12.2", features = ["termcolor"] } +crossbeam = "0.8" +parking_lot = "0.12" +ctrlc = "3.2" +warp = "0.3" # Для HTTP API и HTTP-сервера +futures = "0.3" # Для работы с асинхронными потоками FutureExt +chrono = { version = "0.4", features = ["serde"] } +rlua = "0.20.1" diff --git a/futriix/src/main.rs b/futriix/src/main.rs new file mode 100644 index 0000000..88ec181 --- /dev/null +++ b/futriix/src/main.rs @@ -0,0 +1,77 @@ +// futriix/main.rs +mod server; + +use server::*; +use std::fs; +use std::path::Path; +use toml::Value; +use simplelog::*; +use chrono::Local; +use log::info; +use ctrlc; +use colored::Colorize; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let config_content = fs::read_to_string("futriix.config.toml")?; + let config: Value = toml::from_str(&config_content)?; + + let ip = config["server"]["ip"].as_str().unwrap_or("127.0.0.1"); + let port = config["server"]["port"].as_integer().unwrap_or(8080) as u16; + let log_path = config["server"]["log_path"].as_str().unwrap_or("futriix.log"); + + ctrlc::set_handler(move || { + println!("{}", "Server is now shutdown".truecolor(0, 191, 255)); + std::process::exit(0); + })?; + + let log_dir = Path::new("logs"); + if !log_dir.exists() { + fs::create_dir(log_dir)?; + } + + let timestamp = Local::now().format("%d.%m.%Y %H-%M-%S"); + let log_filename = format!("futriix_{}.log", timestamp); + let full_log_path = log_dir.join(log_filename); + + let symlink_path = Path::new(log_path); + if symlink_path.exists() { + fs::remove_file(symlink_path)?; + } + #[cfg(unix)] + std::os::unix::fs::symlink(full_log_path.clone(), symlink_path)?; + #[cfg(windows)] + std::os::windows::fs::symlink_file(full_log_path.clone(), symlink_path)?; + + let log_file = fs::OpenOptions::new() + .create(true) + .append(true) + .open(&full_log_path)?; + + CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Info, + Config::default(), + TerminalMode::Mixed, + ColorChoice::Auto, + ), + WriteLogger::new( + LevelFilter::Info, + Config::default(), + log_file, + ), + ])?; + + println!(); + println!("{}", "Futriix Server started successfully!".truecolor(0, 191, 255)); + + let server = FutriixServer::new(&config); + let addr = format!("{}:{}", ip, port); + info!("Starting server on {}", addr); + info!("Log file: {}", full_log_path.display()); + info!("Symlink: {}", symlink_path.display()); + + server.run(&addr).await?; + + Ok(()) +} diff --git a/futriix/src/server.rs b/futriix/src/server.rs new file mode 100644 index 0000000..97b70cc --- /dev/null +++ b/futriix/src/server.rs @@ -0,0 +1,657 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::Path; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use crossbeam::queue::SegQueue; +use parking_lot::RwLock; +use tokio::net::{TcpListener, TcpStream}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::time; +use log::{info, error, debug, warn}; +use warp::Filter; +use rlua::Lua; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub enum Command { + Insert { key: String, value: serde_json::Value }, + Get { key: String, version: Option }, + Update { key: String, value: serde_json::Value }, + Delete { key: String }, + BeginTransaction, + CommitTransaction(u64), + RollbackTransaction(u64), + CreateIndex { field: String }, + DropIndex { field: String }, + SysExec { script_name: String }, + Replicate { commands: Vec<(Command, u128)> }, + ReplicationEnable, + ReplicationDisable, + ReplicationAddPeer { addr: String }, + ReplicationRemovePeer { addr: String }, + ReplicationStatus, + BackupCreate { path: String }, + BackupRestore { path: String }, + LuaExec { code: String }, + Halt, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Response { + Success(Option), + Error(String), + ReplicationStatus { + enabled: bool, + peers: Vec, + last_sync: u128, + }, + BackupStatus { success: bool, message: String }, + LuaOutput(String), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct VersionedValue { + value: serde_json::Value, + version: u64, + timestamp: u128, + tx_id: Option, +} + +pub struct FutriixServer { + db: Arc>>>, + command_queue: Arc>, + transactions: Arc>>>, + indexes: Arc>>>>, + replication_enabled: bool, + peer_nodes: Vec, + sync_interval: u64, + last_sync_timestamp: Arc>, + command_history: Arc>>, + next_tx_id: Arc>, + shutdown: Arc>, +} + +impl FutriixServer { + pub fn new(config: &toml::Value) -> Self { + debug!("Initializing server with config: {:?}", config); + + let replication_enabled = config.get("replication") + .and_then(|r| r.get("enabled")) + .and_then(|v| v.as_bool()) + .unwrap_or(false); + + let peer_nodes = config.get("replication") + .and_then(|r| r.get("peer_nodes")) + .and_then(|n| n.as_array()) + .map(|nodes| { + nodes.iter() + .filter_map(|n| n.as_str().map(|s| s.to_string())) + .collect() + }) + .unwrap_or_default(); + + let sync_interval = config.get("replication") + .and_then(|r| r.get("sync_interval")) + .and_then(|i| i.as_integer()) + .map(|i| i as u64) + .unwrap_or(1000); + + FutriixServer { + db: Arc::new(RwLock::new(HashMap::new())), + command_queue: Arc::new(SegQueue::new()), + transactions: Arc::new(RwLock::new(HashMap::new())), + indexes: Arc::new(RwLock::new(HashMap::new())), + replication_enabled, + peer_nodes, + sync_interval, + last_sync_timestamp: Arc::new(RwLock::new(0)), + command_history: Arc::new(RwLock::new(Vec::new())), + next_tx_id: Arc::new(RwLock::new(1)), + shutdown: Arc::new(RwLock::new(false)), + } + } + + pub async fn run(&self, addr: &str) -> Result<(), Box> { + info!("Starting server on {}", addr); + let listener = TcpListener::bind(addr).await?; + + let http_port = addr.split(':').last().unwrap_or("8080").parse::().unwrap_or(8080) + 1; + let http_addr = format!("0.0.0.0:{}", http_port); + self.start_http_api(http_addr.clone()); + + info!("Futriix Server started successfully!"); + info!("Listening on: {}", addr); + info!("HTTP API available on: {}", http_addr); + + if self.replication_enabled { + info!("Replication enabled with peers: {:?}", self.peer_nodes); + let sync_task = self.start_replication_sync(); + tokio::spawn(sync_task); + } + + let scripts_dir = Path::new("scripts"); + if !scripts_dir.exists() { + if let Err(e) = std::fs::create_dir(scripts_dir) { + warn!("Failed to create scripts directory: {}", e); + } + } + + let backups_dir = Path::new("backups"); + if !backups_dir.exists() { + if let Err(e) = std::fs::create_dir(backups_dir) { + warn!("Failed to create backups directory: {}", e); + } + } + + loop { + if *self.shutdown.read() { + info!("Shutdown signal received, stopping server"); + break; + } + + match listener.accept().await { + Ok((socket, _)) => { + let server = self.clone(); + tokio::spawn(async move { + if let Err(e) = server.handle_connection(socket).await { + error!("Connection error: {}", e); + } + }); + } + Err(e) => { + error!("Accept error: {}", e); + } + } + } + Ok(()) + } + + fn start_http_api(&self, addr: String) { + let db = self.db.clone(); + let command_queue_insert = self.command_queue.clone(); + let command_queue_update = self.command_queue.clone(); + let command_queue_delete = self.command_queue.clone(); + + tokio::spawn(async move { + let get = warp::path!("api" / "get" / String) + .and(warp::get()) + .map(move |key: String| { + let db = db.read(); + match db.get(&key) { + Some(versions) => { + if let Some(latest) = versions.last() { + warp::reply::json(&latest.value) + } else { + warp::reply::json(&serde_json::Value::Null) + } + }, + None => warp::reply::json(&serde_json::Value::Null), + } + }); + + let insert = warp::path!("api" / "insert") + .and(warp::post()) + .and(warp::body::json()) + .map(move |data: HashMap| { + for (key, value) in data { + command_queue_insert.push(Command::Insert { key, value }); + } + warp::reply::json(&serde_json::json!({"status": "ok"})) + }); + + let update = warp::path!("api" / "update") + .and(warp::post()) + .and(warp::body::json()) + .map(move |data: HashMap| { + for (key, value) in data { + command_queue_update.push(Command::Update { key, value }); + } + warp::reply::json(&serde_json::json!({"status": "ok"})) + }); + + let delete = warp::path!("api" / "delete" / String) + .and(warp::delete()) + .map(move |key: String| { + command_queue_delete.push(Command::Delete { key }); + warp::reply::json(&serde_json::json!({"status": "ok"})) + }); + + let routes = get.or(insert).or(update).or(delete); + + warp::serve(routes) + .run(addr.parse::().unwrap()) + .await; + }); + } + + fn start_replication_sync(&self) -> impl std::future::Future + 'static { + let peer_nodes = self.peer_nodes.clone(); + let command_history = self.command_history.clone(); + let last_sync_timestamp = self.last_sync_timestamp.clone(); + let sync_interval = self.sync_interval; + + async move { + let mut interval = time::interval(Duration::from_millis(sync_interval)); + loop { + interval.tick().await; + + let commands_to_sync = { + let history = command_history.read(); + let last_sync = *last_sync_timestamp.read(); + history.iter() + .filter(|(_, ts)| *ts > last_sync) + .map(|(cmd, ts)| (cmd.clone(), *ts)) + .collect::>() + }; + + if !commands_to_sync.is_empty() { + for peer in &peer_nodes { + if let Ok(mut stream) = TcpStream::connect(peer).await { + let replicate_cmd = Command::Replicate { + commands: commands_to_sync.clone(), + }; + + if let Ok(cmd_bytes) = rmp_serde::to_vec(&replicate_cmd) { + let len = cmd_bytes.len() as u32; + if let Err(e) = stream.write_all(&len.to_be_bytes()).await { + warn!("Failed to write command length to peer {}: {}", peer, e); + continue; + } + if let Err(e) = stream.write_all(&cmd_bytes).await { + warn!("Failed to write command to peer {}: {}", peer, e); + } + } + } + } + } + } + } + } + + async fn handle_connection( + &self, + mut socket: TcpStream, + ) -> Result<(), Box> { + loop { + let mut len_buf = [0u8; 4]; + if let Err(e) = socket.read_exact(&mut len_buf).await { + error!("Failed to read command length: {}", e); + break; + } + let len = u32::from_be_bytes(len_buf) as usize; + + let mut buf = vec![0u8; len]; + if let Err(e) = socket.read_exact(&mut buf).await { + error!("Failed to read command: {}", e); + break; + } + + let cmd: Command = match rmp_serde::from_slice(&buf) { + Ok(cmd) => cmd, + Err(e) => { + error!("Failed to deserialize command: {}", e); + break; + } + }; + + self.command_queue.push(cmd.clone()); + + while let Some(cmd) = self.command_queue.pop() { + if let Command::Replicate { commands } = cmd { + for (cmd, ts) in commands { + match cmd { + Command::Insert { key, value } | Command::Update { key, value } => { + let mut db = self.db.write(); + let timestamp = match SystemTime::now() + .duration_since(UNIX_EPOCH) + { + Ok(duration) => duration.as_millis(), + Err(e) => { + error!("SystemTime error: {}", e); + 0 + } + }; + let versioned_value = VersionedValue { + value: value.clone(), + version: 0, + timestamp, + tx_id: None, + }; + db.entry(key.clone()) + .or_default() + .push(versioned_value); + } + _ => { + let _ = Self::process_command( + &self.db, + &self.transactions, + &self.indexes, + &self.next_tx_id, + &self.shutdown, + cmd, + ); + } + } + *self.last_sync_timestamp.write() = ts; + } + continue; + } + + let response = Self::process_command( + &self.db, + &self.transactions, + &self.indexes, + &self.next_tx_id, + &self.shutdown, + cmd.clone(), + ); + + let response_bytes = match rmp_serde::to_vec(&response) { + Ok(bytes) => bytes, + Err(e) => { + error!("Failed to serialize response: {}", e); + break; + } + }; + let len = response_bytes.len() as u32; + if let Err(e) = socket.write_all(&len.to_be_bytes()).await { + error!("Failed to write response length: {}", e); + break; + } + if let Err(e) = socket.write_all(&response_bytes).await { + error!("Failed to write response: {}", e); + break; + } + } + } + Ok(()) + } + + fn process_command( + db: &Arc>>>, + transactions: &Arc>>>, + indexes: &Arc>>>>, + next_tx_id: &Arc>, + shutdown: &Arc>, + cmd: Command, + ) -> Response { + match cmd { + Command::Insert { key, value } => { + let mut db = db.write(); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); + let version = db.get(&key).map_or(0, |v| v.last().map_or(0, |vv| vv.version + 1)); + + let versioned_value = VersionedValue { + value: value.clone(), + version, + timestamp, + tx_id: None, + }; + + db.entry(key.clone()) + .or_default() + .push(versioned_value.clone()); + + Self::update_indexes(indexes, key, &value); + Response::Success(None) + }, + Command::Get { key, version } => { + let db = db.read(); + match db.get(&key) { + Some(versions) => { + let value = if let Some(v) = version { + versions.iter().find(|vv| vv.version == v) + } else { + versions.last() + }; + + match value { + Some(vv) => Response::Success(Some(vv.value.clone())), + None => Response::Error("Version not found".to_string()), + } + }, + None => Response::Error("Key not found".to_string()), + } + }, + Command::Update { key, value } => { + let mut db = db.write(); + if db.contains_key(&key) { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis(); + let version = db.get(&key).unwrap().last().unwrap().version + 1; + + let versioned_value = VersionedValue { + value: value.clone(), + version, + timestamp, + tx_id: None, + }; + + db.get_mut(&key).unwrap().push(versioned_value.clone()); + Self::update_indexes(indexes, key, &value); + Response::Success(None) + } else { + Response::Error("Key not found".to_string()) + } + }, + Command::Delete { key } => { + let mut db = db.write(); + if db.remove(&key).is_some() { + Self::remove_from_indexes(indexes, key); + Response::Success(None) + } else { + Response::Error("Key not found".to_string()) + } + }, + Command::BeginTransaction => { + let tx_id = { + let mut next_id = next_tx_id.write(); + let id = *next_id; + *next_id += 1; + id + }; + + let mut transactions = transactions.write(); + transactions.insert(tx_id, HashMap::new()); + Response::Success(Some(tx_id.into())) + }, + Command::CommitTransaction(tx_id) => { + let mut db = db.write(); + let mut transactions = transactions.write(); + + if let Some(tx_data) = transactions.remove(&tx_id) { + for (key, versioned_value) in tx_data { + db.entry(key.clone()) + .or_default() + .push(versioned_value.clone()); + Self::update_indexes(indexes, key, &versioned_value.value); + } + Response::Success(None) + } else { + Response::Error("Transaction not found".to_string()) + } + }, + Command::RollbackTransaction(tx_id) => { + let mut transactions = transactions.write(); + if transactions.remove(&tx_id).is_some() { + Response::Success(None) + } else { + Response::Error("Transaction not found".to_string()) + } + }, + Command::CreateIndex { field } => { + let mut indexes = indexes.write(); + if !indexes.contains_key(&field) { + indexes.insert(field, HashMap::new()); + Response::Success(None) + } else { + Response::Error("Index already exists".to_string()) + } + }, + Command::DropIndex { field } => { + let mut indexes = indexes.write(); + if indexes.remove(&field).is_some() { + Response::Success(None) + } else { + Response::Error("Index not found".to_string()) + } + }, + Command::SysExec { script_name } => { + match Self::execute_lua_script(&script_name) { + Ok(output) => Response::Success(Some(output.into())), + Err(e) => Response::Error(format!("Script execution failed: {}", e)), + } + }, + Command::LuaExec { code } => { + match Self::execute_lua_code(&code) { + Ok(output) => Response::LuaOutput(output), + Err(e) => Response::Error(format!("Lua execution failed: {}", e)), + } + }, + Command::ReplicationEnable => Response::Success(None), + Command::ReplicationDisable => Response::Success(None), + Command::ReplicationAddPeer { addr: _ } => Response::Success(None), + Command::ReplicationRemovePeer { addr: _ } => Response::Success(None), + Command::ReplicationStatus => Response::Success(None), + Command::Replicate { .. } => Response::Success(None), + Command::BackupCreate { path } => { + match Self::create_backup(db, &path) { + Ok(_) => Response::BackupStatus { + success: true, + message: format!("Backup created successfully at {}", path), + }, + Err(e) => Response::BackupStatus { + success: false, + message: format!("Backup failed: {}", e), + }, + } + }, + Command::BackupRestore { path } => { + match Self::restore_backup(db, indexes, &path) { + Ok(_) => Response::BackupStatus { + success: true, + message: format!("Backup restored successfully from {}", path), + }, + Err(e) => Response::BackupStatus { + success: false, + message: format!("Restore failed: {}", e), + }, + } + }, + Command::Halt => { + *shutdown.write() = true; + Response::Success(None) + }, + } + } + + fn execute_lua_code(code: &str) -> Result> { + let lua = Lua::new(); + let result = lua.load(code).eval::()?; + Ok(result.to_str()?.to_string()) + } + + fn create_backup( + db: &Arc>>>, + path: &str, + ) -> Result<(), Box> { + let db = db.read(); + let serialized = serde_json::to_string(&*db)?; + std::fs::write(path, serialized)?; + Ok(()) + } + + fn restore_backup( + db: &Arc>>>, + indexes: &Arc>>>>, + path: &str, + ) -> Result<(), Box> { + let data = std::fs::read_to_string(path)?; + let restored: HashMap> = serde_json::from_str(&data)?; + + let mut db = db.write(); + *db = restored; + + let mut indexes = indexes.write(); + indexes.clear(); + + for (key, versions) in db.iter() { + if let Some(latest) = versions.last() { + for (field, index) in indexes.iter_mut() { + if let Some(field_value) = latest.value.get(field) { + index.entry(field_value.clone()) + .or_default() + .push(key.clone()); + } + } + } + } + + Ok(()) + } + + fn update_indexes( + indexes: &Arc>>>>, + key: String, + value: &serde_json::Value, + ) { + let mut indexes = indexes.write(); + for (field, index) in indexes.iter_mut() { + if let Some(field_value) = value.get(field) { + index.entry(field_value.clone()) + .or_default() + .push(key.clone()); + } + } + } + + fn remove_from_indexes( + indexes: &Arc>>>>, + key: String, + ) { + let mut indexes = indexes.write(); + for index in indexes.values_mut() { + for entries in index.values_mut() { + if let Some(pos) = entries.iter().position(|k| k == &key) { + entries.remove(pos); + } + } + } + } + + fn execute_lua_script(script_name: &str) -> Result> { + let script_path = Path::new("scripts").join(script_name).with_extension("lua"); + let output = std::process::Command::new("lua") + .arg(&script_path) + .output()?; + + if output.status.success() { + Ok(String::from_utf8(output.stdout)?) + } else { + Err(String::from_utf8(output.stderr)?.into()) + } + } +} + +impl Clone for FutriixServer { + fn clone(&self) -> Self { + FutriixServer { + db: self.db.clone(), + command_queue: self.command_queue.clone(), + transactions: self.transactions.clone(), + indexes: self.indexes.clone(), + replication_enabled: self.replication_enabled, + peer_nodes: self.peer_nodes.clone(), + sync_interval: self.sync_interval, + last_sync_timestamp: self.last_sync_timestamp.clone(), + command_history: self.command_history.clone(), + next_tx_id: self.next_tx_id.clone(), + shutdown: self.shutdown.clone(), + } + } +}