diff --git a/Cargo.lock b/Cargo.lock index 4a987b86..c1762afe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -169,21 +169,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -549,12 +534,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64" version = "0.21.5" @@ -705,9 +684,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cairo-sys-rs" @@ -792,19 +771,6 @@ dependencies = [ "libc", ] -[[package]] -name = "chrono" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.48.5", -] - [[package]] name = "cipher" version = "0.4.4" @@ -1158,41 +1124,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "darling" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.48", -] - -[[package]] -name = "darling_macro" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.48", -] - [[package]] name = "dashmap" version = "5.5.3" @@ -1200,7 +1131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown", "lock_api", "once_cell", "parking_lot_core", @@ -1223,7 +1154,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" dependencies = [ "powerfmt", - "serde", ] [[package]] @@ -2151,19 +2081,13 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap 2.2.6", + "indexmap", "slab", "tokio", "tokio-util", "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.14.3" @@ -2324,29 +2248,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core 0.51.1", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "iced-x86" version = "1.20.0" @@ -2367,12 +2268,6 @@ dependencies = [ "objc2 0.4.1", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.5.0" @@ -2416,17 +2311,6 @@ dependencies = [ "quote", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.2.6" @@ -2434,7 +2318,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown", "serde", ] @@ -2759,6 +2643,7 @@ dependencies = [ "ansi_term", "anyhow", "async-trait", + "bytes", "clap", "dialoguer", "directories", @@ -2772,12 +2657,11 @@ dependencies = [ "hook", "image", "include_dir", - "indexmap 2.2.6", + "indexmap", "inventory", "itertools 0.12.0", "mint_lib", "mockall", - "modio", "obake", "opener", "path-slash", @@ -2802,6 +2686,7 @@ dependencies = [ "thiserror", "tokio", "tracing", + "tracing-subscriber", "typetag", "uasset_utils", "unreal_asset", @@ -2875,30 +2760,6 @@ dependencies = [ "syn 2.0.48", ] -[[package]] -name = "modio" -version = "0.7.1" -source = "git+https://github.com/trumank/modio-rs.git?branch=dev#d979c0a1bf0fd865bb30feb850079530ec6b84ba" -dependencies = [ - "bitflags 1.3.2", - "bytes", - "futures-core", - "futures-util", - "http", - "mime", - "pin-project-lite", - "reqwest", - "reqwest-middleware", - "serde", - "serde_json", - "serde_test", - "serde_with", - "tokio", - "tokio-util", - "tracing", - "url", -] - [[package]] name = "naive-cityhash" version = "0.2.0" @@ -3241,7 +3102,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4d6a8c22fc714f0c2373e6091bf6f5e9b37b1bc0b1184874b7e0a4e303d318f" dependencies = [ "dlv-list", - "hashbrown 0.14.3", + "hashbrown", ] [[package]] @@ -3831,7 +3692,7 @@ version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ - "base64 0.21.5", + "base64", "bytes", "encoding_rs", "futures-core", @@ -4011,7 +3872,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64", ] [[package]] @@ -4186,15 +4047,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_test" -version = "1.0.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" -dependencies = [ - "serde", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -4207,34 +4059,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" -dependencies = [ - "base64 0.13.1", - "chrono", - "hex", - "indexmap 1.9.3", - "serde", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "sha1" version = "0.10.6" @@ -4384,6 +4208,8 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d342c51730e54029130d7dc9fd735d28c4cd360f1368c01981d4f03ff207f096" dependencies = [ + "futures-core", + "pin-project", "snafu-derive", ] @@ -4832,7 +4658,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.6", + "indexmap", "toml_datetime", "winnow", ] @@ -4843,7 +4669,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.2.6", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -4995,7 +4821,7 @@ dependencies = [ "anyhow", "byteorder", "cityhasher", - "indexmap 2.2.6", + "indexmap", "typed-path", "unicase", "unreal_asset", @@ -5188,7 +5014,7 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" dependencies = [ - "base64 0.21.5", + "base64", "flate2", "log", "once_cell", @@ -5207,7 +5033,6 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] [[package]] @@ -5575,19 +5400,10 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core 0.52.0", + "windows-core", "windows-targets 0.52.0", ] -[[package]] -name = "windows-core" -version = "0.51.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 84781b0e..96d67741 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,13 +62,12 @@ image = { version = "0.24.7", default-features = false, features = ["png"] } indexmap = { version = "2.1.0", features = ["serde"] } inventory = "0.3.14" mint_lib = { path = "mint_lib" } -modio = { git = "https://github.com/trumank/modio-rs.git", branch = "dev", default-features = false, features = ["rustls-tls"] } obake = { version = "1.0.5", features = ["serde"] } opener = "0.6.1" path-slash = "0.2.1" rayon = "1.8.0" regex = "1.10.2" -reqwest.workspace = true +reqwest = { workspace = true, features = ["stream"] } reqwest-middleware = "0.2.4" rfd = "0.12.1" rust-ini = "0.20.0" @@ -92,10 +91,12 @@ repak.workspace = true include_dir = "0.7.3" postcard.workspace = true fs-err.workspace = true -snafu.workspace = true +snafu = { workspace = true, features = ["futures"] } strum = { version = "0.26", features = ["derive"] } itertools.workspace = true egui_dnd = "0.6.0" +tracing-subscriber = "0.3.18" +bytes = "1.6.0" [target.'cfg(target_env = "msvc")'.dependencies] hook = { path = "hook", artifact = "cdylib", optional = true, target = "x86_64-pc-windows-msvc"} diff --git a/src/providers/mod.rs b/src/providers/mod.rs index dd979fd0..c66b180f 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -81,7 +81,7 @@ pub enum ProviderError { #[snafu(transparent)] DrgModioError { source: DrgModioError }, #[snafu(display("mod.io-related error encountered while working on mod {mod_id}: {source}"))] - ModCtxtModioError { source: ::modio::Error, mod_id: u32 }, + ModCtxtModioError { source: DrgModioError, mod_id: u32 }, #[snafu(display("I/O error encountered while working on mod {mod_id}: {source}"))] ModCtxtIoError { source: std::io::Error, mod_id: u32 }, #[snafu(transparent)] diff --git a/src/providers/modio.rs b/src/providers/modio.rs index 3bea1cd6..4060aa82 100644 --- a/src/providers/modio.rs +++ b/src/providers/modio.rs @@ -2,13 +2,14 @@ use std::collections::{BTreeSet, HashSet}; use std::sync::OnceLock; use std::time::{SystemTime, UNIX_EPOCH}; +use futures::TryStreamExt; +use itertools::Itertools; #[cfg(test)] use mockall::{automock, predicate::*}; -use ::modio; - use reqwest::{Request, Response}; use reqwest_middleware::{Middleware, Next}; +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use task_local_extensions::Extensions; use tracing::*; @@ -26,7 +27,7 @@ const MODIO_PROVIDER_ID: &str = "modio"; inventory::submit! { super::ProviderFactory { id: MODIO_PROVIDER_ID, - new: ModioProvider::::new_provider, + new: ModioProvider::::new_provider, can_provide: |url| re_mod().is_match(url), parameters: &[ super::ProviderParameter { @@ -106,26 +107,43 @@ pub struct ModioMod { } impl ModioMod { - fn new(mod_: modio::mods::Mod, files: Vec) -> Self { + fn new(mod_: ModioModResponse, modfiles: Vec) -> Self { Self { name_id: mod_.name_id, name: mod_.name, latest_modfile: mod_.modfile.map(|f| f.id), - modfiles: files.into_iter().map(ModioFile::new).collect(), + modfiles, tags: mod_.tags.into_iter().map(|t| t.name).collect(), } } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ModioModResponse { id: u32, + name_id: String, + name: String, + profile_url: String, + modfile: Option, + tags: Vec, +} +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ModioModResponseTag { + name: String, } -impl From for ModioModResponse { - fn from(value: modio::mods::Mod) -> Self { - Self { id: value.id } - } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ModioFileResponse { + id: u32, + date_added: u64, + version: Option, + changelog: Option, + filesize: u64, + download: ModioFileResponseDownload, +} +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct ModioFileResponseDownload { + binary_url: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -135,13 +153,13 @@ pub struct ModioFile { version: Option, changelog: Option, } -impl ModioFile { - fn new(file: modio::files::File) -> Self { +impl From for ModioFile { + fn from(value: ModioFileResponse) -> Self { Self { - id: file.id, - date_added: file.date_added, - version: file.version, - changelog: file.changelog, + id: value.id, + date_added: value.date_added, + version: value.version, + changelog: value.changelog, } } } @@ -161,10 +179,10 @@ impl Middleware for LoggingMiddleware { ) -> reqwest_middleware::Result { loop { info!( - "request started {} {:?}", + "request started {} {}", self.requests .fetch_add(1, std::sync::atomic::Ordering::Relaxed), - req.url().path() + req.url() ); let res = next.clone().run(req.try_clone().unwrap(), extensions).await; if let Ok(res) = &res { @@ -186,13 +204,15 @@ impl Middleware for LoggingMiddleware { pub enum DrgModioError { #[snafu(display("missing OAuth token"))] MissingOauthToken, - #[snafu(display("mod.io error: {source}"))] - GenericModioError { source: modio::Error }, + #[snafu(display("reqwest middlware error: {source}"))] + GenericReqwestMiddlewareError { source: anyhow::Error }, + #[snafu(display("reqwest error: {source}"))] + GenericReqwestError { source: reqwest::Error }, #[snafu(display("failed to perform basic mod.io probe: {source}"))] - CheckFailed { source: modio::Error }, + CheckFailed { source: anyhow::Error }, #[snafu(display("failed to fetch mod files for <{url}> (mod_id = {mod_id}): {source}"))] FetchModFilesFailed { - source: modio::Error, + source: anyhow::Error, url: String, mod_id: u32, }, @@ -200,14 +220,14 @@ pub enum DrgModioError { "failed to fetch mod file {modfile_id} for <{url}> (mod_id = {mod_id}): {source}" ))] FetchModFileFailed { - source: modio::Error, + source: anyhow::Error, url: String, mod_id: u32, modfile_id: u32, }, #[snafu(display("failed to fetch mod <{url}> (mod_id = {mod_id}): {source}"))] FetchModFailed { - source: modio::Error, + source: anyhow::Error, url: String, mod_id: u32, }, @@ -215,7 +235,7 @@ pub enum DrgModioError { "failed to fetch dependencies for mod <{url}> (mod_id = {mod_id}): {source}" ))] FetchDependenciesFailed { - source: modio::Error, + source: anyhow::Error, url: String, mod_id: u32, }, @@ -243,13 +263,12 @@ pub trait DrgModio: Sync + Send { Self: Sized; async fn check(&self) -> Result<(), DrgModioError>; async fn fetch_mod(&self, url: String, id: u32) -> Result; - async fn fetch_files(&self, url: String, mod_id: u32) -> Result; async fn fetch_file( &self, url: String, mod_id: u32, modfile_id: u32, - ) -> Result; + ) -> Result; async fn fetch_dependencies(&self, url: String, mod_id: u32) -> Result, DrgModioError>; async fn fetch_mods_by_name( @@ -259,96 +278,120 @@ pub trait DrgModio: Sync + Send { async fn fetch_mods_by_ids( &self, filter_ids: Vec, - ) -> Result, DrgModioError>; + ) -> Result, DrgModioError>; async fn fetch_mod_updates_since( &self, mod_ids: Vec, last_update: u64, ) -> Result, DrgModioError>; - fn download(&self, action: A) -> modio::download::Downloader - where - modio::download::DownloadAction: From; + async fn download( + &self, + mod_id: u32, + file_id: u32, + ) -> futures::stream::BoxStream<'static, Result>; } -#[async_trait::async_trait] -impl DrgModio for modio::Modio { - fn with_parameters(parameters: &HashMap) -> Result { - let client = reqwest_middleware::ClientBuilder::new(reqwest::Client::new()) - .with::(Default::default()) - .build(); - let modio = modio::Modio::new( - modio::Credentials::with_token( - "".to_owned(), // TODO patch modio to not use API key at all - parameters.get("oauth").context(MissingOauthTokenSnafu)?, - ), - client, - ) - .context(GenericModioSnafu)?; - - Ok(modio) +struct ModioAlt { + client: reqwest_middleware::ClientWithMiddleware, + oauth: String, +} +impl ModioAlt { + async fn get>( + &self, + endpoint: U, + ) -> Result { + self.client + .get(&format!( + "https://api.mod.io/v1/games/{MODIO_DRG_ID}/{}", + endpoint.as_ref() + )) + .header("Authorization", format!("Bearer {}", self.oauth)) + .send() + .await + // TODO clean up this nonsense + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .error_for_status() + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .json::() + .await + .context(GenericReqwestSnafu) } + async fn fetch_list( + &self, + endpoint: &str, + query: &str, + ) -> Result, DrgModioError> { + #[derive(Deserialize)] + #[allow(unused)] + struct ResultPage { + data: Vec, + result_count: i64, + result_offset: i64, + result_limit: i64, + result_total: i64, + } - async fn check(&self) -> Result<(), DrgModioError> { - use modio::filter::Eq; - use modio::mods::filters::Id; + let mut offset = 0; + let mut results = vec![]; - self.game(MODIO_DRG_ID) - .mods() - .search(Id::eq(0)) - .collect() - .await - .context(CheckFailedSnafu)?; - Ok(()) + loop { + let url = format!("{endpoint}?_offset={offset}&{query}"); + let page: ResultPage = self.get(&url).await?; + results.extend(page.data); + if results.len() == page.result_total as usize { + break; + } + offset += page.result_count; + } + Ok(results) + } +} +#[async_trait::async_trait] +impl DrgModio for ModioAlt { + fn with_parameters(parameters: &HashMap) -> Result { + let client: reqwest_middleware::ClientWithMiddleware = + reqwest_middleware::ClientBuilder::new(reqwest::Client::new()) + .with::(Default::default()) + .build(); + let oauth = parameters + .get("oauth") + .context(MissingOauthTokenSnafu)? + .to_string(); + + Ok(Self { client, oauth }) } - async fn fetch_mod(&self, url: String, id: u32) -> Result { - use modio::filter::NotEq; - use modio::mods::filters::Id; - - let files = self - .game(MODIO_DRG_ID) - .mod_(id) - .files() - .search(Id::ne(0)) - .collect() - .await - .with_context(|_| FetchModFilesFailedSnafu { - mod_id: id, - url: url.clone(), - })?; - let r#mod = self - .game(MODIO_DRG_ID) - .mod_(id) - .get() + async fn check(&self) -> Result<(), DrgModioError> { + #[derive(Deserialize)] + struct Empty {} + self.get::("mods?id=0") .await - .context(FetchModFailedSnafu { mod_id: id, url })?; - - Ok(ModioMod::new(r#mod, files)) + .map(|_| ()) + .map_err(Into::::into) + .context(CheckFailedSnafu) } - async fn fetch_files(&self, url: String, mod_id: u32) -> Result { - use modio::filter::NotEq; - use modio::mods::filters::Id; - - let files = self - .game(MODIO_DRG_ID) - .mod_(mod_id) - .files() - .search(Id::ne(0)) - .collect() + async fn fetch_mod(&self, url: String, mod_id: u32) -> Result { + let res_files = self + .fetch_list::(&format!("mods/{mod_id}/files"), "id-not=0") .await + .map_err(Into::::into) .with_context(|_| FetchModFilesFailedSnafu { + url: url.to_string(), mod_id, - url: url.clone(), - })?; - let r#mod = self - .game(MODIO_DRG_ID) - .mod_(mod_id) - .get() - .await - .context(FetchModFailedSnafu { mod_id, url })?; + })? + .into_iter() + .map(Into::into) + .collect(); - Ok(ModioMod::new(r#mod, files)) + let res: ModioModResponse = self + .get(&format!("mods/{mod_id}")) + .await + .map_err(Into::::into) + .with_context(|_| FetchModFailedSnafu { url, mod_id })?; + Ok(ModioMod::new(res, res_files)) } async fn fetch_file( @@ -356,19 +399,17 @@ impl DrgModio for modio::Modio { url: String, mod_id: u32, modfile_id: u32, - ) -> Result { - let file = self - .game(MODIO_DRG_ID) - .mod_(mod_id) - .file(modfile_id) - .get() + ) -> Result { + let res: ModioFileResponse = self + .get(&format!("mods/{mod_id}/files/{modfile_id}")) .await + .map_err(Into::::into) .with_context(|_| FetchModFileFailedSnafu { url, mod_id, modfile_id, })?; - Ok(file) + Ok(res) } async fn fetch_dependencies( @@ -376,54 +417,39 @@ impl DrgModio for modio::Modio { url: String, mod_id: u32, ) -> Result, DrgModioError> { - Ok(self - .game(MODIO_DRG_ID) - .mod_(mod_id) - .dependencies() - .list() + #[derive(Deserialize)] + struct ModDependency { + mod_id: u32, + } + let res: Vec = self + .fetch_list(&format!("mods/{mod_id}/dependencies"), "") .await - .with_context(|_| FetchDependenciesFailedSnafu { url, mod_id })? - .into_iter() - .map(|d| d.mod_id) - .collect::>()) + .map_err(Into::::into) + .with_context(|_| FetchDependenciesFailedSnafu { url, mod_id })?; + Ok(res.into_iter().map(|d| d.mod_id).collect()) } async fn fetch_mods_by_name( &self, name_id: &str, ) -> Result, DrgModioError> { - use modio::filter::{Eq, In}; - use modio::mods::filters::{NameId, Visible}; - - let filter = NameId::eq(name_id).and(Visible::_in(vec![0, 1])); - Ok(self - .game(MODIO_DRG_ID) - .mods() - .search(filter) - .collect() - .await - .context(GenericModioSnafu)? - .into_iter() - .map(|m| m.into()) - .collect()) + let params = vec![format!("name_id={name_id}"), "visible-in=0,1".to_string()]; + let res: Vec = self + .fetch_list("mods", ¶ms.into_iter().join("&")) + .await?; + Ok(res) } async fn fetch_mods_by_ids( &self, filter_ids: Vec, - ) -> Result, DrgModioError> { - use modio::filter::In; - use modio::mods::filters::Id; - - let filter = Id::_in(filter_ids); - - Ok(self - .game(MODIO_DRG_ID) - .mods() - .search(filter) - .collect() - .await - .context(GenericModioSnafu)?) + ) -> Result, DrgModioError> { + let mod_list_str = filter_ids.into_iter().map(|id| id.to_string()).join(","); + let params = vec![format!("id-in={mod_list_str}")]; + let res: Vec = self + .fetch_list("mods", ¶ms.into_iter().join("&")) + .await?; + Ok(res) } async fn fetch_mod_updates_since( @@ -431,37 +457,73 @@ impl DrgModio for modio::Modio { mod_ids: Vec, last_update: u64, ) -> Result, DrgModioError> { - use modio::filter::Cmp; - use modio::filter::In; - use modio::filter::NotIn; - - use modio::mods::filters::events::EventType; - use modio::mods::filters::events::ModId; - use modio::mods::filters::DateAdded; - use modio::mods::EventType as EventTypes; - - let events = self - .game(MODIO_DRG_ID) - .mods() - .events( - EventType::not_in(vec![ - EventTypes::ModCommentAdded, - EventTypes::ModCommentDeleted, - ]) - .and(ModId::_in(mod_ids)) - .and(DateAdded::gt(last_update)), - ) - .collect() - .await - .context(GenericModioSnafu)?; - Ok(events.iter().map(|e| e.mod_id).collect::>()) + #[derive(Deserialize)] + struct ResultEvent { + mod_id: u32, + } + + let mod_list_str = mod_ids.into_iter().map(|id| id.to_string()).join(","); + let params = vec![ + format!("date_added-gt={last_update}"), + format!("event_type-not-in=MOD_COMMENT_ADDED%2CMOD_COMMENT_DELETED"), + format!("mod_id-in={mod_list_str}"), + ]; + let res: Vec = self + .fetch_list("mods/events", ¶ms.into_iter().join("&")) + .await?; + Ok(res.iter().map(|e| e.mod_id).collect::>()) } - fn download(&self, action: A) -> modio::download::Downloader - where - modio::download::DownloadAction: From, - { - self.download(action) + async fn download( + &self, + mod_id: u32, + file_id: u32, + ) -> futures::stream::BoxStream<'static, Result> { + use futures::StreamExt; + use futures::TryFutureExt; + + let client = self.client.clone(); + let oauth = self.oauth.clone(); + + ({ + let oauth = oauth.clone(); + let client = client.clone(); + || async move { + client + .get(&format!( + "https://api.mod.io/v1/games/{MODIO_DRG_ID}/mods/{mod_id}/files/{file_id}", + )) + .header("Authorization", format!("Bearer {}", oauth)) + .send() + .await + // TODO clean up this nonsense + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .error_for_status() + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .json::() + .await + .context(GenericReqwestSnafu) + } + })() + .and_then(|res| async move { + Ok(client + .get(res.download.binary_url) + .header("Authorization", format!("Bearer {}", oauth)) + .send() + .await + // TODO clean up this nonsense + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .error_for_status() + .map_err(Into::::into) + .context(GenericReqwestMiddlewareSnafu)? + .bytes_stream() + .context(GenericReqwestSnafu)) + }) + .try_flatten_stream() + .boxed() } } @@ -569,10 +631,7 @@ impl ModProvider for ModioProvider { for m in mods { let id = m.id; // TODO avoid fetching mod a second time - let m = self - .modio - .fetch_files(m.profile_url.to_string(), id) - .await?; + let m = self.modio.fetch_mod(m.profile_url.to_string(), id).await?; write_cache(&cache, |c| { c.mod_id_map.insert(m.name_id.to_owned(), id); c.mods.insert(id, m); @@ -747,15 +806,13 @@ impl ModProvider for ModioProvider { .await?; let size = file.filesize; - let download: modio::download::DownloadAction = file.into(); info!("downloading mod {url:?}..."); - use futures::stream::TryStreamExt; use tokio::io::AsyncWriteExt; let mut cursor = std::io::Cursor::new(vec![]); - let mut stream = Box::pin(self.modio.download(download).stream()); + let mut stream = self.modio.download(mod_id, file.id).await; while let Some(bytes) = stream .try_next() .await @@ -1106,7 +1163,12 @@ mod test { .returning(move |name| { mod_names .get(name) - .map(|id| vec![ModioModResponse { id: **id }]) + .map(|id| { + vec![ModioModResponse { + id: **id, + ..Default::default() + }] + }) .ok_or(DrgModioError::GenericError { msg: "not found" }) }); mock.expect_fetch_mod().times(1).returning(move |_, id| {