From 073e7d979df4bc63d5c1523b29891b801c3dc61f Mon Sep 17 00:00:00 2001 From: Mark <> Date: Sun, 2 Mar 2025 02:53:37 +0100 Subject: [PATCH] init --- .gitignore | 2 + Cargo.lock | 1560 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 12 + src/game.rs | 238 ++++++ src/main.rs | 457 ++++++++++++ src/stations_list.rs | 711 ++++++++++++++++++ src/stations_thread.rs | 58 ++ 7 files changed, 3038 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/game.rs create mode 100644 src/main.rs create mode 100644 src/stations_list.rs create mode 100644 src/stations_thread.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4896dc5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/stations_list diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e913409 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1560 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bahnreise" +version = "0.1.0" +dependencies = [ + "rand", + "rand_derive2", + "reqwest", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40545c26d092346d8a8dab71ee48e7685a7a9cba76e634790c215b41a4a7b4cf" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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 = "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-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[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-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +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.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[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.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc_macro2_helper" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79528bef70da112116feb5ecb6b64f1394e5360660d6474a760789ea07885501" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[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 = "rand_derive2" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e07d80c051ce2007c5cbb87ae0a6be7c5e5345cb03e06717b64ed5c426cbfb3" +dependencies = [ + "proc-macro2", + "proc_macro2_helper", + "quote", + "syn", +] + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.214" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +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 = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +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 = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d8a1819 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bahnreise" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = "0.8.5" +rand_derive2 = "0.1.21" +reqwest = { version = "0.12.9", features = ["blocking"] } +serde = { version = "1.0.214", features = ["derive"] } +serde_json = "1.0.132" +tokio = "1.41.1" diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..3a94835 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,238 @@ +use std::{error::Error, sync::Arc, time::Instant}; + +use crate::stations_list::{Arrivals, Departures, FilterTransports, StationsList}; + +pub struct Game { + stations: Arc, + history: Vec<(String, Option)>, + location: String, + location_name: Option, + departures: Result>, + target: String, + target_name: Option, + location_activity_count: usize, + target_activity_count: usize, + filter_transports: FilterTransports, + hint: Option<(Arrivals, Instant)>, +} +impl Game { + pub fn new( + filter_transports: FilterTransports, + location: Option, + target: Option, + stations: Arc, + ) -> Result> { + let location = if let Some(location) = location { + match stations + .find_stations(&location) + .first() + .and_then(|(id, _)| { + stations.get_station(id, |station| { + station + .query_activity_count(id, false, 10, filter_transports.only_known()) + .map(|c| (id.to_owned(), c)) + }) + }) { + Some(Ok((id, c))) => (c, id), + Some(Err(e)) => Err(e)?, + None => Err(format!("start does not exist"))?, + } + } else { + let mut location: Option<(usize, String)> = None; + let mut location_counter = 0; + for _ in 0..10 { + if let Some(st) = stations.get_random_station(3, |id, station| { + if let Some(act) = station + .query_activity_count(id, false, 10, filter_transports.only_known()) + .ok() + .filter(|v| *v > 1) + { + Some((act, id.to_owned())) + } else { + None + } + }) { + location_counter += 1; + if location.as_ref().is_none_or(|p| p.0 < st.0) { + location = Some(st); + } + // after 5 locations with 2+ departures, take the best one + if location_counter >= 5 { + break; + } + } + } + location.ok_or("could not find a start location")? + }; + let target = if let Some(target) = target { + match stations.find_stations(&target).first().and_then(|(id, _)| { + stations.get_station(id, |station| { + station + .query_activity_count(id, false, 10, filter_transports.only_known()) + .map(|c| (id.to_owned(), c)) + }) + }) { + Some(Ok((id, c))) => (c, id), + Some(Err(e)) => Err(e)?, + None => Err(format!("ziel does not exist"))?, + } + } else { + let mut target: Option<(usize, String)> = None; + let mut target_counter = 0; + for _ in 0..10 { + if let Some(st) = stations.get_random_station(3, |id, station| { + if let Some(act) = (id != location.1.as_str()) + .then(|| { + station + .query_activity_count(id, true, 10, filter_transports.only_known()) + .ok() + .filter(|v| *v > 1) + }) + .flatten() + { + Some((act, id.to_owned())) + } else { + None + } + }) { + target_counter += 1; + if target.as_ref().is_none_or(|p| p.0 < st.0) { + target = Some(st); + } + // after 5 targets with 2+ arrivals, take the best one + if target_counter >= 5 { + break; + } + } + } + target.ok_or("could not find a target station")? + }; + Ok(Self { + stations, + history: vec![], + location: location.1, + location_name: None, + departures: Err("".into()), + target: target.1, + target_name: None, + location_activity_count: location.0, + target_activity_count: target.0, + filter_transports, + hint: None, + }) + } + + pub fn history(&self) -> &[(String, Option)] { + &self.history + } + pub fn location(&self) -> &str { + &self.location + } + pub fn location_name(&mut self) -> &str { + if self.location_name.is_none() { + self.location_name = self + .stations + .get_station(self.location(), |s| s.name().to_owned()); + } + self.location_name + .as_ref() + .map(|v| v.as_str()) + .unwrap_or(self.location()) + } + pub fn location_name_immut(&self) -> &str { + self.location_name + .as_ref() + .map(|v| v.as_str()) + .unwrap_or(self.location()) + } + pub fn target_name_immut(&self) -> &str { + self.target_name + .as_ref() + .map(|v| v.as_str()) + .unwrap_or(self.target()) + } + pub fn target(&self) -> &str { + &self.target + } + pub fn target_name(&mut self) -> &str { + if self.target_name.is_none() { + self.target_name = self + .stations + .get_station(self.target(), |s| s.name().to_owned()); + } + self.target_name + .as_ref() + .map(|v| v.as_str()) + .unwrap_or(self.target()) + } + pub fn location_activity_count(&self) -> usize { + self.location_activity_count + } + pub fn target_activity_count(&self) -> usize { + self.target_activity_count + } + + pub fn update_departures(&mut self) { + let v = std::mem::replace(&mut self.departures, Err("".into())).or_else(|_| { + match self.stations.get_station(&self.location, |location| { + location.query_departures(&self.location, 15, self.filter_transports) + }) { + Some(v) => { + if let Ok(v) = &v { + self.stations.add_new_from_departures(v); + } + v + } + None => Err( + "current location no longer exists (this is most likely a server error)".into(), + ), + } + }); + self.departures = v; + } + pub fn departures_cached(&self) -> Result<&Departures, &Box> { + self.departures.as_ref() + } + /// Err if check failed, Ok(false) if 0 or 1 departures, Ok(true) otherwise + pub fn go_to_check(&self, location: &str) -> Result> { + self.stations + .requery_station_if_necessary_or_add_new(location); + if let Some(act) = self.stations.get_station(location, |s| { + s.query_activity_count(location, false, 10, self.filter_transports.only_known()) + }) { + Ok(act?) + } else { + Err("no such station".into()) + } + } + + /// true if win + pub fn go_to_unchecked(&mut self, location: String) -> bool { + let ploc = std::mem::replace(&mut self.location, location); + let plocn = self.location_name.take(); + self.history.push((ploc, plocn)); + self.departures = Err("".into()); + self.location == self.target + } + + pub fn get_hint(&mut self) -> Result<&Arrivals, Box> { + if self + .hint + .as_ref() + .is_none_or(|v| v.1.elapsed().as_secs_f64() > 60.0) + { + let o = self + .stations + .get_station(&self.target, |station| { + station.query_arrivals(&self.target, 15, self.filter_transports) + }) + .ok_or_else(|| "failed to find target station"); + if let Ok(Ok(o)) = o { + self.hint = Some((o, Instant::now())); + } else if self.hint.is_none() { + self.hint = Some((o??, Instant::now())); + } + } + Ok(&self.hint.as_ref().unwrap().0) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3b0249c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,457 @@ +use std::{ + collections::HashMap, + io::{BufRead, BufReader, BufWriter, Write}, + net::{TcpListener, TcpStream}, + sync::Arc, +}; + +use game::Game; +use stations_list::{FilterTransports, StationsList, ALL_FILTER_TRANSPORTS}; + +mod game; +mod stations_list; +mod stations_thread; + +fn main() { + let stations = StationsList::new_from("stations_list".into()).unwrap(); + let stations = Arc::new(stations); + let _thread = stations_thread::spawn(Arc::clone(&stations), 100); + let listener = TcpListener::bind("0.0.0.0:26021").unwrap(); + let mut threads: Vec> = vec![]; + let max_threads = 8; + loop { + if let Ok((mut con, _)) = listener.accept() { + if let Some(i) = threads + .iter() + .enumerate() + .rev() + .find(|v| v.1.is_finished()) + .map(|v| v.0) + { + threads.swap_remove(i); + } + if threads.len() < max_threads { + let stations = Arc::clone(&stations); + threads.push(std::thread::spawn(move || { + let _ = connection(&mut con, stations); + let _ = con.shutdown(std::net::Shutdown::Both); + })); + } else { + let _ = write!(&mut con, "server busy, try again later"); + let _ = con.shutdown(std::net::Shutdown::Both); + } + } + } +} + +fn connection(con: &mut TcpStream, stations: Arc) -> std::io::Result<()> { + let mut w = BufWriter::new(con); + writeln!(w, "–––=== Bahnhofsreise ===̣–––")?; + writeln!(w, "Bekannte Bahnhöfe: {}", stations.station_count())?; + let filter_transports: FilterTransports = rand::random(); + let mut filter_transports = filter_transports.and_unknown(); + let mut start: Vec<(String, String)> = vec![]; + let mut ziel: Vec<(String, String)> = vec![]; + loop { + for (i, ft) in ALL_FILTER_TRANSPORTS.iter().enumerate() { + writeln!( + w, + "Kategorie {}: [{}] {}", + i + 1, + if *ft == filter_transports { 'x' } else { ' ' }, + ft.explined() + )?; + } + write!(w, " Start: ")?; + if start.is_empty() { + writeln!(w, "Zufällig")?; + } else { + for (i, v) in start.iter().enumerate() { + if i != 0 { + write!(w, " / ")?; + } + write!(w, "{}", v.1)?; + } + writeln!(w)?; + } + write!(w, " Ziel: ")?; + if ziel.is_empty() { + writeln!(w, "Zufällig")?; + } else { + for (i, v) in ziel.iter().enumerate() { + if i != 0 { + write!(w, " / ")?; + } + write!(w, "{}", v.1)?; + } + writeln!(w)?; + } + writeln!( + w, + "Enter zum Starten, oder Kategorie <1-6>, oder Start , oder Ziel " + )?; + w.flush()?; + + let line = BufReader::new(w.get_mut()) + .lines() + .next() + .ok_or(std::io::ErrorKind::BrokenPipe)?? + .to_lowercase(); + let line = line.trim(); + match line { + "kategorie 1" => filter_transports = ALL_FILTER_TRANSPORTS[0], + "kategorie 2" => filter_transports = ALL_FILTER_TRANSPORTS[1], + "kategorie 3" => filter_transports = ALL_FILTER_TRANSPORTS[2], + "kategorie 4" => filter_transports = ALL_FILTER_TRANSPORTS[3], + "kategorie 5" => filter_transports = ALL_FILTER_TRANSPORTS[4], + "kategorie 6" => filter_transports = ALL_FILTER_TRANSPORTS[5], + "" => break, + line => { + if line.starts_with("start ") { + start = stations.find_stations(line[6..].trim()); + } else if line.starts_with("ziel ") { + ziel = stations.find_stations(line[5..].trim()); + } + } + } + } + + if start.is_empty() || ziel.is_empty() { + writeln!( + w, + "Suche nach Bahnhöfen, wo was los ist... (kann etwas dauern)", + )?; + } else { + writeln!(w, "Abfahrt . . .",)?; + } + w.flush()?; + start.truncate(1); + let start = start.pop(); + ziel.truncate(1); + let ziel = ziel.pop(); + let custom_ziel = ziel.is_some(); + let mut game = match Game::new( + filter_transports, + start.map(|v| v.0), + ziel.map(|v| v.0), + Arc::clone(&stations), + ) { + Ok(game) => game, + Err(e) => { + writeln!(w, "\n[FEHLER] Spielvorbereitung hat nicht geklappt :(\n")?; + writeln!(w, "{e}")?; + w.flush()?; + return Ok(()); + } + }; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w)?; + writeln!(w, "→ Dein Startbahnhof: {}.", game.location_name())?; + writeln!(w, " → Dein Zielbahnhof: {}.", game.target_name())?; + writeln!( + w, + " → Activity Scores: {} → {}", + game.location_activity_count(), + game.target_activity_count() + )?; + if custom_ziel && game.target_activity_count() < 3 { + writeln!( + w, + "Geringer Activity Score von {} am Ziel, sicher? [Enter])", + game.target_activity_count() + )?; + w.flush()?; + BufReader::new(w.get_mut()).lines().next(); + } + w.flush()?; + let mut skip_verbindungen = false; + loop { + if !skip_verbindungen { + writeln!(w)?; + writeln!(w)?; + writeln!(w, "–– Mögliche Verbindungen ––")?; + w.flush()?; + game.update_departures(); + game.location_name(); + } + let departures = match game.departures_cached() { + Ok(deps) => deps, + Err(e) => { + writeln!(w, "\n[FEHLER] Zugsuche hat nicht geklappt :(\n")?; + writeln!(w, "{}", e)?; + w.flush()?; + return Ok(()); + } + }; + let mut possible_locations = HashMap::::new(); + for zug in &departures.entries { + for linie in zug { + if linie.canceled { + if !skip_verbindungen { + writeln!( + w, + "[fällt aus] {} | {} → {}", + linie.line_name, linie.stop_place.name, linie.destination.name + )?; + } + } else { + if !skip_verbindungen { + writeln!( + w, + "{} | {} → {}", + linie.line_name, linie.stop_place.name, linie.destination.name + )?; + } + possible_locations.insert( + linie.destination.name.trim().to_lowercase(), + &linie.destination.slug, + ); + if !linie.via_stops.is_empty() { + if !skip_verbindungen { + write!(w, " via")?; + } + let mut width = 3; + for (istop, stop) in linie.via_stops.iter().enumerate() { + if !skip_verbindungen { + if istop > 0 { + write!(w, ",")?; + width += 1; + } + if width > 0 && width + stop.name.len() > 70 { + writeln!(w)?; + write!(w, " ")?; + width = 0; + } + if istop == 0 { + width += 1 + stop.name.len(); + write!(w, " {}", stop.name)?; + } else { + width += 4 + stop.name.len(); + write!(w, " {}", stop.name)?; + } + } + possible_locations.insert(stop.name.trim().to_lowercase(), &stop.slug); + } + if !skip_verbindungen { + writeln!(w)?; + } + } + } + if !skip_verbindungen { + writeln!(w)?; + } + } + if !skip_verbindungen { + writeln!(w)?; + } + } + if !skip_verbindungen { + writeln!(w)?; + writeln!(w)?; + writeln!(w, "→ Aktueller Bahnhof: {}.", game.location_name_immut())?; + writeln!(w, " → Dein Zielbahnhof: {}.", game.target_name_immut())?; + } + skip_verbindungen = false; + let mut r_errs = 0; + loop { + writeln!(w, "Wohin geht die Reise? (`?` für Hilfe)")?; + w.flush()?; + match BufReader::new(w.get_mut()).lines().next() { + None => return Ok(()), + Some(line) => { + let line = line?; + let line = line.trim(); + if line == "?" { + writeln!(w)?; + writeln!(w, "–– Erklärung ––")?; + writeln!( + w, + "Bahnreise ist ein Spiel basierend auf Live-Daten von `bahnhof.de`." + )?; + writeln!( + w, + "Ziel ist es, vom Startbahnhof aus den Zielbahnhof zu erreichen." + )?; + writeln!( + w, + "Das Spiel zeigt eine Liste von Verbindungen an, die an dem Bahnhof" + )?; + writeln!( + w, + "abfahren, an dem du gerade bist. Um irgendwohin zu fahren, tippe" + )?; + writeln!(w, "den Namen eines Bahnhofs eine und bestätige mit Enter.")?; + writeln!( + w, + "Das wiederholst du, bis du am Zielbahnhof angekommen bist." + )?; + writeln!( + w, + "Wenn du nicht weißt, wo der Zielbahnhof ist, kannst du dir" + )?; + writeln!( + w, + "mit `??` einen Tipp anzeigen lassen, der dir verrät, von wo aus" + )?; + writeln!( + w, + "du dein Ziel erreichen kannst. Sonst hilft nur noch eine Landkarte." + )?; + } else if line == "??" { + let target_name = game.target_name_immut().to_owned(); + match game.get_hint() { + Ok(hint) => { + let mut arrivals: Vec<(&'_ str, Vec<&'_ str>)> = vec![]; + for arrival in hint.entries.iter().map(|v| v.iter()).flatten() { + if let Some((_, lines)) = arrivals + .iter_mut() + .find(|v| v.0 == arrival.origin.name.as_str()) + { + if !arrival.canceled { + if !lines.contains(&arrival.line_name.as_str()) { + lines.push(arrival.line_name.as_str()); + } + } + } else { + arrivals.push(( + arrival.origin.name.as_str(), + if !arrival.canceled { + vec![arrival.line_name.as_str()] + } else { + vec![] + }, + )); + } + } + writeln!(w, "\n\n[TIPP] Züge, die in {target_name} ankommen:")?; + for (origin, lines) in arrivals { + writeln!( + w, + "- {origin} ({})", + if lines.is_empty() { + format!("all connections cancelled") + } else { + lines + .into_iter() + .enumerate() + .map(|(i, v)| { + format!("{}{v}", if i == 0 { "" } else { ", " }) + }) + .collect() + } + )?; + } + } + Err(e) => { + writeln!(w, "ups, das hat nicht geklappt... ({e})")?; + } + } + skip_verbindungen = true; + break; + } else if line.is_empty() { + r_errs += 1; + if r_errs > 25 { + return Ok(()); + } + } else if let Some(new) = possible_locations + .get(line.to_lowercase().as_str()) + .map(|v| (*v).to_owned()) + .or_else(|| { + stations + .find_stations(line) + .into_iter() + .filter_map(|(id, _)| { + possible_locations + .values() + .find(|v| **v == id.as_str()) + .map(|_| id.clone()) + }) + .next() + }) + { + if new.trim().is_empty() { + writeln!( + w, + "Diesen Halt gibt es scheinbar, aber er hat keine Bahnhofs-Seite," + )?; + writeln!(w, "d.h. es können leider auch keine Infos/Abfahrten erfragt werden :(")?; + w.flush()?; + } else { + match game.go_to_check(new.as_str()) { + Ok(0) if game.target() != new.as_str() => { + writeln!(w, "sieht dort recht leer aus... (this is softlock protection btw)")?; + w.flush()?; + } + Ok(act) => { + let mut go_to_station = true; + if act <= 3 && game.target() != new.as_str() { + writeln!( + w, + "Geringer Activity Score von {act}, sicher? [ja/nein])" + )?; + w.flush()?; + go_to_station = false; + if let Some(Ok(v)) = + BufReader::new(w.get_mut()).lines().next() + { + if v.as_str() == "ja" { + go_to_station = true; + } + } + } + if go_to_station { + let location = new; + if game.go_to_unchecked(location) { + writeln!(w)?; + writeln!(w)?; + writeln!(w, "yay, du bist da :D")?; + let ilen = game.history().len().to_string().len(); + for (i, (id, name)) in game.history().iter().enumerate() + { + let i = (i + 1).to_string(); + writeln!( + w, + "- {}{i}. {}", + " ".repeat(ilen.saturating_sub(i.len())), + name.as_ref() + .map(|v| v.as_str()) + .unwrap_or(id.as_str()) + )?; + } + writeln!( + w, + "- {} → {}", + " ".repeat(ilen.saturating_sub(1)), + game.location_name() + )?; + w.flush()?; + return Ok(()); + } else { + break; + } + } + } + Err(e) => { + writeln!(w, "hm, vielleicht lieber nicht dorthin? ({e})")?; + w.flush()?; + } + } + } + } else { + writeln!(w, "hm, kenne ich nicht... (typo?)")?; + r_errs += 1; + if r_errs > 25 { + return Ok(()); + } + } + } + } + } + } +} diff --git a/src/stations_list.rs b/src/stations_list.rs new file mode 100644 index 0000000..38be063 --- /dev/null +++ b/src/stations_list.rs @@ -0,0 +1,711 @@ +use std::{ + collections::HashMap, + error::Error, + fmt::Display, + path::PathBuf, + time::{Duration, SystemTime}, +}; + +use rand::{thread_rng, Rng}; +use rand_derive2::RandGen; +use serde::Deserialize; +use tokio::sync::Mutex; + +pub struct StationsList { + dir: PathBuf, + stations: Mutex>, +} +struct OptStation { + updated: Option, + station: Option, +} + +impl StationsList { + pub fn new_from(dir: PathBuf) -> Result> { + let _ = std::fs::create_dir_all(&dir); + let mut stations: HashMap = Default::default(); + for f in std::fs::read_dir(&dir)? { + let f = f?; + let fmeta = f.metadata()?; + if fmeta.is_file() { + let id = f.file_name(); + let id = id + .to_str() + .ok_or_else(|| format!("non-utf8 filename {:?}!", f.file_name()))?; + let fp = f.path(); + let save = std::fs::read_to_string(&fp)?; + let station = if save.trim().is_empty() { + None + } else { + Some(Station::from_save(id, save)?) + }; + stations.insert( + id.trim().to_owned(), + OptStation { + updated: Some(fmeta.modified().unwrap_or_else(|_| SystemTime::now())), + station, + }, + ); + } else if fmeta.is_dir() { + } + } + let mut count_s: usize = 0; + let mut count_n: usize = 0; + for station in stations.values() { + if station.station.is_some() { + count_s += 1; + } else { + count_n += 1; + } + } + eprintln!("Loaded {count_s} stations and {count_n} non-stations from {dir:?}"); + Ok(Self { + dir, + stations: Mutex::new(stations), + }) + } + + pub fn station_count(&self) -> usize { + self.stations + .blocking_lock() + .values() + .filter(|v| v.station.is_some()) + .count() + } + + pub fn add_new_from_departures(&self, deps: &Departures) { + let mut stations = self.stations.blocking_lock(); + for dep in deps.entries.iter().map(|v| v.iter()).flatten() { + for id in [&dep.stop_place.slug, &dep.destination.slug] + .into_iter() + .chain(dep.via_stops.iter().map(|v| &v.slug)) + .map(|v| v.trim()) + .filter(|v| !v.is_empty()) + { + if !stations.contains_key(id) { + stations.insert( + id.to_owned(), + OptStation { + updated: None, + station: None, + }, + ); + } + } + } + } + + pub fn query_for_new_stations(&self) -> Result<(), Box> { + // maybe find a new station or update an existing one + let html = reqwest::blocking::get("https://bahnhof.de/en/search")? + .error_for_status()? + .text()?; + for (match_index, match_str) in html.match_indices(r#"href="/en/"#) { + let id = &html[match_index + match_str.len()..]; + if let Some(end) = id.find(r#"""#) { + let id = id[..end].trim(); + if !id.contains(['/', '%', '&', '?', '#']) { + let mut stations = self.stations.blocking_lock(); + Self::requery_station_if_necessary_or_add_new_int(id, &self.dir, &mut stations); + } + } + } + Ok(()) + } + pub fn requery_random_station(&self, cache_count: usize) -> Result<(), Box> { + let mut stations = self.stations.blocking_lock(); + if !stations.is_empty() { + for id in stations + .iter() + .skip(thread_rng().gen_range(0..stations.len())) + .take(cache_count) + .map(|v| v.0.clone()) + .collect::>() + { + if Self::requery_station_if_necessary_or_add_new_int(&id, &self.dir, &mut stations) + { + break; + } + } + } + Ok(()) + } + pub fn requery_station_if_necessary_or_add_new(&self, id: &str) -> bool { + Self::requery_station_if_necessary_or_add_new_int( + id, + &self.dir, + &mut self.stations.blocking_lock(), + ) + } + fn requery_station_if_necessary_or_add_new_int( + id: &str, + dir: &PathBuf, + stations: &mut HashMap, + ) -> bool { + if let Some(s) = stations.get_mut(id) { + // recheck stations after a day, and recheck non-stations after a week + if s.updated.is_none_or(|updated| { + updated.elapsed().is_ok_and(|elapsed| { + elapsed + > Duration::from_secs( + if s.station.is_some() { 1 } else { 7 } * 24 * 60 * 60, + ) + }) + }) { + match Station::query_station(id) { + Ok(station) => { + if let Some(prev) = s.station.take() { + if prev == station { + eprintln!("Confirmed station {id} (unchanged)"); + } else { + eprintln!( + "Updated station {id} (changed): {prev:?} -> {station:?}" + ); + if let Err(e) = std::fs::write(dir.join(id), station.to_save()) { + eprintln!("[ERR] Couldn't save file {:?}: {e}", dir.join(id)); + } + } + } else { + eprintln!("Added new station {id}: non-station -> {station:?}"); + if let Err(e) = std::fs::write(dir.join(id), station.to_save()) { + eprintln!("[ERR] Couldn't save file {:?}: {e}", dir.join(id)); + } + } + s.station = Some(station); + } + Err(e) => { + if s.updated.is_none_or(|updated| { + updated.elapsed().is_ok_and(|elapsed| { + elapsed > Duration::from_secs(7 * 24 * 60 * 60) + }) + }) { + eprintln!("Error querying station {id} and last updated over a week ago, marking as non-station! Error: {e}"); + let _ = stations.remove(id); + if let Err(e) = std::fs::write(dir.join(id), "") { + eprintln!("[ERR] Couldn't save file {:?}: {e}", dir.join(id)); + } + } else { + eprintln!( + "Error querying station {id}, keeping old data for now. Error: {e}" + ); + } + } + } + true + } else { + false + } + } else { + stations.insert( + id.to_owned(), + OptStation { + updated: Some(SystemTime::now()), + station: match Station::query_station(id) { + Ok(station) => { + eprintln!("Added new station {id}: nothing -> {station:?}"); + if let Err(e) = std::fs::write(dir.join(id), station.to_save()) { + eprintln!("[ERR] Couldn't save file {:?}: {e}", dir.join(id)); + } + Some(station) + } + Err(e) => { + eprintln!("Marked {id} as not a station. Error: {e}"); + if let Err(e) = std::fs::write(dir.join(id), "") { + eprintln!("[ERR] Couldn't save file {:?}: {e}", dir.join(id)); + } + None + } + }, + }, + ); + true + } + } + + pub fn get_station(&self, id: &str, map: impl FnOnce(&Station) -> T) -> Option { + self.stations + .blocking_lock() + .get(id) + .and_then(|v| v.station.as_ref()) + .map(map) + } + + pub fn get_random_station( + &self, + limit_tries_cuberoot: usize, + map: impl Fn(&str, &Station) -> Option, + ) -> Option { + let stations = self.stations.blocking_lock(); + for i in 1..=limit_tries_cuberoot { + for i in rand::seq::index::sample( + &mut thread_rng(), + stations.len(), + (i * i).min(stations.len()), + ) { + if let Some((id, opt)) = stations.iter().nth(i) { + if let Some(station) = &opt.station { + if let Some(v) = map(id, station) { + return Some(v); + } + } + } + } + } + None + } + + pub fn find_stations(&self, name: &str) -> Vec<(String, String)> { + let stations = self.stations.blocking_lock(); + let name = name.to_lowercase(); + if let Some(s) = stations.get(&name).and_then(|v| v.station.as_ref()) { + vec![(name.to_owned(), s.name.clone())] + } else { + let mut o = HashMap::::new(); + for (id, station) in stations.iter() { + if let Some(station) = &station.station { + if station.name.to_lowercase() == name { + o.insert(id.clone(), (station.name.clone(), 0)); + } + } + } + if o.len() < 3 { + for (id, station) in stations.iter() { + if let Some(station) = &station.station { + if station.name.to_lowercase().starts_with(&name) { + o.insert( + id.clone(), + ( + station.name.clone(), + station.name.len().saturating_sub(name.len()).min(100), + ), + ); + } + } + } + if o.len() < 3 { + for (id, station) in stations.iter() { + if let Some(station) = &station.station { + if let Some(pos) = station.name.to_lowercase().find(&name) { + o.insert(id.clone(), (station.name.clone(), (100 + pos).min(200))); + } + } + } + } + } + let mut o = o.into_iter().collect::>(); + o.sort_unstable_by_key(|v| v.1 .1); + o.into_iter().map(|v| (v.0, v.1 .0)).collect() + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Station { + name: String, + eva_numbers: Vec, +} +impl Station { + pub fn name(&self) -> &str { + &self.name + } + + fn from_save(id: &str, save: String) -> Result> { + let mut name = None; + let mut eva_numbers = None; + for line in save.lines() { + match line + .split_once('=') + .map_or_else(|| ("", line.trim()), |(a, b)| (a.trim(), b.trim())) + { + ("name", v) => name = Some(v.to_owned()), + ("evanums", v) => eva_numbers = Some(v.to_owned()), + ("", line) => match line { + line => Err(format!("invalid flag-line in bahnhof {id}: {line}"))?, + }, + (key, value) => Err(format!( + "invalid key-value-line in bahnhof {id}: {key}={value}" + ))?, + } + } + Ok(Station { + name: name.ok_or_else(|| format!("in station {id}: missing name"))?, + eva_numbers: eva_numbers + .ok_or_else(|| format!("in station {id}: missing evanums"))? + .split(',') + .map(|v| { + v.trim() + .parse() + .map_err(|e| format!("eva number {v} could not be parsed: {e}")) + }) + .collect::>()?, + }) + } + fn to_save(&self) -> String { + let mut o = String::new(); + o.push_str("name="); + o.push_str(&self.name); + o.push('\n'); + if !self.eva_numbers.is_empty() { + o.push_str("evanums="); + for (i, num) in self.eva_numbers.iter().enumerate() { + if i != 0 { + o.push(','); + } + o.push_str(&format!("{num}")); + } + o.push('\n'); + } + o + } + + fn query_station(id: &str) -> Result> { + let html = + reqwest::blocking::get(format!("https://www.bahnhof.de/{id}/departure"))?.text()?; + let start_pat = r#"Abfahrt "#; + if let (Some(start), Some(end)) = (html.find(start_pat), html.find("")) { + let start = start + start_pat.len(); + let mut name = if end > start { + html[start..end].trim() + } else { + "" + }; + // skip next char (some UTF8 dash) + if !name.is_empty() { + name = name[name.chars().next().unwrap().len_utf8()..].trim(); + } + let pat = r#", _>>()?; + if eva_numbers.is_empty() { + Err("no evaNumbers (found empty list)")?; + } + Ok(Self { + name: name.to_owned(), + eva_numbers, + }) + } else { + Err("missing evaNumbers")? + } + } else { + Err("missing evaNumbers")? + } + } else { + let start_pat = ""; + if let (Some(start), Some(end)) = (html.find(start_pat), html.find("")) { + if start + start_pat.len() < end { + Err(format!( + "missing title `Abfahrt - `: title was `{}`", + &html[start + start_pat.len()..end] + ))? + } else { + Err(format!( + "missing title `Abfahrt - `: before " + ))? + } + } else { + Err(format!( + "missing title `Abfahrt - <name>`: no <title> found in `{html}`" + ))? + } + } + } + + pub fn query_departures( + &self, + id: &str, + minutes: u8, + filter_transports: FilterTransports, + ) -> Result<Departures, Box<dyn Error>> { + let _ = id; + if self.eva_numbers.is_empty() { + Err("station has no eva numbers")?; + } + let json = + reqwest::blocking::get(self.query_departures_url(false, minutes, filter_transports))? + .text()?; + Ok(serde_json::from_str(&json).map_err(|e| format!("{e}\nin:\n{json}"))?) + } + pub fn query_arrivals( + &self, + id: &str, + minutes: u8, + filter_transports: FilterTransports, + ) -> Result<Arrivals, Box<dyn Error>> { + let _ = id; + if self.eva_numbers.is_empty() { + Err("station has no eva numbers")?; + } + let json = + reqwest::blocking::get(self.query_departures_url(true, minutes, filter_transports))? + .text()?; + Ok(serde_json::from_str(&json).map_err(|e| format!("{e}\nin:\n{json}"))?) + } + fn query_departures_url( + &self, + arrivals: bool, + minutes: u8, + filter_transports: FilterTransports, + ) -> String { + let mut url = format!( + "https://www.bahnhof.de/api/boards/{}?", + if arrivals { "arrivals" } else { "departures" } + ); + for num in self.eva_numbers.iter() { + url.push_str(&format!("evaNumbers={num}&")); + } + url.push_str(&format!( + "duration={minutes}{filter_transports}&locale=de&sortBy=TIME_SCHEDULE" + )); + url + } + pub fn query_activity_count( + &self, + id: &str, + arrivals: bool, + minutes: u8, + filter_transports: FilterTransports, + ) -> Result<usize, Box<dyn Error>> { + let _ = id; + if self.eva_numbers.is_empty() { + Err("station has no eva numbers")?; + } + let mut url = format!( + "https://www.bahnhof.de/api/boards/{}?", + if arrivals { "arrivals" } else { "departures" } + ); + for num in self.eva_numbers.iter() { + url.push_str(&format!("evaNumbers={num}&")); + } + url.push_str(&format!( + "duration={minutes}{filter_transports}&locale=de&sortBy=TIME_SCHEDULE" + )); + let json = reqwest::blocking::get(url)?.text()?; + let arrivals: ArrivalsOrDepartures = + serde_json::from_str(&json).map_err(|e| format!("{e}\nin:\n{json}"))?; + Ok(arrivals + .entries + .iter() + .filter(|v| !v.iter().any(|v| v.canceled)) + .count()) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArrivalsOrDepartures { + pub entries: Vec<Vec<ArrivalOrDeparture>>, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArrivalOrDeparture { + pub canceled: bool, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Departures { + /// Vec<Vec<_>> because there may be a departure of multiple trains (usually coupled together, but still different trains/routes/destinations) + pub entries: Vec<Vec<Departure>>, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Departure { + pub canceled: bool, + pub line_name: String, + pub stop_place: SomeStation, + pub destination: SomeStation, + pub via_stops: Vec<SomeStation>, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SomeStation { + // pub eva_number: String, + pub name: String, + #[serde(default)] + pub slug: String, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Arrivals { + pub entries: Vec<Vec<Arrival>>, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Arrival { + pub canceled: bool, + pub line_name: String, + // pub stop_place: SomeStation, + pub origin: SomeStation, + // pub via_stops: Vec<SomeStation>, +} + +#[derive(Clone, Copy, RandGen, PartialEq, Eq)] +pub enum FilterTransports { + All, + AllKnown, + AllTrains, + AllTrainsKnown, + Trains, + TrainsKnown, + AllRegionalTrains, + AllRegionalTrainsKnown, + RegionalTrains, + RegionalTrainsKnown, + HighSpeedTrains, + HighSpeedTrainsKnown, +} +pub const ALL_FILTER_TRANSPORTS: [FilterTransports; 6] = [ + FilterTransports::All, + FilterTransports::AllTrains, + FilterTransports::Trains, + FilterTransports::AllRegionalTrains, + FilterTransports::RegionalTrains, + FilterTransports::HighSpeedTrains, +]; +impl Display for FilterTransports { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.allow_high_speed() { + write!( + f, + "&filterTransports=HIGH_SPEED_TRAIN&filterTransports=INTERCITY_TRAIN&filterTransports=INTER_REGIONAL_TRAIN" + )?; + } + if self.allow_regional() { + write!(f, "&filterTransports=REGIONAL_TRAIN")?; + } + if self.allow_local() { + write!(f, "&filterTransports=CITY_TRAIN&filterTransports=TRAM")?; + } + if self.allow_bus() { + write!(f, "&filterTransports=BUS")?; + } + if self.allow_unknown() { + write!(f, "&filterTransports=UNKNOWN")?; + } + Ok(()) + } +} +impl FilterTransports { + fn allow_unknown(&self) -> bool { + match self { + Self::All + | Self::AllTrains + | Self::Trains + | Self::AllRegionalTrains + | Self::RegionalTrains + | Self::HighSpeedTrains => true, + Self::AllKnown + | Self::AllTrainsKnown + | Self::TrainsKnown + | Self::AllRegionalTrainsKnown + | Self::RegionalTrainsKnown + | Self::HighSpeedTrainsKnown => false, + } + } + fn allow_bus(&self) -> bool { + match self { + Self::All | Self::AllKnown => true, + Self::AllTrains + | Self::AllTrainsKnown + | Self::Trains + | Self::TrainsKnown + | Self::AllRegionalTrains + | Self::AllRegionalTrainsKnown + | Self::RegionalTrains + | Self::RegionalTrainsKnown + | Self::HighSpeedTrains + | Self::HighSpeedTrainsKnown => false, + } + } + fn allow_local(&self) -> bool { + match self { + Self::All + | Self::AllKnown + | Self::AllTrains + | Self::AllTrainsKnown + | Self::AllRegionalTrains + | Self::AllRegionalTrainsKnown => true, + Self::Trains + | Self::TrainsKnown + | Self::RegionalTrains + | Self::RegionalTrainsKnown + | Self::HighSpeedTrains + | Self::HighSpeedTrainsKnown => false, + } + } + fn allow_regional(&self) -> bool { + match self { + Self::All + | Self::AllKnown + | Self::AllTrains + | Self::AllTrainsKnown + | Self::Trains + | Self::TrainsKnown + | Self::AllRegionalTrains + | Self::AllRegionalTrainsKnown + | Self::RegionalTrains + | Self::RegionalTrainsKnown => true, + Self::HighSpeedTrains | Self::HighSpeedTrainsKnown => false, + } + } + fn allow_high_speed(&self) -> bool { + match self { + Self::All + | Self::AllKnown + | Self::AllTrains + | Self::AllTrainsKnown + | Self::Trains + | Self::TrainsKnown + | Self::HighSpeedTrains + | Self::HighSpeedTrainsKnown => true, + Self::AllRegionalTrains + | Self::AllRegionalTrainsKnown + | Self::RegionalTrains + | Self::RegionalTrainsKnown => false, + } + } + + pub fn only_known(&self) -> Self { + match self { + Self::All | Self::AllKnown => Self::AllKnown, + Self::AllTrains | Self::AllTrainsKnown => Self::AllTrainsKnown, + Self::Trains | Self::TrainsKnown => Self::TrainsKnown, + Self::AllRegionalTrains | Self::AllRegionalTrainsKnown => Self::AllRegionalTrainsKnown, + Self::RegionalTrains | Self::RegionalTrainsKnown => Self::RegionalTrainsKnown, + Self::HighSpeedTrains | Self::HighSpeedTrainsKnown => Self::HighSpeedTrainsKnown, + } + } + pub fn and_unknown(&self) -> Self { + match self { + Self::All | Self::AllKnown => Self::All, + Self::AllTrains | Self::AllTrainsKnown => Self::AllTrains, + Self::Trains | Self::TrainsKnown => Self::Trains, + Self::AllRegionalTrains | Self::AllRegionalTrainsKnown => Self::AllRegionalTrains, + Self::RegionalTrains | Self::RegionalTrainsKnown => Self::RegionalTrains, + Self::HighSpeedTrains | Self::HighSpeedTrainsKnown => Self::HighSpeedTrains, + } + } + + pub fn explined(&self) -> &'static str { + match self { + Self::All | Self::AllKnown => "Öffis (Zug, S+U, Tram, Bus)", + Self::AllTrains | Self::AllTrainsKnown => "Schienenverkehr (Zug, S+U, Tram)", + Self::Trains | Self::TrainsKnown => "Züge (Zug, ohne Tram)", + Self::AllRegionalTrains | Self::AllRegionalTrainsKnown => { + "Deutschland-Ticket (Regio, S+U, Tram)" + } + Self::RegionalTrains | Self::RegionalTrainsKnown => "Regionalzüge (Regio, ohne Tram)", + Self::HighSpeedTrains | Self::HighSpeedTrainsKnown => "nur Hochgeschwindigkeitszüge", + } + } +} diff --git a/src/stations_thread.rs b/src/stations_thread.rs new file mode 100644 index 0000000..0530e5a --- /dev/null +++ b/src/stations_thread.rs @@ -0,0 +1,58 @@ +use std::{ + sync::{atomic::AtomicUsize, Arc}, + thread::{sleep, JoinHandle}, + time::Duration, +}; + +use rand::Rng; + +use crate::stations_list::StationsList; + +pub struct StationsThread { + #[allow(dead_code)] + min_stations: Arc<AtomicUsize>, + #[allow(dead_code)] + join_handle: JoinHandle<()>, +} +impl StationsThread { + #[allow(dead_code)] + pub fn turbo(&self, min_stations: usize) { + self.min_stations + .store(min_stations, std::sync::atomic::Ordering::Relaxed); + } +} + +pub fn spawn(stations: Arc<StationsList>, min_stations: usize) -> StationsThread { + let min_stations = Arc::new(AtomicUsize::new(min_stations)); + let min_station_count = Arc::clone(&min_stations); + let join_handle = std::thread::spawn(move || loop { + let turbo = + stations.station_count() < min_station_count.load(std::sync::atomic::Ordering::Relaxed); + sleep(Duration::from_secs( + rand::thread_rng().gen_range(10..=80) * if turbo { 1 } else { 60 }, + )); + match stations.query_for_new_stations() { + Ok(()) => (), + Err(e) => { + eprintln!("Failed to query for new stations:\n{e}"); + } + } + for _ in 0..10 { + sleep(Duration::from_secs(if turbo { + rand::thread_rng().gen_range(10..=30) + } else { + rand::thread_rng().gen_range(30..=120) + })); + match stations.requery_random_station(10) { + Ok(()) => (), + Err(e) => { + eprintln!("Failed to requery stations:\n{e}"); + } + } + } + }); + StationsThread { + min_stations, + join_handle, + } +}