Have app send download analytics meta (#5954)

* wip: add download reasons to app

* update how download meta is gathered

* cargo fmt

* prepr frontend
This commit is contained in:
aecsocket
2026-04-30 20:55:47 +01:00
committed by GitHub
parent 38a39feef1
commit 1875b89556
20 changed files with 195 additions and 32 deletions

View File

@@ -107,7 +107,7 @@ defineExpose({
const install = async () => { const install = async () => {
installing.value = true installing.value = true
await installMod(instance.value.path, selectedVersion.value.id).catch(handleError) await installMod(instance.value.path, selectedVersion.value.id, 'standalone').catch(handleError)
installing.value = false installing.value = false
onInstall.value(selectedVersion.value.id) onInstall.value(selectedVersion.value.id)
incompatibleModal.value.hide() incompatibleModal.value.hide()

View File

@@ -116,7 +116,7 @@ async function install(instance) {
return return
} }
await installMod(instance.path, version.id).catch(handleError) await installMod(instance.path, version.id, 'standalone').catch(handleError)
await installVersionDependencies(instance, version).catch(handleError) await installVersionDependencies(instance, version).catch(handleError)
instance.installedMod = true instance.installedMod = true
@@ -188,7 +188,7 @@ const createInstance = async () => {
const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError) const id = await create(name.value, gameVersion, loader, 'latest', icon.value).catch(handleError)
await installMod(id, versions.value[0].id).catch(handleError) await installMod(id, versions.value[0].id, 'standalone').catch(handleError)
await router.push(`/instance/${encodeURIComponent(id)}/`) await router.push(`/instance/${encodeURIComponent(id)}/`)

View File

@@ -184,8 +184,18 @@ export async function update_project(path: string, projectPath: string): Promise
// Add a project to a profile from a version // Add a project to a profile from a version
// Returns a path to the new project file // Returns a path to the new project file
export async function add_project_from_version(path: string, versionId: string): Promise<string> { export type DownloadReason = 'standalone' | 'dependency' | 'modpack'
return await invoke('plugin:profile|profile_add_project_from_version', { path, versionId })
export async function add_project_from_version(
path: string,
versionId: string,
reason: DownloadReason,
): Promise<string> {
return await invoke('plugin:profile|profile_add_project_from_version', {
path,
versionId,
reason,
})
} }
// Add a project to a profile from a path + project_type // Add a project to a profile from a path + project_type

View File

@@ -343,7 +343,7 @@ async function switchProjectVersion(mod: ContentItem, version: Labrinth.Versions
} }
try { try {
await remove_project(props.instance.path, mod.file_path!) await remove_project(props.instance.path, mod.file_path!)
const newPath = await add_project_from_version(props.instance.path, version.id) const newPath = await add_project_from_version(props.instance.path, version.id, 'standalone')
const profile = await get(props.instance.path).catch(handleError) const profile = await get(props.instance.path).catch(handleError)
if (profile) { if (profile) {

View File

@@ -426,7 +426,7 @@ export function createContentInstall(opts: {
} }
try { try {
await add_project_from_version(instance.id, version.id) await add_project_from_version(instance.id, version.id, 'standalone')
await installVersionDependencies( await installVersionDependencies(
profile, profile,
version, version,
@@ -484,7 +484,7 @@ export function createContentInstall(opts: {
) )
if (!id) return if (!id) return
await add_project_from_version(id, version.id) await add_project_from_version(id, version.id, 'standalone')
await opts.router.push(`/instance/${encodeURIComponent(id)}/`) await opts.router.push(`/instance/${encodeURIComponent(id)}/`)
const instance = await get(id) const instance = await get(id)
@@ -585,7 +585,7 @@ export function createContentInstall(opts: {
const installedProjectIds: string[] = [project.id] const installedProjectIds: string[] = [project.id]
addInstallingItem(instancePath, project, version) addInstallingItem(instancePath, project, version)
try { try {
await add_project_from_version(instance.path, version.id) await add_project_from_version(instance.path, version.id, 'standalone')
await installVersionDependencies( await installVersionDependencies(
instance, instance,
version, version,

View File

@@ -177,7 +177,7 @@ export const installVersionDependencies = async (profile, version, onDepInstalli
const batch = queuedInstalls.slice(i, i + batchSize) const batch = queuedInstalls.slice(i, i + batchSize)
await Promise.all( await Promise.all(
batch.map(async ({ versionId }) => { batch.map(async ({ versionId }) => {
await add_project_from_version(profile.path, versionId) await add_project_from_version(profile.path, versionId, 'dependency')
}), }),
) )
} }

View File

@@ -4,6 +4,7 @@ use path_util::SafeRelativeUtf8UnixPathBuf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use theseus::DownloadReason;
use theseus::data::{ContentItem, Dependency, LinkedModpackInfo}; use theseus::data::{ContentItem, Dependency, LinkedModpackInfo};
use theseus::prelude::*; use theseus::prelude::*;
use theseus::profile::QuickPlayType; use theseus::profile::QuickPlayType;
@@ -249,8 +250,9 @@ pub async fn profile_update_project(
pub async fn profile_add_project_from_version( pub async fn profile_add_project_from_version(
path: &str, path: &str,
version_id: &str, version_id: &str,
reason: DownloadReason,
) -> Result<String> { ) -> Result<String> {
Ok(profile::add_project_from_version(path, version_id).await?) Ok(profile::add_project_from_version(path, version_id, reason).await?)
} }
// Adds a project to a profile from a path // Adds a project to a profile from a path

View File

@@ -90,6 +90,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
None, None,
None, None,
None, None,
None,
Some((&loading_bar, 80.0)), Some((&loading_bar, 80.0)),
&state.fetch_semaphore, &state.fetch_semaphore,
&state.pool, &state.pool,

View File

@@ -78,9 +78,14 @@ pub async fn import_curseforge(
thumbnail_url: Some(thumbnail_url), thumbnail_url: Some(thumbnail_url),
}) = minecraft_instance.installed_modpack.clone() }) = minecraft_instance.installed_modpack.clone()
{ {
let icon_bytes = let icon_bytes = fetch(
fetch(&thumbnail_url, None, &state.fetch_semaphore, &state.pool) &thumbnail_url,
.await?; None,
None,
&state.fetch_semaphore,
&state.pool,
)
.await?;
let filename = thumbnail_url.rsplit('/').next_back(); let filename = thumbnail_url.rsplit('/').next_back();
if let Some(filename) = filename { if let Some(filename) = filename {
icon = Some( icon = Some(

View File

@@ -4,9 +4,12 @@ use crate::data::ModLoader;
use crate::event::emit::{emit_loading, init_loading}; use crate::event::emit::{emit_loading, init_loading};
use crate::event::{LoadingBarId, LoadingBarType}; use crate::event::{LoadingBarId, LoadingBarType};
use crate::state::{ use crate::state::{
CacheBehaviour, CachedEntry, LinkedData, ProfileInstallStage, SideType, CacheBehaviour, CachedEntry, LinkedData, Profile, ProfileInstallStage,
SideType,
};
use crate::util::fetch::{
DownloadMeta, DownloadReason, fetch, fetch_advanced, write_cached_icon,
}; };
use crate::util::fetch::{fetch, fetch_advanced, write_cached_icon};
use crate::util::io; use crate::util::io;
use path_util::SafeRelativeUtf8UnixPathBuf; use path_util::SafeRelativeUtf8UnixPathBuf;
@@ -287,12 +290,29 @@ pub async fn generate_pack_from_version_id(
) )
})?; })?;
let profile =
Profile::get(&profile_path, &state.pool)
.await?
.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(
profile_path.to_string(),
)
.as_error()
})?;
let download_meta = DownloadMeta {
reason: DownloadReason::Modpack,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};
let file = fetch_advanced( let file = fetch_advanced(
Method::GET, Method::GET,
&url, &url,
hash.map(|x| &**x), hash.map(|x| &**x),
None, None,
None, None,
Some(&download_meta),
Some((&loading_bar, 70.0)), Some((&loading_bar, 70.0)),
&state.fetch_semaphore, &state.fetch_semaphore,
&state.pool, &state.pool,
@@ -320,9 +340,14 @@ pub async fn generate_pack_from_version_id(
emit_loading(&loading_bar, 10.0, Some("Retrieving icon"))?; emit_loading(&loading_bar, 10.0, Some("Retrieving icon"))?;
let fetched = if let Some(icon_url) = project.icon_url { let fetched = if let Some(icon_url) = project.icon_url {
let state = State::get().await?; let state = State::get().await?;
let icon_bytes = let icon_bytes = fetch(
fetch(&icon_url, None, &state.fetch_semaphore, &state.pool) &icon_url,
.await?; None,
None,
&state.fetch_semaphore,
&state.pool,
)
.await?;
let filename = icon_url.rsplit('/').next(); let filename = icon_url.rsplit('/').next();

View File

@@ -6,9 +6,12 @@ use crate::pack::install_from::{
EnvType, PackFile, PackFileHash, set_profile_information, EnvType, PackFile, PackFileHash, set_profile_information,
}; };
use crate::state::{ use crate::state::{
CacheBehaviour, CachedEntry, ProfileInstallStage, SideType, cache_file_hash, CacheBehaviour, CachedEntry, Profile, ProfileInstallStage, SideType,
cache_file_hash,
};
use crate::util::fetch::{
DownloadMeta, DownloadReason, fetch_mirrors, sha1_async, write,
}; };
use crate::util::fetch::{fetch_mirrors, sha1_async, write};
use crate::util::io; use crate::util::io;
use crate::{State, profile}; use crate::{State, profile};
use async_zip::base::read::seek::ZipFileReader; use async_zip::base::read::seek::ZipFileReader;
@@ -207,6 +210,22 @@ pub async fn install_zipped_mrpack_files(
) )
.await?; .await?;
let profile =
Profile::get(&profile_path, &state.pool)
.await?
.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(
profile_path.to_string(),
)
.as_error()
})?;
let download_meta = DownloadMeta {
reason: DownloadReason::Modpack,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};
let num_files = pack.files.len(); let num_files = pack.files.len();
loading_try_for_each_concurrent( loading_try_for_each_concurrent(
futures::stream::iter(pack.files.into_iter()) futures::stream::iter(pack.files.into_iter())
@@ -218,6 +237,7 @@ pub async fn install_zipped_mrpack_files(
None, None,
|project| { |project| {
let profile_path = profile_path.clone(); let profile_path = profile_path.clone();
let download_meta = download_meta.clone();
async move { async move {
//TODO: Future update: prompt user for optional files in a modpack //TODO: Future update: prompt user for optional files in a modpack
if let Some(env) = project.env if let Some(env) = project.env
@@ -235,6 +255,7 @@ pub async fn install_zipped_mrpack_files(
.map(|x| &**x) .map(|x| &**x)
.collect::<Vec<&str>>(), .collect::<Vec<&str>>(),
project.hashes.get(&PackFileHash::Sha1).map(|x| &**x), project.hashes.get(&PackFileHash::Sha1).map(|x| &**x),
Some(&download_meta),
&state.fetch_semaphore, &state.fetch_semaphore,
&state.pool, &state.pool,
) )

View File

@@ -109,6 +109,7 @@ pub async fn profile_create(
let fetched = crate::util::fetch::fetch( let fetched = crate::util::fetch::fetch(
icon, icon,
None, None,
None,
&state.fetch_semaphore, &state.fetch_semaphore,
&state.pool, &state.pool,
) )

View File

@@ -461,6 +461,7 @@ pub async fn update_project(
let mut path = Profile::add_project_version( let mut path = Profile::add_project_version(
profile_path, profile_path,
update_version, update_version,
fetch::DownloadReason::Standalone,
&state.pool, &state.pool,
&state.fetch_semaphore, &state.fetch_semaphore,
&state.io_semaphore, &state.io_semaphore,
@@ -501,11 +502,14 @@ pub async fn update_project(
pub async fn add_project_from_version( pub async fn add_project_from_version(
profile_path: &str, profile_path: &str,
version_id: &str, version_id: &str,
reason: fetch::DownloadReason,
) -> crate::Result<String> { ) -> crate::Result<String> {
let state = State::get().await?; let state = State::get().await?;
let project_path = Profile::add_project_version( let project_path = Profile::add_project_version(
profile_path, profile_path,
version_id, version_id,
reason,
&state.pool, &state.pool,
&state.fetch_semaphore, &state.fetch_semaphore,
&state.io_semaphore, &state.io_semaphore,

View File

@@ -148,6 +148,7 @@ pub async fn download_client(
let bytes = fetch( let bytes = fetch(
&client_download.url, &client_download.url,
Some(&client_download.sha1), Some(&client_download.sha1),
None,
&st.fetch_semaphore, &st.fetch_semaphore,
&st.pool, &st.pool,
) )
@@ -238,7 +239,7 @@ pub async fn download_assets(
async { async {
if !resource_path.exists() || force { if !resource_path.exists() || force {
let resource = fetch_cell let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore, &st.pool)) .get_or_try_init(|| fetch(&url, Some(hash), None, &st.fetch_semaphore, &st.pool))
.await?; .await?;
write(&resource_path, resource, &st.io_semaphore).await?; write(&resource_path, resource, &st.io_semaphore).await?;
tracing::trace!("Fetched asset with hash {hash}"); tracing::trace!("Fetched asset with hash {hash}");
@@ -252,7 +253,7 @@ pub async fn download_assets(
if with_legacy && !resource_path.exists() || force { if with_legacy && !resource_path.exists() || force {
let resource = fetch_cell let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore, &st.pool)) .get_or_try_init(|| fetch(&url, Some(hash), None, &st.fetch_semaphore, &st.pool))
.await?; .await?;
write(&resource_path, resource, &st.io_semaphore).await?; write(&resource_path, resource, &st.io_semaphore).await?;
tracing::trace!("Fetched legacy asset with hash {hash}"); tracing::trace!("Fetched legacy asset with hash {hash}");
@@ -326,6 +327,7 @@ pub async fn download_libraries(
let data = fetch( let data = fetch(
&native.url, &native.url,
Some(&native.sha1), Some(&native.sha1),
None,
&st.fetch_semaphore, &st.fetch_semaphore,
&st.pool, &st.pool,
) )
@@ -370,6 +372,7 @@ pub async fn download_libraries(
let bytes = fetch( let bytes = fetch(
&artifact.url, &artifact.url,
Some(&artifact.sha1), Some(&artifact.sha1),
None,
&st.fetch_semaphore, &st.fetch_semaphore,
&st.pool, &st.pool,
) )
@@ -406,7 +409,8 @@ pub async fn download_libraries(
// failed download here is not a fatal condition. // failed download here is not a fatal condition.
// //
// See DEV-479. // See DEV-479.
match fetch(&url, None, &st.fetch_semaphore, &st.pool).await match fetch(&url, None, None, &st.fetch_semaphore, &st.pool)
.await
{ {
Ok(bytes) => { Ok(bytes) => {
write(&path, &bytes, &st.io_semaphore).await?; write(&path, &bytes, &st.io_semaphore).await?;
@@ -465,6 +469,7 @@ pub async fn download_log_config(
let bytes = fetch( let bytes = fetch(
&log_download.url, &log_download.url,
Some(&log_download.sha1), Some(&log_download.sha1),
None,
&st.fetch_semaphore, &st.fetch_semaphore,
&st.pool, &st.pool,
) )

View File

@@ -25,6 +25,7 @@ pub use event::{
}; };
pub use logger::start_logger; pub use logger::start_logger;
pub use state::State; pub use state::State;
pub use util::fetch::DownloadReason;
pub fn launcher_user_agent() -> String { pub fn launcher_user_agent() -> String {
const LAUNCHER_BASE_USER_AGENT: &str = const LAUNCHER_BASE_USER_AGENT: &str =

View File

@@ -326,6 +326,7 @@ impl FriendsSocket {
None, None,
None, None,
None, None,
None,
semaphore, semaphore,
exec, exec,
) )
@@ -358,6 +359,7 @@ impl FriendsSocket {
None, None,
None, None,
None, None,
None,
semaphore, semaphore,
exec, exec,
) )

View File

@@ -21,7 +21,9 @@
use crate::pack::install_from::{PackFileHash, PackFormat}; use crate::pack::install_from::{PackFileHash, PackFormat};
use crate::state::profiles::{Profile, ProfileFile, ProjectType}; use crate::state::profiles::{Profile, ProfileFile, ProjectType};
use crate::state::{CacheBehaviour, CachedEntry}; use crate::state::{CacheBehaviour, CachedEntry};
use crate::util::fetch::{FetchSemaphore, fetch_mirrors, sha1_async}; use crate::util::fetch::{
DownloadMeta, DownloadReason, FetchSemaphore, fetch_mirrors, sha1_async,
};
use async_zip::base::read::seek::ZipFileReader; use async_zip::base::read::seek::ZipFileReader;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
@@ -319,6 +321,7 @@ pub async fn get_content_items(
); );
match get_modpack_identifiers( match get_modpack_identifiers(
&linked_data.version_id, &linked_data.version_id,
profile,
pool, pool,
fetch_semaphore, fetch_semaphore,
) )
@@ -638,6 +641,7 @@ pub async fn get_linked_modpack_content(
let modpack_ids = match get_modpack_identifiers( let modpack_ids = match get_modpack_identifiers(
&linked_data.version_id, &linked_data.version_id,
profile,
pool, pool,
fetch_semaphore, fetch_semaphore,
) )
@@ -802,6 +806,7 @@ impl ModpackIdentifiers {
/// Checks cache first, falls back to downloading mrpack if not cached. /// Checks cache first, falls back to downloading mrpack if not cached.
async fn get_modpack_identifiers( async fn get_modpack_identifiers(
version_id: &str, version_id: &str,
profile: &crate::state::Profile,
pool: &SqlitePool, pool: &SqlitePool,
fetch_semaphore: &FetchSemaphore, fetch_semaphore: &FetchSemaphore,
) -> crate::Result<ModpackIdentifiers> { ) -> crate::Result<ModpackIdentifiers> {
@@ -880,9 +885,16 @@ async fn get_modpack_identifiers(
)) ))
})?; })?;
let download_meta = DownloadMeta {
reason: DownloadReason::Modpack,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};
let mrpack_bytes = fetch_mirrors( let mrpack_bytes = fetch_mirrors(
&[&primary_file.url], &[&primary_file.url],
primary_file.hashes.get("sha1").map(|s| s.as_str()), primary_file.hashes.get("sha1").map(|s| s.as_str()),
Some(&download_meta),
fetch_semaphore, fetch_semaphore,
pool, pool,
) )

View File

@@ -35,6 +35,7 @@ impl ModrinthCredentials {
None, None,
Some(("Authorization", &*creds.session)), Some(("Authorization", &*creds.session)),
None, None,
None,
semaphore, semaphore,
exec, exec,
) )
@@ -226,6 +227,7 @@ async fn fetch_info(
None, None,
Some(("Authorization", token)), Some(("Authorization", token)),
None, None,
None,
semaphore, semaphore,
exec, exec,
) )

View File

@@ -1110,10 +1110,25 @@ impl Profile {
pub async fn add_project_version( pub async fn add_project_version(
profile_path: &str, profile_path: &str,
version_id: &str, version_id: &str,
reason: util::fetch::DownloadReason,
pool: &SqlitePool, pool: &SqlitePool,
fetch_semaphore: &FetchSemaphore, fetch_semaphore: &FetchSemaphore,
io_semaphore: &IoSemaphore, io_semaphore: &IoSemaphore,
) -> crate::Result<String> { ) -> crate::Result<String> {
let profile =
Self::get(profile_path, pool).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(
profile_path.to_string(),
)
.as_error()
})?;
let download_meta = util::fetch::DownloadMeta {
reason,
game_version: profile.game_version.clone(),
loader: profile.loader.as_str().to_string(),
};
let version = let version =
CachedEntry::get_version(version_id, None, pool, fetch_semaphore) CachedEntry::get_version(version_id, None, pool, fetch_semaphore)
.await? .await?
@@ -1139,6 +1154,7 @@ impl Profile {
let bytes = util::fetch::fetch( let bytes = util::fetch::fetch(
&file.url, &file.url,
file.hashes.get("sha1").map(|x| &**x), file.hashes.get("sha1").map(|x| &**x),
Some(&download_meta),
fetch_semaphore, fetch_semaphore,
pool, pool,
) )

View File

@@ -9,6 +9,7 @@ use parking_lot::Mutex;
use rand::Rng; use rand::Rng;
use reqwest::Method; use reqwest::Method;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -17,6 +18,30 @@ use std::time::{self};
use tokio::sync::Semaphore; use tokio::sync::Semaphore;
use tokio::{fs::File, io::AsyncWriteExt}; use tokio::{fs::File, io::AsyncWriteExt};
pub const DOWNLOAD_META_HEADER: &str = "modrinth-download-meta";
#[derive(Debug, derive_more::Display, Clone, Copy, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[display(rename_all = "snake_case")]
pub enum DownloadReason {
Standalone,
Dependency,
Modpack,
}
#[derive(Debug, Clone, Serialize)]
pub struct DownloadMeta {
pub reason: DownloadReason,
pub game_version: String,
pub loader: String,
}
impl DownloadMeta {
pub fn to_header_value(&self) -> String {
serde_json::to_string(self).unwrap_or_default()
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct IoSemaphore(pub Semaphore); pub struct IoSemaphore(pub Semaphore);
#[derive(Debug)] #[derive(Debug)]
@@ -156,17 +181,29 @@ const FETCH_ATTEMPTS: usize = 2;
pub async fn fetch( pub async fn fetch(
url: &str, url: &str,
sha1: Option<&str>, sha1: Option<&str>,
download_meta: Option<&DownloadMeta>,
semaphore: &FetchSemaphore, semaphore: &FetchSemaphore,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
) -> crate::Result<Bytes> { ) -> crate::Result<Bytes> {
fetch_advanced(Method::GET, url, sha1, None, None, None, semaphore, exec) fetch_advanced(
.await Method::GET,
url,
sha1,
None,
None,
download_meta,
None,
semaphore,
exec,
)
.await
} }
#[tracing::instrument(skip(semaphore))] #[tracing::instrument(skip(semaphore))]
pub async fn fetch_with_client( pub async fn fetch_with_client(
url: &str, url: &str,
sha1: Option<&str>, sha1: Option<&str>,
download_meta: Option<&DownloadMeta>,
semaphore: &FetchSemaphore, semaphore: &FetchSemaphore,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
client: &reqwest::Client, client: &reqwest::Client,
@@ -177,6 +214,7 @@ pub async fn fetch_with_client(
sha1, sha1,
None, None,
None, None,
download_meta,
None, None,
semaphore, semaphore,
exec, exec,
@@ -198,7 +236,7 @@ where
T: DeserializeOwned, T: DeserializeOwned,
{ {
let result = fetch_advanced( let result = fetch_advanced(
method, url, sha1, json_body, None, None, semaphore, exec, method, url, sha1, json_body, None, None, None, semaphore, exec,
) )
.await?; .await?;
let value = serde_json::from_slice(&result)?; let value = serde_json::from_slice(&result)?;
@@ -215,6 +253,7 @@ pub async fn fetch_advanced(
sha1: Option<&str>, sha1: Option<&str>,
json_body: Option<serde_json::Value>, json_body: Option<serde_json::Value>,
header: Option<(&str, &str)>, header: Option<(&str, &str)>,
download_meta: Option<&DownloadMeta>,
loading_bar: Option<(&LoadingBarId, f64)>, loading_bar: Option<(&LoadingBarId, f64)>,
semaphore: &FetchSemaphore, semaphore: &FetchSemaphore,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
@@ -225,6 +264,7 @@ pub async fn fetch_advanced(
sha1, sha1,
json_body, json_body,
header, header,
download_meta,
loading_bar, loading_bar,
semaphore, semaphore,
exec, exec,
@@ -242,6 +282,7 @@ pub async fn fetch_advanced_with_client(
sha1: Option<&str>, sha1: Option<&str>,
json_body: Option<serde_json::Value>, json_body: Option<serde_json::Value>,
header: Option<(&str, &str)>, header: Option<(&str, &str)>,
download_meta: Option<&DownloadMeta>,
loading_bar: Option<(&LoadingBarId, f64)>, loading_bar: Option<(&LoadingBarId, f64)>,
semaphore: &FetchSemaphore, semaphore: &FetchSemaphore,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
@@ -262,12 +303,15 @@ pub async fn fetch_advanced_with_client(
None None
}; };
let download_meta_header = download_meta
.map(|m| (DOWNLOAD_META_HEADER.to_string(), m.to_header_value()));
for attempt in 1..=(FETCH_ATTEMPTS + 1) { for attempt in 1..=(FETCH_ATTEMPTS + 1) {
if is_api_url && GLOBAL_FETCH_FENCE.is_blocked() { if is_api_url && GLOBAL_FETCH_FENCE.is_blocked() {
return Err(ErrorKind::ApiIsDownError.into()); return Err(ErrorKind::ApiIsDownError.into());
} }
let mut req = INSECURE_REQWEST_CLIENT.request(method.clone(), url); let mut req = client.request(method.clone(), url);
if let Some(body) = json_body.clone() { if let Some(body) = json_body.clone() {
req = req.json(&body); req = req.json(&body);
@@ -281,6 +325,11 @@ pub async fn fetch_advanced_with_client(
req = req.header("Authorization", &creds.session); req = req.header("Authorization", &creds.session);
} }
if let Some((name, value)) = &download_meta_header {
tracing::info!("Sending download analytics: {value}");
req = req.header(name.as_str(), value.as_str());
}
let result = req.send().await; let result = req.send().await;
match result { match result {
Ok(resp) => { Ok(resp) => {
@@ -375,6 +424,7 @@ pub async fn fetch_advanced_with_client(
pub async fn fetch_mirrors( pub async fn fetch_mirrors(
mirrors: &[&str], mirrors: &[&str],
sha1: Option<&str>, sha1: Option<&str>,
download_meta: Option<&DownloadMeta>,
semaphore: &FetchSemaphore, semaphore: &FetchSemaphore,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
) -> crate::Result<Bytes> { ) -> crate::Result<Bytes> {
@@ -385,9 +435,15 @@ pub async fn fetch_mirrors(
} }
for (index, mirror) in mirrors.iter().enumerate() { for (index, mirror) in mirrors.iter().enumerate() {
let result = let result = fetch_with_client(
fetch_with_client(mirror, sha1, semaphore, exec, &REQWEST_CLIENT) mirror,
.await; sha1,
download_meta,
semaphore,
exec,
&REQWEST_CLIENT,
)
.await;
if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) { if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
return result; return result;