diff --git a/Cargo.toml b/Cargo.toml index e15f1df..72344e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = ["sdgb-api", "sdgb-cli"] resolver = "3" +default-members = ["sdgb-cli"] [workspace.dependencies] sdgb-api = { path = "./sdgb-api", default-features = false } @@ -15,6 +16,7 @@ serde_json = "1.0.141" strum = { version = "0.27.2", features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread"] } compio = { version = "0.15.0", features = ["runtime"] } +redb = "2.6.1" [profile.release] lto = true diff --git a/sdgb-api/src/lib.rs b/sdgb-api/src/lib.rs index 8855e18..d674f57 100644 --- a/sdgb-api/src/lib.rs +++ b/sdgb-api/src/lib.rs @@ -6,3 +6,6 @@ mod error; pub use error::ApiError; pub use bincode; + +#[cfg(all(feature = "compio", feature = "tokio"))] +compile_error!("you must not enable both `compio` and `tokio`"); diff --git a/sdgb-api/src/title/mod.rs b/sdgb-api/src/title/mod.rs index 6dc6de8..eb5a66f 100644 --- a/sdgb-api/src/title/mod.rs +++ b/sdgb-api/src/title/mod.rs @@ -74,6 +74,9 @@ pub trait MaiVersionExt: MaiVersion { #[cfg(feature = "tokio")] use tokio::task::spawn_blocking; + #[cfg(all(not(feature = "compio"), not(feature = "tokio")))] + compile_error!("you must enable one of `compio` or `tokio`"); + async { let req = spawn_blocking(move || Self::api_call(api, agent_extra, data)) .await diff --git a/sdgb-cli/Cargo.toml b/sdgb-cli/Cargo.toml index 79eac49..18c8917 100644 --- a/sdgb-cli/Cargo.toml +++ b/sdgb-cli/Cargo.toml @@ -6,26 +6,26 @@ authors = ["mokurin000"] description = "CLI tool for SDGB protocol" [features] -default = ["compio", "cache"] +default = ["compio", "fetchall"] compio = ["dep:compio", "sdgb-api/compio"] tokio = ["dep:tokio", "sdgb-api/tokio"] -cache = ["dep:redb"] +fetchall = ["dep:redb", "dep:futures-util"] [dependencies] sdgb-api = { workspace = true, features = ["bincode"] } -spdlog-rs = { workspace = true } +spdlog-rs = { workspace = true } snafu = { workspace = true } serde_json = { workspace = true } strum = { workspace = true } +redb = { workspace = true, optional = true } tokio = { workspace = true, features = ["macros"], optional = true } compio = { workspace = true, features = ["macros"], optional = true } nyquest-preset = { version = "0.2.0", features = ["async"] } palc = { version = "0.0.1", features = ["derive"] } -futures-util = "0.3.31" -redb = { version = "2.6.1", optional = true } +futures-util = { version = "0.3.31", optional = true } ctrlc = { version = "3.4.7", features = ["termination"] } diff --git a/sdgb-cli/src/cache/mod.rs b/sdgb-cli/src/cache/mod.rs index 55a6343..b5bdfaa 100644 --- a/sdgb-cli/src/cache/mod.rs +++ b/sdgb-cli/src/cache/mod.rs @@ -1,6 +1,6 @@ use std::sync::LazyLock; -use redb::{Table, TableDefinition, WriteTransaction}; +use redb::{ReadTransaction, Table, TableDefinition, WriteTransaction}; static DATABASE: LazyLock = LazyLock::new(|| { redb::Database::builder() @@ -17,6 +17,16 @@ pub fn open_table(write: &WriteTransaction) -> Result>, r Ok(write.open_table(DIFINITION)?) } +pub fn read_txn() -> Result { + Ok(DATABASE.begin_read()?) +} + +pub fn open_table_read( + read: &ReadTransaction, +) -> Result>, redb::Error> { + Ok(read.open_table(DIFINITION)?) +} + pub fn init_db() -> Result<(), redb::Error> { let write_txn = DATABASE.begin_write()?; write_txn.open_table(DIFINITION)?; diff --git a/sdgb-cli/src/commands.rs b/sdgb-cli/src/commands.rs index 9c8a40f..68aefca 100644 --- a/sdgb-cli/src/commands.rs +++ b/sdgb-cli/src/commands.rs @@ -41,10 +41,13 @@ pub enum Commands { user_id: u32, }, + #[cfg(feature = "fetchall")] ListAllUser { #[arg(short, long, default_value_t = 5)] concurrency: usize, }, + #[cfg(feature = "fetchall")] + ListAllUserDump {}, Logout { #[arg(short, long)] diff --git a/sdgb-cli/src/main.rs b/sdgb-cli/src/main.rs index 1727ad6..f2594f5 100644 --- a/sdgb-cli/src/main.rs +++ b/sdgb-cli/src/main.rs @@ -1,16 +1,10 @@ -use std::{ - fs::OpenOptions, - io::{self, BufRead}, - sync::atomic::{AtomicBool, Ordering}, -}; +use std::sync::atomic::{AtomicBool, Ordering}; -use futures_util::StreamExt; use nyquest_preset::nyquest::ClientBuilder; use palc::Parser; use spdlog::{Level, LevelFilter::MoreSevereEqual}; use sdgb_api::{ - ApiError, all_net::QRCode, auth_lite::{SDGB, SDHJ, delivery_raw}, title::{ @@ -26,7 +20,7 @@ use spdlog::{error, info, warn}; use crate::{commands::Cli, utils::login_action}; -#[cfg(feature = "cache")] +#[cfg(feature = "fetchall")] mod cache; mod commands; mod utils; @@ -49,7 +43,7 @@ async fn main() -> Result<(), Box> { EARLY_QUIT.store(true, Ordering::Relaxed); })?; - let Cli { command } = ::parse(); + let Cli { command } = ::parse(); let client = ClientBuilder::default().build_async().await?; @@ -108,7 +102,13 @@ async fn main() -> Result<(), Box> { println!("{}", String::from_utf8_lossy(&resp)); } + #[cfg(feature = "fetchall")] commands::Commands::ListAllUser { concurrency } => { + use futures_util::StreamExt; + use redb::ReadableTable; + use sdgb_api::bincode::borrow_decode_from_slice; + use std::io::{self, BufRead}; + let mut stdin = io::stdin().lock(); let mut buf = String::new(); let mut user_ids = Vec::new(); @@ -123,31 +123,22 @@ async fn main() -> Result<(), Box> { user_ids.push(user_id); } - #[cfg(feature = "cache")] let _ = cache::init_db(); - #[cfg(feature = "cache")] let write = cache::write_txn()?; - #[cfg(feature = "cache")] let config = sdgb_api::bincode::config::Configuration::< sdgb_api::bincode::config::LittleEndian, >::default() .with_no_limit(); - let players = futures_util::stream::iter(user_ids) + let _ = futures_util::stream::iter(user_ids) .map(async |user_id| { - #[cfg(feature = "cache")] - { - use redb::ReadableTable; - use sdgb_api::bincode::borrow_decode_from_slice; + let cache_table = cache::open_table(&write)?; + let data = cache_table.get(user_id)?; + if let Some(data) = data { + let decoded: (GetUserPreviewApiResp, _) = + borrow_decode_from_slice(&data.value(), config)?; - let cache_table = cache::open_table(&write)?; - let data = cache_table.get(user_id)?; - if let Some(data) = data { - let decoded: (GetUserPreviewApiResp, _) = - borrow_decode_from_slice(&data.value(), config)?; - - return Ok(decoded.0); - } + return Ok(decoded.0); } if EARLY_QUIT.load(Ordering::Relaxed) { @@ -166,18 +157,15 @@ async fn main() -> Result<(), Box> { Ok(resp) => { info!("preview: {user_id} succeed"); - #[cfg(feature = "cache")] - { - use sdgb_api::bincode::encode_to_vec; + use sdgb_api::bincode::encode_to_vec; - if let Ok(mut table) = cache::open_table(&write) - && let Ok(encoded) = encode_to_vec(resp, config) - { - _ = table.insert(resp.user_id, encoded); - } + if let Ok(mut table) = cache::open_table(&write) + && let Ok(encoded) = encode_to_vec(resp, config) + { + _ = table.insert(resp.user_id, encoded); } } - Err(ApiError::JSON { .. }) => { + Err(sdgb_api::ApiError::JSON { .. }) => { warn!("account unregistered: {user_id}"); } Err(e) => { @@ -192,20 +180,40 @@ async fn main() -> Result<(), Box> { .collect::>() .await; - #[cfg(feature = "cache")] let _ = write.commit(); - - if !EARLY_QUIT.load(Ordering::Relaxed) { - let output = OpenOptions::new() - .write(true) - .truncate(true) - .create(true) - .open("players.json")?; - serde_json::to_writer_pretty(output, &players)?; - } else { - info!("current progress: {}", players.len()); - } } + #[cfg(feature = "fetchall")] + commands::Commands::ListAllUserDump { .. } => { + use std::fs::OpenOptions; + + use redb::ReadableTable; + use sdgb_api::bincode::{self, borrow_decode_from_slice}; + + use crate::cache::{open_table_read, read_txn}; + + let txn = read_txn()?; + let table = open_table_read(&txn)?; + + let config = bincode::config::Configuration::::default() + .with_no_limit(); + + let user_ids = table + .iter()? + .flatten() + .map(|d| borrow_decode_from_slice(&d.1.value(), config)) + .flatten() + .map(|(value, _)| value) + .collect::>(); + + let file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open("players.json")?; + file.lock()?; + serde_json::to_writer(file, &user_ids)?; + } + commands::Commands::Userdata { user_id } => { let action = async |_| match Sdgb1_50::request::<_, GetUserDataApiResp>( &client, @@ -228,3 +236,6 @@ async fn main() -> Result<(), Box> { Ok(()) } + +#[cfg(all(feature = "compio", feature = "tokio"))] +compile_error!("you must not enable both `compio` and `tokio`");