perf: cache for players scraping

This commit is contained in:
mokurin000
2025-07-30 19:06:17 +08:00
parent 19a0d53624
commit 5ad0135deb
8 changed files with 136 additions and 16 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target /target
/*.txt /*.txt
/players.redb

47
Cargo.lock generated
View File

@@ -97,6 +97,26 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "bincode"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
dependencies = [
"bincode_derive",
"serde",
"unty",
]
[[package]]
name = "bincode_derive"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
dependencies = [
"virtue",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.1" version = "2.9.1"
@@ -553,7 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -1237,6 +1257,15 @@ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
] ]
[[package]]
name = "redb"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef838cd981b5c46e9e91e20e4623e43b29b5c251eb245b34da0cbd2da09ab27"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "ref-cast" name = "ref-cast"
version = "1.0.24" version = "1.0.24"
@@ -1302,7 +1331,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -1377,6 +1406,7 @@ name = "sdgb-api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aes", "aes",
"bincode",
"cbc", "cbc",
"chrono", "chrono",
"cipher", "cipher",
@@ -1401,6 +1431,7 @@ dependencies = [
"futures-util", "futures-util",
"nyquest-preset", "nyquest-preset",
"palc", "palc",
"redb",
"sdgb-api", "sdgb-api",
"serde_json", "serde_json",
"snafu", "snafu",
@@ -1753,6 +1784,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "unty"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -1765,6 +1802,12 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "virtue"
version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.1+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"

View File

@@ -6,9 +6,10 @@ edition = "2024"
license = "GPL-3.0" license = "GPL-3.0"
[features] [features]
default = ["compio"] default = ["compio", "bincode"]
compio = ["dep:compio"] compio = ["dep:compio"]
tokio = ["dep:tokio"] tokio = ["dep:tokio"]
bincode = ["dep:bincode"]
[dependencies] [dependencies]
snafu = { workspace = true } snafu = { workspace = true }
@@ -36,3 +37,4 @@ flate2 = "1.1.2"
cbc = "0.1.2" cbc = "0.1.2"
aes = "0.8.4" aes = "0.8.4"
cipher = { version = "0.4.4", features = ["block-padding"] } cipher = { version = "0.4.4", features = ["block-padding"] }
bincode = { version = "2.0.1", optional = true }

View File

@@ -4,3 +4,5 @@ pub mod title;
mod error; mod error;
pub use error::ApiError; pub use error::ApiError;
pub use bincode;

View File

@@ -1,5 +1,6 @@
use std::fmt::Display; use std::fmt::Display;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Serialize)] #[derive(Serialize)]
@@ -8,10 +9,10 @@ pub struct GetUserPreviewApi {
pub user_id: u32, pub user_id: u32,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GetUserPreviewApiResp { pub struct GetUserPreviewApiResp {
pub user_id: i64, pub user_id: u32,
pub user_name: String, pub user_name: String,
pub is_login: bool, pub is_login: bool,
pub last_rom_version: String, pub last_rom_version: String,

View File

@@ -6,12 +6,15 @@ authors = ["mokurin000"]
description = "CLI tool for SDGB protocol" description = "CLI tool for SDGB protocol"
[features] [features]
default = ["compio"] default = ["compio", "cache"]
compio = ["dep:compio", "sdgb-api/compio"] compio = ["dep:compio", "sdgb-api/compio"]
tokio = ["dep:tokio", "sdgb-api/tokio"] tokio = ["dep:tokio", "sdgb-api/tokio"]
cache = ["dep:redb"]
[dependencies] [dependencies]
sdgb-api = { workspace = true } sdgb-api = { workspace = true, features = ["bincode"] }
snafu = { workspace = true } snafu = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
@@ -27,3 +30,4 @@ spdlog-rs = { version = "0.4.3", default-features = false, features = [
"release-level-info", "release-level-info",
] } ] }
futures-util = "0.3.31" futures-util = "0.3.31"
redb = { version = "2.6.1", optional = true }

25
sdgb-cli/src/cache/mod.rs vendored Normal file
View File

@@ -0,0 +1,25 @@
use std::sync::LazyLock;
use redb::{Table, TableDefinition, WriteTransaction};
static DATABASE: LazyLock<redb::Database> = LazyLock::new(|| {
redb::Database::builder()
.create("players.redb")
.expect("failed to open database")
});
const DIFINITION: TableDefinition<'_, u32, Vec<u8>> = redb::TableDefinition::new("players");
pub fn write_txn() -> Result<WriteTransaction, redb::Error> {
Ok(DATABASE.begin_write()?)
}
pub fn open_table(write: &WriteTransaction) -> Result<Table<'_, u32, Vec<u8>>, redb::Error> {
Ok(write.open_table(DIFINITION)?)
}
pub fn init_db() -> Result<(), redb::Error> {
let write_txn = DATABASE.begin_write()?;
write_txn.open_table(DIFINITION)?;
write_txn.commit()?;
Ok(())
}

View File

@@ -6,6 +6,7 @@ use std::{
use futures_util::StreamExt; use futures_util::StreamExt;
use nyquest_preset::nyquest::ClientBuilder; use nyquest_preset::nyquest::ClientBuilder;
use palc::Parser; use palc::Parser;
use sdgb_api::{ use sdgb_api::{
ApiError, ApiError,
all_net::QRCode, all_net::QRCode,
@@ -23,6 +24,8 @@ use spdlog::{error, info, warn};
use crate::commands::Cli; use crate::commands::Cli;
#[cfg(feature = "cache")]
mod cache;
mod commands; mod commands;
#[cfg_attr(feature = "compio", compio::main)] #[cfg_attr(feature = "compio", compio::main)]
@@ -102,9 +105,33 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
user_ids.push(user_id); user_ids.push(user_id);
} }
let users = futures_util::stream::iter(user_ids) #[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)
.map(async |user_id| { .map(async |user_id| {
info!("preview: {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)?;
return Ok(decoded.0);
}
}
let resp = Sdgb1_50::request::<_, GetUserPreviewApiResp>( let resp = Sdgb1_50::request::<_, GetUserPreviewApiResp>(
&client, &client,
APIMethod::GetUserPreviewApi, APIMethod::GetUserPreviewApi,
@@ -113,15 +140,30 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
) )
.await; .await;
if let Err(e) = &resp { match &resp {
if matches!(e, ApiError::JSON { .. }) { Ok(resp) => {
info!("preview: {user_id} succeed");
#[cfg(feature = "cache")]
{
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);
}
}
}
Err(ApiError::JSON { .. }) => {
warn!("account unregistered: {user_id}"); warn!("account unregistered: {user_id}");
} else { }
Err(e) => {
error!("preview failed: {e}"); error!("preview failed: {e}");
} }
} }
resp Result::<_, Box<dyn snafu::Error>>::Ok(resp?)
}) })
.buffer_unordered(20) .buffer_unordered(20)
.filter_map(async |r| r.ok()) .filter_map(async |r| r.ok())
@@ -132,8 +174,8 @@ async fn main() -> Result<(), Box<dyn snafu::Error>> {
.write(true) .write(true)
.truncate(true) .truncate(true)
.create(true) .create(true)
.open("users.json")?; .open("players.json")?;
serde_json::to_writer_pretty(output, &users)?; serde_json::to_writer_pretty(output, &players)?;
} }
} }