From a3cc54f8a67a403d368c1ccf3a90fbab83c77479 Mon Sep 17 00:00:00 2001 From: quexeky Date: Tue, 20 Jan 2026 17:44:33 +1100 Subject: [PATCH] feat: CLI Configuration and maintainability --- cli/.gitignore | 3 +- cli/Cargo.lock | 400 ++++++++++++++++++++++++++- cli/Cargo.toml | 3 + cli/flake.nix | 1 + cli/src/cli.rs | 12 +- cli/src/commands/upload/interface.rs | 7 +- cli/src/commands/upload/mod.rs | 3 +- cli/src/commands/upload/s3.rs | 5 +- cli/src/commands/upload/void.rs | 40 --- cli/src/config.rs | 67 ----- cli/src/config/config.rs | 90 ++++++ cli/src/config/configurable.rs | 7 + cli/src/config/mod.rs | 4 + cli/src/config/s3.rs | 72 +++++ cli/src/config/server.rs | 18 ++ cli/src/interactive.rs | 47 ++++ cli/src/main.rs | 29 +- 17 files changed, 672 insertions(+), 136 deletions(-) delete mode 100644 cli/src/commands/upload/void.rs delete mode 100644 cli/src/config.rs create mode 100644 cli/src/config/config.rs create mode 100644 cli/src/config/configurable.rs create mode 100644 cli/src/config/mod.rs create mode 100644 cli/src/config/s3.rs create mode 100644 cli/src/config/server.rs create mode 100644 cli/src/interactive.rs diff --git a/cli/.gitignore b/cli/.gitignore index b13d16da..9c242044 100644 --- a/cli/.gitignore +++ b/cli/.gitignore @@ -1,2 +1,3 @@ /target -logs/ \ No newline at end of file +logs/ +.vscode \ No newline at end of file diff --git a/cli/Cargo.lock b/cli/Cargo.lock index aec11375..524ea235 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -220,6 +235,21 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link 0.2.1", +] + [[package]] name = "base64" version = "0.22.1" @@ -354,6 +384,33 @@ dependencies = [ "cc", ] +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.4" @@ -426,6 +483,15 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -461,6 +527,33 @@ dependencies = [ "libc", ] +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -521,6 +614,28 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "dialoguer" version = "0.12.0" @@ -544,6 +659,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -564,6 +700,15 @@ dependencies = [ "const-random", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downpour" version = "0.1.0" @@ -572,11 +717,14 @@ dependencies = [ "async-trait", "chrono", "clap", + "color-eyre", "console", "dialoguer", + "dirs", "droplet-rs", "fern", "indicatif", + "inquire", "log", "rand", "reqwest 0.13.1", @@ -655,6 +803,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -802,6 +960,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -839,6 +1006,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "h2" version = "0.4.13" @@ -1154,6 +1327,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "2.12.1" @@ -1177,6 +1356,20 @@ dependencies = [ "web-time", ] +[[package]] +name = "inquire" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae51d5da01ce7039024fbdec477767c102c454dbdb09d4e2a432ece705b1b25d" +dependencies = [ + "bitflags", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -1265,6 +1458,16 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -1277,6 +1480,21 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -1333,6 +1551,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[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.1.1" @@ -1340,6 +1567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -1464,6 +1692,15 @@ dependencies = [ "objc2-core-foundation", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.7.1" @@ -1544,6 +1781,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-multimap" version = "0.7.3" @@ -1554,6 +1797,35 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + [[package]] name = "pem" version = "3.0.6" @@ -1695,9 +1967,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1751,6 +2023,26 @@ dependencies = [ "yasna", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + [[package]] name = "reqwest" version = "0.12.28" @@ -1889,12 +2181,27 @@ dependencies = [ "url", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rusticata-macros" version = "4.1.0" @@ -2041,6 +2348,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "security-framework" version = "2.11.1" @@ -2077,6 +2390,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2143,6 +2462,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shell-words" version = "1.1.1" @@ -2155,6 +2483,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.7" @@ -2212,9 +2561,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -2329,6 +2678,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.44" @@ -2527,6 +2885,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -2547,6 +2927,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[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.2.2" @@ -2600,6 +2986,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index be242caa..0d407dd5 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -10,6 +10,7 @@ chrono = "0.4.43" clap = { version = "4.5.54", features = ["derive"] } console = "0.16.2" dialoguer = "0.12.0" +dirs = "6.0.0" droplet-rs = { git = "https://github.com/Drop-OSS/droplet-rs.git", version = "0.14" } fern = { version = "0.7.1", features = ["colored"] } indicatif = "0.18.3" @@ -23,3 +24,5 @@ tokio = { version = "1.48.0", features = ["macros"] } tokio-util = "0.7.18" url = "2.5.8" webbrowser = "1.0.6" +color-eyre = "0.6.5" +inquire = "0.9.2" diff --git a/cli/flake.nix b/cli/flake.nix index e196f2cc..e98989c5 100644 --- a/cli/flake.nix +++ b/cli/flake.nix @@ -34,6 +34,7 @@ git rust-bin.nightly.latest.default rust-analyzer + cargo-expand ]; diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 96e11d2b..944b95ad 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -2,6 +2,8 @@ use std::path::PathBuf; use clap::{Args, Parser, Subcommand, ValueEnum}; +use crate::config::config::ConfigOption; + #[derive(Parser)] #[command(version, about, long_about = None)] pub struct Cli { @@ -16,16 +18,12 @@ pub struct Cli { #[derive(Subcommand)] pub enum Commands { /// Configures a new Drop server - Configure { - /// Endpoint of the Drop server - url: String, - /// API token for non-interactive configuration. - #[arg(short, long)] - token: Option, - }, + #[command(subcommand)] + Configure(ConfigOption), /// Uploads new game version to depot Upload(UploadInfo), } + #[derive(Args)] pub struct UploadInfo { /// Identifies the specific upload style that will be used for the set depot diff --git a/cli/src/commands/upload/interface.rs b/cli/src/commands/upload/interface.rs index c4e81834..dbcbd988 100644 --- a/cli/src/commands/upload/interface.rs +++ b/cli/src/commands/upload/interface.rs @@ -1,10 +1,7 @@ use std::path::Path; use crate::{ - cli::UploadInfo, - commands::upload::{s3::S3, uploadable::Uploadable}, - config::Config, - manifest::generate_manifest, + cli::UploadInfo, commands::upload::{s3::S3, uploadable::Uploadable}, config::config::Config, manifest::generate_manifest }; use log::info; @@ -16,7 +13,7 @@ pub async fn upload(info: &UploadInfo, config: Config) -> anyhow::Result<()> { let manifest = generate_manifest(&Path::new(path)).await?; let mut uploader: Box = match info.upload_style { crate::cli::UploadStyle::S3 => Box::new(S3::new( - config + &config .get_active_s3() .ok_or(anyhow::Error::msg("Could not get active S3 value"))?, )?), diff --git a/cli/src/commands/upload/mod.rs b/cli/src/commands/upload/mod.rs index 8c948cde..54a7fc1b 100644 --- a/cli/src/commands/upload/mod.rs +++ b/cli/src/commands/upload/mod.rs @@ -1,5 +1,4 @@ pub mod interface; pub mod s3; +pub mod speedtest; pub mod uploadable; -pub mod void; -pub mod speedtest; \ No newline at end of file diff --git a/cli/src/commands/upload/s3.rs b/cli/src/commands/upload/s3.rs index a838d29e..2de7c614 100644 --- a/cli/src/commands/upload/s3.rs +++ b/cli/src/commands/upload/s3.rs @@ -2,12 +2,11 @@ use crate::{ commands::upload::{ speedtest::{SPEEDTEST_PATH, Speedtest}, uploadable::Uploadable, - }, - config::S3Config, + }, config::s3::S3Config, }; use async_trait::async_trait; use droplet_rs::manifest::{ChunkData, Manifest}; -use s3::{Bucket, creds::Credentials}; +use s3::Bucket; use serde_json::json; use std::{ops::Deref, path::PathBuf}; diff --git a/cli/src/commands/upload/void.rs b/cli/src/commands/upload/void.rs deleted file mode 100644 index 28503c8b..00000000 --- a/cli/src/commands/upload/void.rs +++ /dev/null @@ -1,40 +0,0 @@ -use async_trait::async_trait; -use droplet_rs::manifest::{ChunkData, Manifest}; -use log::warn; - -use crate::commands::upload::uploadable::Uploadable; - -pub struct VoidUploadable; -#[async_trait] -impl Uploadable for VoidUploadable { - async fn upload_chunk( - &mut self, - _id: &String, - _version: &String, - _chunk_id: &String, - _chunk: &ChunkData, - ) -> anyhow::Result<()> { - warn!("Uploading chunk to VoidUploader"); - Ok(()) - } - - async fn upload_speedtest(&mut self) -> anyhow::Result<()> { - warn!("Uploading speedtest to VoidUploader"); - Ok(()) - } - - async fn upload_manifest( - &mut self, - _manifest: Manifest, - _game_id: &String, - _version_id: &String, - ) -> anyhow::Result<()> { - warn!("Uploading manifest to VoidUploader"); - Ok(()) - } -} -impl VoidUploadable { - pub fn new() -> Self { - Self - } -} diff --git a/cli/src/config.rs b/cli/src/config.rs deleted file mode 100644 index ef66a075..00000000 --- a/cli/src/config.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::str::FromStr; - -use s3::{Bucket, Region, creds::Credentials}; - -pub struct Config { - items: Vec, - active_s3: Option, -} -pub struct ConfigItem { - name: String, - config_option: ConfigOption, -} -enum ConfigOption { - S3(S3Config), -} -pub struct S3Config { - secret_key: String, - key_id: String, - region: String, - bucket_name: String, - endpoint: Option, -} -impl Config { - pub fn new() -> Self { - Self { - items: Vec::new(), - active_s3: None, - } - } - pub fn get_active_s3(&self) -> Option<&S3Config> { - if let Some(active_s3) = &self.active_s3 { - self.items - .iter() - .filter_map(|item| { - if item.name == *active_s3 { - match &item.config_option { - ConfigOption::S3(s3_config) => Some(s3_config), - } - } else { - None - } - }) - .next() - } else { - None - } - } -} -impl S3Config { - pub fn generate_bucket(&self) -> anyhow::Result { - let credentials = - Credentials::new(Some(&self.key_id), Some(&self.secret_key), None, None, None)?; - - let region = if let Some(endpoint) = &self.endpoint { - Region::Custom { - region: self.region.clone(), - endpoint: endpoint.clone(), - } - } else { - Region::from_str(&self.region)? - }; - - let bucket = Bucket::new(&self.bucket_name, region, credentials)?; - - Ok(*bucket) - } -} diff --git a/cli/src/config/config.rs b/cli/src/config/config.rs new file mode 100644 index 00000000..27ff92a2 --- /dev/null +++ b/cli/src/config/config.rs @@ -0,0 +1,90 @@ +use std::{fs, str::FromStr}; + +use clap::Subcommand; +use dialoguer::{Input, theme::ColorfulTheme}; +use log::warn; +use serde::{Deserialize, Serialize}; + +use crate::config::{ + s3::{S3Config, S3ConfigCli}, + server::ServerConfig, +}; + +const CONFIG_DIR: &str = "downpour/config.json"; + +#[derive(Serialize, Deserialize)] +pub struct Config { + items: Vec, + active_s3: Option, +} +impl Config { + pub fn save(&self) -> anyhow::Result<()> { + let json = serde_json::to_string(self)?; + let save_path = dirs::config_dir() + .expect("Apparently your home directory doesn't exist") // Should probably formalise that error + .join(CONFIG_DIR); + fs::write(save_path, json)?; + Ok(()) + } + pub fn read() -> Self { + let save_path = dirs::config_dir() + .expect("Apparently your home directory doesn't exist") // Should probably formalise that error + .join(CONFIG_DIR); + if fs::exists(&save_path).expect(&format!("Could not read save path {:#?}", &save_path)) { + serde_json::from_str(&fs::read_to_string(save_path).unwrap()).unwrap() + } else { + Config::new() + } + } + pub fn add_item(&mut self, item: ConfigItem) { + if matches!(item.config_option, ConfigOption::S3(..)) { + self.active_s3 = Some(item.name.clone()) + } + self.items.push(item); + } +} + +#[derive(Serialize, Deserialize)] +pub struct ConfigItem { + name: String, + config_option: ConfigOption, +} +#[derive(Subcommand, Serialize, Deserialize)] +pub enum ConfigOption { + Server(ServerConfig), + S3(S3ConfigCli), +} + +impl Config { + pub fn new() -> Self { + Self { + items: Vec::new(), + active_s3: None, + } + } + pub fn get_active_s3(&self) -> Option { + if let Some(active_s3) = &self.active_s3 { + self.items + .iter() + .filter_map(|item| { + if item.name == *active_s3 { + match &item.config_option { + ConfigOption::S3(s3_config) => Some(s3_config), + _ => { + warn!("Name {} is not of type 'S3'", item.name); + None + } + } + } else { + None + } + }) + .next() + .cloned() + .map(|c| c.into()) + } else { + None + } + } +} + diff --git a/cli/src/config/configurable.rs b/cli/src/config/configurable.rs new file mode 100644 index 00000000..8a81aca2 --- /dev/null +++ b/cli/src/config/configurable.rs @@ -0,0 +1,7 @@ +use crate::config::config::Config; + +/// Trait which represents data which may be stored in `config_dir/downpour/config.json` +pub trait Configurable { + fn configure(&self, config: &mut Config); +} + diff --git a/cli/src/config/mod.rs b/cli/src/config/mod.rs new file mode 100644 index 00000000..8fd2538b --- /dev/null +++ b/cli/src/config/mod.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod configurable; +pub mod s3; +pub mod server; diff --git a/cli/src/config/s3.rs b/cli/src/config/s3.rs new file mode 100644 index 00000000..798f8eee --- /dev/null +++ b/cli/src/config/s3.rs @@ -0,0 +1,72 @@ +use std::str::FromStr; + +use clap::Args; +use dialoguer::{Input, theme::ColorfulTheme}; +use s3::{Bucket, Region, creds::Credentials}; +use serde::{Deserialize, Serialize}; + +use crate::{config::configurable::Configurable, interactive_optional_variable, interactive_variable}; + + +#[derive(Serialize, Deserialize, Args, Clone)] +pub struct S3ConfigCli { + secret_key: Option, + key_id: Option, + region: Option, + bucket_name: Option, + endpoint: Option, +} + +impl From for S3Config { + fn from(value: S3ConfigCli) -> Self { + interactive_variable!(value, secret_key, "S3 Secret Key"); + interactive_variable!(value, key_id, "S3 Key ID"); + interactive_variable!(value, region, "S3 Region"); + interactive_variable!(value, bucket_name, "S3 Bucket Name"); + interactive_optional_variable!(value, endpoint, "S3 Endpoint (leave blank for none"); + Self { + secret_key, + key_id, + region, + bucket_name, + endpoint, + } + } +} + + + +#[derive(Serialize, Deserialize, Debug)] +pub struct S3Config { + secret_key: String, + key_id: String, + region: String, + bucket_name: String, + endpoint: Option, +} + +impl S3Config { + pub fn generate_bucket(&self) -> anyhow::Result { + let credentials = + Credentials::new(Some(&self.key_id), Some(&self.secret_key), None, None, None)?; + + let region = if let Some(endpoint) = &self.endpoint { + Region::Custom { + region: self.region.clone(), + endpoint: endpoint.clone(), + } + } else { + Region::from_str(&self.region)? + }; + + let bucket = Bucket::new(&self.bucket_name, region, credentials)?; + + Ok(*bucket) + } +} + +impl Configurable for S3Config { + fn configure(&self, config: &mut super::config::Config) { + println!("Configuring S3Config with {:?}", self); + } +} diff --git a/cli/src/config/server.rs b/cli/src/config/server.rs new file mode 100644 index 00000000..a2939826 --- /dev/null +++ b/cli/src/config/server.rs @@ -0,0 +1,18 @@ +use clap::Args; +use serde::{Deserialize, Serialize}; + +use crate::config::configurable::Configurable; + +#[derive(Serialize, Deserialize, Args, Clone)] +pub struct ServerConfig { + /// Endpoint of the Drop server + url: String, + #[arg(short, long)] + token: String, +} + +impl Configurable for ServerConfig { + fn configure(&self, config: &mut super::config::Config) { + println!("Configured ServerConfig") + } +} \ No newline at end of file diff --git a/cli/src/interactive.rs b/cli/src/interactive.rs new file mode 100644 index 00000000..bc87ee4b --- /dev/null +++ b/cli/src/interactive.rs @@ -0,0 +1,47 @@ +use std::str::FromStr; + +use dialoguer::{Input, theme::ColorfulTheme}; + +#[macro_export] +macro_rules! interactive_variable { + ($value:ident, $var:ident, $prompt:expr) => { + let $var = if let Some($var) = $value.$var { + $var + } else { + crate::interactive::query_variable($prompt).unwrap() + }; + }; +} +#[macro_export] +macro_rules! interactive_optional_variable { + ($value:ident, $var:ident, $prompt:expr) => { + let $var = if let Some($var) = $value.$var { + Some($var) + } else { + crate::interactive::query_optional_variable($prompt).unwrap() + }; + }; +} +pub fn query_variable(prompt: impl ToString) -> dialoguer::Result +where + ::Err: ToString, +{ + Input::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt.to_string()) + .interact_text() +} +pub fn query_optional_variable( + prompt: impl ToString, +) -> dialoguer::Result> +where + ::Err: ToString, +{ + let input: T = Input::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt.to_string()) + .allow_empty(true) + .interact_text()?; + if input.to_string().len() == 0 { + return Ok(None); + } + Ok(Some(input)) +} \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs index 0d0fc98b..4782ad8b 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,6 +1,11 @@ use crate::{ cli::{Cli, Commands}, - commands::{configure::interactive_configure, upload}, config::Config, + commands::{configure::interactive_configure, upload}, + config::{ + config::{Config, ConfigOption}, + configurable::Configurable, + s3::S3Config, + }, }; use clap::Parser; use fern::colors::{Color, ColoredLevelConfig}; @@ -13,17 +18,18 @@ mod commands; mod config; mod manifest; +#[macro_use] +pub mod interactive; + + #[tokio::main] async fn main() -> anyhow::Result<()> { configure_logging()?; let cli = Cli::parse(); - let config = Config::new(); + let mut config = Config::read(); match &cli.command { - Commands::Configure { url, token } => { - if let Some(token) = token { - } else { - interactive_configure(url.to_string()).await?; - } + Commands::Configure(options) => { + configure_command(&mut config, options).await?; } Commands::Upload(info) => { upload::interface::upload(info, config).await?; @@ -33,6 +39,15 @@ async fn main() -> anyhow::Result<()> { Ok(()) } +async fn configure_command(config: &mut Config, options: &ConfigOption) -> anyhow::Result<()> { + let configuration: Box = match options { + ConfigOption::Server(options) => Box::new(options.clone()), + ConfigOption::S3(options) => Box::new(S3Config::from(options.clone())), + }; + configuration.configure(config); + Ok(()) +} + pub fn configure_logging() -> anyhow::Result<()> { let log_level = env::var("RUST_LOG") .unwrap_or_else(|_| "info".to_string())