From 96545c825b51397e01807dfec1a74bda879f12f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Tue, 21 Jul 2026 12:30:37 -0400 Subject: [PATCH 1/2] feat(app): context tracing --- apps/app-frontend/src/helpers/cache.js | 2 +- apps/app-frontend/src/helpers/friends.ts | 2 +- apps/app-frontend/src/helpers/install.ts | 2 +- apps/app-frontend/src/helpers/instance.ts | 2 +- apps/app-frontend/src/helpers/invoke.ts | 83 +++++ apps/app-frontend/src/helpers/jre.js | 2 +- apps/app-frontend/src/helpers/metadata.js | 2 +- apps/app-frontend/src/helpers/mr_auth.ts | 2 +- apps/app-frontend/src/helpers/tags.js | 2 +- apps/app-frontend/src/helpers/worlds.ts | 2 +- apps/app/src/api/cache.rs | 23 +- apps/app/src/api/friends.rs | 23 +- apps/app/src/api/install.rs | 43 ++- apps/app/src/api/instance.rs | 104 +++++-- apps/app/src/api/jre.rs | 8 +- apps/app/src/api/metadata.rs | 15 +- apps/app/src/api/mod.rs | 6 + apps/app/src/api/mr_auth.rs | 12 +- apps/app/src/api/tags.rs | 35 ++- apps/app/src/api/worlds.rs | 17 +- packages/app-lib/src/api/cache.rs | 9 +- packages/app-lib/src/api/friends.rs | 36 ++- packages/app-lib/src/api/instance/content.rs | 24 +- .../app-lib/src/api/instance/export_mrpack.rs | 7 +- packages/app-lib/src/api/instance/install.rs | 9 +- .../app-lib/src/api/instance/lifecycle.rs | 3 + packages/app-lib/src/api/instance/projects.rs | 23 +- packages/app-lib/src/api/instance/run.rs | 58 ++-- packages/app-lib/src/api/jre.rs | 19 +- packages/app-lib/src/api/metadata.rs | 13 +- packages/app-lib/src/api/mod.rs | 2 +- packages/app-lib/src/api/mr_auth.rs | 23 +- .../app-lib/src/api/pack/import/atlauncher.rs | 5 + .../app-lib/src/api/pack/import/curseforge.rs | 4 + .../app-lib/src/api/pack/import/gdlauncher.rs | 3 + packages/app-lib/src/api/pack/import/mmc.rs | 11 +- packages/app-lib/src/api/pack/import/mod.rs | 10 + packages/app-lib/src/api/pack/install_from.rs | 14 +- .../app-lib/src/api/pack/install_mrpack.rs | 9 + packages/app-lib/src/api/tags.rs | 84 +++-- packages/app-lib/src/api/worlds.rs | 3 + packages/app-lib/src/install/runner.rs | 138 ++++++--- packages/app-lib/src/launcher/download.rs | 55 +++- packages/app-lib/src/launcher/mod.rs | 88 ++++-- packages/app-lib/src/lib.rs | 4 + packages/app-lib/src/operation.rs | 293 ++++++++++++++++++ packages/app-lib/src/state/cache.rs | 33 +- packages/app-lib/src/state/friends.rs | 13 +- .../commands/apply_content_install.rs | 22 ++ .../commands/apply_content_update.rs | 54 +++- .../commands/check_content_updates.rs | 11 +- .../instances/commands/create_instance.rs | 8 +- .../state/instances/commands/list_content.rs | 109 +++++-- .../instances/commands/sync_content_files.rs | 7 +- .../app-lib/src/state/instances/watcher.rs | 10 +- packages/app-lib/src/state/mod.rs | 7 +- packages/app-lib/src/state/mr_auth.rs | 13 +- packages/app-lib/src/state/process.rs | 4 + packages/app-lib/src/util/fetch.rs | 184 ++++++++++- 59 files changed, 1527 insertions(+), 282 deletions(-) create mode 100644 apps/app-frontend/src/helpers/invoke.ts create mode 100644 packages/app-lib/src/operation.rs diff --git a/apps/app-frontend/src/helpers/cache.js b/apps/app-frontend/src/helpers/cache.js index ab10222526..641e069550 100644 --- a/apps/app-frontend/src/helpers/cache.js +++ b/apps/app-frontend/src/helpers/cache.js @@ -1,4 +1,4 @@ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' export async function get_project(id, cacheBehaviour) { return await invoke('plugin:cache|get_project', { id, cacheBehaviour }) diff --git a/apps/app-frontend/src/helpers/friends.ts b/apps/app-frontend/src/helpers/friends.ts index 7fd27b5a88..e480ee31fd 100644 --- a/apps/app-frontend/src/helpers/friends.ts +++ b/apps/app-frontend/src/helpers/friends.ts @@ -1,5 +1,5 @@ import type { User } from '@modrinth/utils' -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' import type { Dayjs } from 'dayjs' import dayjs from 'dayjs' diff --git a/apps/app-frontend/src/helpers/install.ts b/apps/app-frontend/src/helpers/install.ts index ee0f3f2d83..ceaaee83d2 100644 --- a/apps/app-frontend/src/helpers/install.ts +++ b/apps/app-frontend/src/helpers/install.ts @@ -1,4 +1,4 @@ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' import { install_job_listener } from './events' import type { InstanceLink, InstanceLoader } from './types' diff --git a/apps/app-frontend/src/helpers/instance.ts b/apps/app-frontend/src/helpers/instance.ts index 3a7a1b567e..5a1eb3235b 100644 --- a/apps/app-frontend/src/helpers/instance.ts +++ b/apps/app-frontend/src/helpers/instance.ts @@ -5,7 +5,7 @@ */ import type { Labrinth } from '@modrinth/api-client' import type { ContentItem, ContentOwner } from '@modrinth/ui' -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' import type { InstallJobSnapshot } from './install' import type { diff --git a/apps/app-frontend/src/helpers/invoke.ts b/apps/app-frontend/src/helpers/invoke.ts new file mode 100644 index 0000000000..427fe74f04 --- /dev/null +++ b/apps/app-frontend/src/helpers/invoke.ts @@ -0,0 +1,83 @@ +import { invoke as tauriInvoke } from '@tauri-apps/api/core' + +export type OperationCause = + | 'app/startup' + | 'navigation/home' + | 'navigation/library' + | 'navigation/browse' + | 'navigation/project' + | 'navigation/instance/overview' + | 'navigation/instance/content' + | 'navigation/instance/logs' + | 'navigation/servers' + | 'navigation/server/manage' + | 'navigation/server/content' + | 'instance/refresh/user' + | 'instance/refresh/filesystem_watch' + | 'instance/update/all' + | 'instance/update/single' + | 'instance/install' + | 'cache/revalidate' + | 'auth/session_refresh' + | 'background/friends' + | 'minecraft/launch' + | 'app/update_check' + | 'unattributed' + +function navigationCause(pathname: string): OperationCause { + if (pathname === '/hosting/manage' || pathname === '/hosting/manage/') { + return 'navigation/servers' + } + if (pathname.startsWith('/hosting/manage/')) { + return pathname.split('/').includes('content') + ? 'navigation/server/content' + : 'navigation/server/manage' + } + if (pathname.startsWith('/browse/')) return 'navigation/browse' + if (pathname.startsWith('/project/')) return 'navigation/project' + if (pathname.startsWith('/library')) return 'navigation/library' + if (pathname.startsWith('/instance/')) { + return pathname.split('/').includes('logs') + ? 'navigation/instance/logs' + : 'navigation/instance/content' + } + + return 'navigation/home' +} + +function commandCause(command: string): OperationCause { + if (command === 'plugin:instance|instance_update_all') return 'instance/update/all' + if ( + command === 'plugin:instance|instance_update_project' || + command === 'plugin:instance|instance_switch_project_version_with_dependencies' || + command === 'plugin:instance|instance_update_managed_modrinth_version' + ) { + return 'instance/update/single' + } + if ( + command.startsWith('plugin:install|install_') || + command === 'plugin:jre|jre_auto_install_java' || + command === 'plugin:instance|instance_add_project_from_version' || + command === 'plugin:instance|instance_install_project_with_dependencies' || + command === 'plugin:instance|instance_repair_managed_modrinth' + ) { + return 'instance/install' + } + if ( + command === 'plugin:instance|instance_run' || + command === 'plugin:worlds|start_join_singleplayer_world' || + command === 'plugin:worlds|start_join_server' + ) { + return 'minecraft/launch' + } + if (command === 'plugin:mr-auth|get') return 'auth/session_refresh' + + return navigationCause(window.location.pathname) +} + +export function invoke(command: string, args: Record = {}): Promise { + return tauriInvoke(command, { + ...args, + invocationContext: { cause: commandCause(command) }, + }) +} diff --git a/apps/app-frontend/src/helpers/jre.js b/apps/app-frontend/src/helpers/jre.js index 21af5847e1..9e1ddf1c5d 100644 --- a/apps/app-frontend/src/helpers/jre.js +++ b/apps/app-frontend/src/helpers/jre.js @@ -3,7 +3,7 @@ * So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized, * and deserialized into a usable JS object. */ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' /* diff --git a/apps/app-frontend/src/helpers/metadata.js b/apps/app-frontend/src/helpers/metadata.js index 51898dd53e..1553318572 100644 --- a/apps/app-frontend/src/helpers/metadata.js +++ b/apps/app-frontend/src/helpers/metadata.js @@ -1,4 +1,4 @@ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' /// Gets the game versions from daedalus // Returns a VersionManifest diff --git a/apps/app-frontend/src/helpers/mr_auth.ts b/apps/app-frontend/src/helpers/mr_auth.ts index d28ad63db4..e44bf2ba57 100644 --- a/apps/app-frontend/src/helpers/mr_auth.ts +++ b/apps/app-frontend/src/helpers/mr_auth.ts @@ -3,7 +3,7 @@ * So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized, * and deserialized into a usable JS object. */ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' export type ModrinthCredentials = { session: string diff --git a/apps/app-frontend/src/helpers/tags.js b/apps/app-frontend/src/helpers/tags.js index fdfdb04d07..3e93b26685 100644 --- a/apps/app-frontend/src/helpers/tags.js +++ b/apps/app-frontend/src/helpers/tags.js @@ -3,7 +3,7 @@ * So, for example, addDefaultInstance creates a blank instance object, where the Rust struct is serialized, * and deserialized into a usable JS object. */ -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' // Gets cached category tags export async function get_categories() { diff --git a/apps/app-frontend/src/helpers/worlds.ts b/apps/app-frontend/src/helpers/worlds.ts index 3b5285f51b..09f64a8f65 100644 --- a/apps/app-frontend/src/helpers/worlds.ts +++ b/apps/app-frontend/src/helpers/worlds.ts @@ -1,6 +1,6 @@ import type { GameVersion } from '@modrinth/ui' import { autoToHTML } from '@sfirew/minecraft-motd-parser' -import { invoke } from '@tauri-apps/api/core' +import { invoke } from '@/helpers/invoke' import dayjs from 'dayjs' import { get_full_path } from '@/helpers/instance' diff --git a/apps/app/src/api/cache.rs b/apps/app/src/api/cache.rs index f9f7b7452a..2e376ab492 100644 --- a/apps/app/src/api/cache.rs +++ b/apps/app/src/api/cache.rs @@ -6,20 +6,27 @@ macro_rules! impl_cache_methods { $( paste::paste! { #[tauri::command] - pub async fn [](id: &str, cache_behaviour: Option) -> Result> + pub async fn []( + id: &str, + cache_behaviour: Option, + invocation_context: theseus::InvocationContext, + ) -> Result> { - Ok(theseus::cache::[](id, cache_behaviour).await?) + let context = crate::api::operation_context(invocation_context); + Ok(theseus::cache::[](&context, id, cache_behaviour).await?) } #[tauri::command] pub async fn []( ids: Vec, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { + let context = crate::api::operation_context(invocation_context); let ids = ids.iter().map(|x| &**x).collect::>(); let entries = - theseus::cache::[](&*ids, cache_behaviour).await?; + theseus::cache::[](&context, &*ids, cache_behaviour).await?; Ok(entries) } @@ -73,9 +80,13 @@ pub async fn purge_cache_types(cache_types: Vec) -> Result<()> { pub async fn get_project_versions( project_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result>> { - Ok( - theseus::cache::get_project_versions(project_id, cache_behaviour) - .await?, + let context = crate::api::operation_context(invocation_context); + Ok(theseus::cache::get_project_versions( + &context, + project_id, + cache_behaviour, ) + .await?) } diff --git a/apps/app/src/api/friends.rs b/apps/app/src/api/friends.rs index 82d1d37f69..12b42437dc 100644 --- a/apps/app/src/api/friends.rs +++ b/apps/app/src/api/friends.rs @@ -13,8 +13,11 @@ pub fn init() -> TauriPlugin { } #[tauri::command] -pub async fn friends() -> crate::api::Result> { - Ok(theseus::friends::friends().await?) +pub async fn friends( + invocation_context: theseus::InvocationContext, +) -> crate::api::Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::friends::friends(&context).await?) } #[tauri::command] @@ -23,11 +26,19 @@ pub async fn friend_statuses() -> crate::api::Result> { } #[tauri::command] -pub async fn add_friend(user_id: &str) -> crate::api::Result<()> { - Ok(theseus::friends::add_friend(user_id).await?) +pub async fn add_friend( + user_id: &str, + invocation_context: theseus::InvocationContext, +) -> crate::api::Result<()> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::friends::add_friend(&context, user_id).await?) } #[tauri::command] -pub async fn remove_friend(user_id: &str) -> crate::api::Result<()> { - Ok(theseus::friends::remove_friend(user_id).await?) +pub async fn remove_friend( + user_id: &str, + invocation_context: theseus::InvocationContext, +) -> crate::api::Result<()> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::friends::remove_friend(&context, user_id).await?) } diff --git a/apps/app/src/api/install.rs b/apps/app/src/api/install.rs index 96d2f0481b..df30c9be61 100644 --- a/apps/app/src/api/install.rs +++ b/apps/app/src/api/install.rs @@ -67,15 +67,23 @@ impl InstallPostInstallEditRequest { #[tauri::command] pub async fn install_get_modpack_preview( location: CreatePackLocation, + invocation_context: theseus::InvocationContext, ) -> Result { - Ok(theseus::pack::install_from::get_instance_from_pack(location).await?) + let context = crate::api::operation_context(invocation_context); + Ok( + theseus::pack::install_from::get_instance_from_pack(&context, location) + .await?, + ) } #[tauri::command] pub async fn install_create_instance( request: InstallCreateInstanceRequest, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::install::create_instance( + &context, request.name.trim().to_string(), request.game_version, request.loader, @@ -93,8 +101,11 @@ pub async fn install_create_instance( pub async fn install_create_modpack_instance( location: CreatePackLocation, post_install_edit: Option, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::install::create_modpack_instance( + &context, location, post_install_edit.map(|edit| edit.into_core()).transpose()?, ) @@ -106,8 +117,11 @@ pub async fn install_import_instance( launcher_type: ImportLauncherType, base_path: PathBuf, instance_folder: String, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::install::import_instance( + &context, launcher_type, base_path, instance_folder, @@ -118,16 +132,28 @@ pub async fn install_import_instance( #[tauri::command] pub async fn install_duplicate_instance( source_instance_id: String, + invocation_context: theseus::InvocationContext, ) -> Result { - Ok(theseus::install::duplicate_instance(source_instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok( + theseus::install::duplicate_instance(&context, source_instance_id) + .await?, + ) } #[tauri::command] pub async fn install_existing_instance( instance_id: String, force: bool, + invocation_context: theseus::InvocationContext, ) -> Result { - Ok(theseus::install::install_existing_instance(instance_id, force).await?) + let context = crate::api::operation_context(invocation_context); + Ok(theseus::install::install_existing_instance( + &context, + instance_id, + force, + ) + .await?) } #[tauri::command] @@ -135,8 +161,11 @@ pub async fn install_pack_to_existing_instance( instance_id: String, location: CreatePackLocation, post_install_edit: Option, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::install::install_pack_to_existing_instance( + &context, instance_id, location, post_install_edit.map(|edit| edit.into_core()).transpose()?, @@ -157,8 +186,12 @@ pub async fn install_job_get(job_id: Uuid) -> Result { } #[tauri::command] -pub async fn install_job_retry(job_id: Uuid) -> Result { - Ok(theseus::install::retry_job(job_id).await?) +pub async fn install_job_retry( + job_id: Uuid, + invocation_context: theseus::InvocationContext, +) -> Result { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::install::retry_job(&context, job_id).await?) } #[tauri::command] diff --git a/apps/app/src/api/instance.rs b/apps/app/src/api/instance.rs index 1b33adf62c..c77a416c75 100644 --- a/apps/app/src/api/instance.rs +++ b/apps/app/src/api/instance.rs @@ -437,15 +437,25 @@ pub async fn instance_list() -> Result> { pub async fn instance_get_projects( instance_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok(theseus::instance::get_projects(instance_id, cache_behaviour).await?) + let context = crate::api::operation_context(invocation_context); + Ok( + theseus::instance::get_projects(&context, instance_id, cache_behaviour) + .await?, + ) } #[tauri::command] pub async fn instance_get_installed_project_ids( instance_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok(theseus::instance::get_installed_project_ids(instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok( + theseus::instance::get_installed_project_ids(&context, instance_id) + .await?, + ) } #[tauri::command] @@ -466,27 +476,36 @@ pub async fn instance_get_install_candidates( pub async fn instance_content( instance_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { - instance_get_content_items(instance_id, cache_behaviour).await + instance_get_content_items(instance_id, cache_behaviour, invocation_context) + .await } #[tauri::command] pub async fn instance_get_content_items( instance_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok( - theseus::instance::get_content_items(instance_id, cache_behaviour) - .await?, + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::get_content_items( + &context, + instance_id, + cache_behaviour, ) + .await?) } #[tauri::command] pub async fn instance_get_dependencies_as_content_items( dependencies: Vec, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::get_dependencies_as_content_items( + &context, dependencies, cache_behaviour, ) @@ -497,22 +516,26 @@ pub async fn instance_get_dependencies_as_content_items( pub async fn instance_get_linked_modpack_info( instance_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok( - theseus::instance::get_linked_modpack_info( - instance_id, - cache_behaviour, - ) - .await?, + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::get_linked_modpack_info( + &context, + instance_id, + cache_behaviour, ) + .await?) } #[tauri::command] pub async fn instance_get_linked_modpack_content( instance_id: &str, cache_behaviour: Option, + invocation_context: theseus::InvocationContext, ) -> Result> { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::get_linked_modpack_content( + &context, instance_id, cache_behaviour, ) @@ -535,19 +558,23 @@ pub async fn instance_get_mod_full_path( #[tauri::command] pub async fn instance_get_optimal_jre_key( instance_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok(theseus::instance::get_optimal_jre_key(instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::get_optimal_jre_key(&context, instance_id).await?) } #[tauri::command] pub async fn instance_check_installed( instance_id: &str, project_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); let check_project_id = project_id; if let Ok(projects) = - theseus::instance::get_projects(instance_id, None).await + theseus::instance::get_projects(&context, instance_id, None).await { Ok(projects.into_iter().any(|(_, project)| { project @@ -563,19 +590,26 @@ pub async fn instance_check_installed( #[tauri::command] pub async fn instance_update_all( instance_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok(theseus::instance::update_all_projects(instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::update_all_projects(&context, instance_id).await?) } #[tauri::command] pub async fn instance_update_project( instance_id: &str, project_path: &str, + invocation_context: theseus::InvocationContext, ) -> Result { - Ok( - theseus::instance::update_project(instance_id, project_path, None) - .await?, + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::update_project( + &context, + instance_id, + project_path, + None, ) + .await?) } #[tauri::command] @@ -584,8 +618,11 @@ pub async fn instance_add_project_from_version( version_id: &str, reason: DownloadReason, dependent_on_version_id: Option, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::add_project_from_version( + &context, instance_id, version_id, reason, @@ -598,8 +635,11 @@ pub async fn instance_add_project_from_version( pub async fn instance_install_project_with_dependencies( instance_id: &str, request: InstallProjectWithDependenciesRequest, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::install_project_with_dependencies( + &context, instance_id, request, ) @@ -611,8 +651,11 @@ pub async fn instance_switch_project_version_with_dependencies( instance_id: &str, project_path: &str, version_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::switch_project_version_with_dependencies( + &context, instance_id, project_path, version_id, @@ -635,8 +678,12 @@ pub async fn instance_add_project_from_path( } #[tauri::command] -pub async fn instance_is_file_on_modrinth(project_path: &Path) -> Result { - Ok(theseus::instance::is_file_on_modrinth(project_path).await?) +pub async fn instance_is_file_on_modrinth( + project_path: &Path, + invocation_context: theseus::InvocationContext, +) -> Result { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::instance::is_file_on_modrinth(&context, project_path).await?) } #[tauri::command] @@ -666,8 +713,11 @@ pub async fn instance_remove_project( pub async fn instance_update_managed_modrinth_version( instance_id: String, version_id: String, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); Ok(theseus::instance::update_managed_modrinth_version( + &context, &instance_id, &version_id, ) @@ -677,8 +727,13 @@ pub async fn instance_update_managed_modrinth_version( #[tauri::command] pub async fn instance_repair_managed_modrinth( instance_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result { - Ok(theseus::instance::repair_managed_modrinth(instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok( + theseus::instance::repair_managed_modrinth(&context, instance_id) + .await?, + ) } #[tauri::command] @@ -689,8 +744,11 @@ pub async fn instance_export_mrpack( version_id: Option, description: Option, name: Option, + invocation_context: theseus::InvocationContext, ) -> Result<()> { + let context = crate::api::operation_context(invocation_context); theseus::instance::export_mrpack( + &context, instance_id, export_location, included_overrides, @@ -713,12 +771,14 @@ pub async fn instance_get_pack_export_candidates( pub async fn instance_run( instance_id: &str, server_address: Option, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); let quick_play = match server_address { Some(addr) => QuickPlayType::Server(ServerAddress::Unresolved(addr)), None => QuickPlayType::None, }; - Ok(theseus::instance::run(instance_id, quick_play).await?) + Ok(theseus::instance::run(&context, instance_id, quick_play).await?) } #[tauri::command] diff --git a/apps/app/src/api/jre.rs b/apps/app/src/api/jre.rs index 71c72257cf..ac383a4594 100644 --- a/apps/app/src/api/jre.rs +++ b/apps/app/src/api/jre.rs @@ -53,8 +53,12 @@ pub async fn jre_test_jre(path: PathBuf, major_version: u32) -> Result { // Auto installs java for the given java version #[tauri::command] -pub async fn jre_auto_install_java(java_version: u32) -> Result { - Ok(jre::auto_install_java(java_version).await?) +pub async fn jre_auto_install_java( + java_version: u32, + invocation_context: theseus::InvocationContext, +) -> Result { + let context = crate::api::operation_context(invocation_context); + Ok(jre::auto_install_java(&context, java_version).await?) } // Gets the maximum memory a system has available. diff --git a/apps/app/src/api/metadata.rs b/apps/app/src/api/metadata.rs index 9364924e50..67bb7dc6a7 100644 --- a/apps/app/src/api/metadata.rs +++ b/apps/app/src/api/metadata.rs @@ -13,12 +13,19 @@ pub fn init() -> tauri::plugin::TauriPlugin { /// Gets the game versions from daedalus #[tauri::command] -pub async fn metadata_get_game_versions() -> Result { - Ok(theseus::metadata::get_minecraft_versions().await?) +pub async fn metadata_get_game_versions( + invocation_context: theseus::InvocationContext, +) -> Result { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::metadata::get_minecraft_versions(&context).await?) } /// Gets the fabric versions from daedalus #[tauri::command] -pub async fn metadata_get_loader_versions(loader: &str) -> Result { - Ok(theseus::metadata::get_loader_versions(loader).await?) +pub async fn metadata_get_loader_versions( + loader: &str, + invocation_context: theseus::InvocationContext, +) -> Result { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::metadata::get_loader_versions(&context, loader).await?) } diff --git a/apps/app/src/api/mod.rs b/apps/app/src/api/mod.rs index 2d45357cbf..3ec0d32703 100644 --- a/apps/app/src/api/mod.rs +++ b/apps/app/src/api/mod.rs @@ -31,6 +31,12 @@ mod oauth_utils; pub type Result = std::result::Result; +pub(crate) fn operation_context( + invocation_context: theseus::InvocationContext, +) -> theseus::OperationContext { + invocation_context.into_operation_context() +} + // // Main returnable Theseus GUI error // // Needs to be Serializable to be returned to the JavaScript side // #[derive(Error, Debug, Serialize)] diff --git a/apps/app/src/api/mr_auth.rs b/apps/app/src/api/mr_auth.rs index 2143d20c5e..f7c92e3981 100644 --- a/apps/app/src/api/mr_auth.rs +++ b/apps/app/src/api/mr_auth.rs @@ -22,7 +22,9 @@ pub fn init() -> TauriPlugin { #[tauri::command] pub async fn modrinth_login( app: tauri::AppHandle, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); let (auth_code_recv_socket_tx, auth_code_recv_socket) = oneshot::channel(); let auth_code = tokio::spawn(oauth_utils::auth_code_reply::listen( auth_code_recv_socket_tx, @@ -58,7 +60,8 @@ pub async fn modrinth_login( )); }; - let credentials = mr_auth::authenticate_finish_flow(&auth_code).await?; + let credentials = + mr_auth::authenticate_finish_flow(&context, &auth_code).await?; if let Some(main_window) = app.get_window("main") { main_window.set_focus().ok(); @@ -73,8 +76,11 @@ pub async fn logout() -> Result<()> { } #[tauri::command] -pub async fn get() -> Result> { - Ok(theseus::mr_auth::get_credentials().await?) +pub async fn get( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::mr_auth::get_credentials(&context).await?) } #[tauri::command] diff --git a/apps/app/src/api/tags.rs b/apps/app/src/api/tags.rs index b69d221d71..4a3839b2ac 100644 --- a/apps/app/src/api/tags.rs +++ b/apps/app/src/api/tags.rs @@ -15,30 +15,45 @@ pub fn init() -> tauri::plugin::TauriPlugin { /// Gets cached category tags from the database #[tauri::command] -pub async fn tags_get_categories() -> Result> { - Ok(theseus::tags::get_category_tags().await?) +pub async fn tags_get_categories( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::tags::get_category_tags(&context).await?) } /// Gets cached report type tags from the database #[tauri::command] -pub async fn tags_get_report_types() -> Result> { - Ok(theseus::tags::get_report_type_tags().await?) +pub async fn tags_get_report_types( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::tags::get_report_type_tags(&context).await?) } /// Gets cached loader tags from the database #[tauri::command] -pub async fn tags_get_loaders() -> Result> { - Ok(theseus::tags::get_loader_tags().await?) +pub async fn tags_get_loaders( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::tags::get_loader_tags(&context).await?) } /// Gets cached game version tags from the database #[tauri::command] -pub async fn tags_get_game_versions() -> Result> { - Ok(theseus::tags::get_game_version_tags().await?) +pub async fn tags_get_game_versions( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::tags::get_game_version_tags(&context).await?) } /// Gets cached donation platform tags from the database #[tauri::command] -pub async fn tags_get_donation_platforms() -> Result> { - Ok(theseus::tags::get_donation_platform_tags().await?) +pub async fn tags_get_donation_platforms( + invocation_context: theseus::InvocationContext, +) -> Result> { + let context = crate::api::operation_context(invocation_context); + Ok(theseus::tags::get_donation_platform_tags(&context).await?) } diff --git a/apps/app/src/api/worlds.rs b/apps/app/src/api/worlds.rs index afa743e0eb..b1e2aa3426 100644 --- a/apps/app/src/api/worlds.rs +++ b/apps/app/src/api/worlds.rs @@ -196,8 +196,10 @@ pub async fn remove_server_from_instance( #[tauri::command] pub async fn get_instance_protocol_version( instance_id: &str, + invocation_context: theseus::InvocationContext, ) -> Result> { - Ok(worlds::get_instance_protocol_version(instance_id).await?) + let context = crate::api::operation_context(invocation_context); + Ok(worlds::get_instance_protocol_version(&context, instance_id).await?) } #[tauri::command] @@ -212,9 +214,15 @@ pub async fn get_server_status( pub async fn start_join_singleplayer_world( instance_id: &str, world: String, + invocation_context: theseus::InvocationContext, ) -> Result { - let process = - instance::run(instance_id, QuickPlayType::Singleplayer(world)).await?; + let context = crate::api::operation_context(invocation_context); + let process = instance::run( + &context, + instance_id, + QuickPlayType::Singleplayer(world), + ) + .await?; Ok(process) } @@ -223,8 +231,11 @@ pub async fn start_join_singleplayer_world( pub async fn start_join_server( instance_id: &str, address: &str, + invocation_context: theseus::InvocationContext, ) -> Result { + let context = crate::api::operation_context(invocation_context); let process = instance::run( + &context, instance_id, QuickPlayType::Server(ServerAddress::Unresolved(address.to_owned())), ) diff --git a/packages/app-lib/src/api/cache.rs b/packages/app-lib/src/api/cache.rs index 62dc68a24d..8bd8b60ad3 100644 --- a/packages/app-lib/src/api/cache.rs +++ b/packages/app-lib/src/api/cache.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::{ CacheBehaviour, CacheValueType, CachedEntry, Organization, Project, ProjectV3, SearchResults, SearchResultsV3, TeamMember, User, Version, @@ -9,23 +10,25 @@ macro_rules! impl_cache_methods { paste::paste! { #[tracing::instrument] pub async fn []( + context: &OperationContext, id: &str, cache_behaviour: Option, ) -> crate::Result> { let state = crate::State::get().await?; - Ok(CachedEntry::[](&[id], cache_behaviour, &state.pool, &state.api_semaphore).await?.into_iter().next()) + Ok(CachedEntry::[](context, &[id], cache_behaviour, &state.pool, &state.api_semaphore).await?.into_iter().next()) } #[tracing::instrument] pub async fn []( + context: &OperationContext, ids: &[&str], cache_behaviour: Option, ) -> crate::Result> { let state = crate::State::get().await?; let entries = - CachedEntry::[](ids, None, &state.pool, &state.api_semaphore).await?; + CachedEntry::[](context, ids, None, &state.pool, &state.api_semaphore).await?; Ok(entries) } @@ -58,11 +61,13 @@ pub async fn purge_cache_types( /// Uses the cache system with the ProjectVersions cache type. #[tracing::instrument] pub async fn get_project_versions( + context: &OperationContext, project_id: &str, cache_behaviour: Option, ) -> crate::Result>> { let state = crate::State::get().await?; CachedEntry::get_project_versions( + context, project_id, cache_behaviour, &state.pool, diff --git a/packages/app-lib/src/api/friends.rs b/packages/app-lib/src/api/friends.rs index 0fe883e01d..2a3e54a9b6 100644 --- a/packages/app-lib/src/api/friends.rs +++ b/packages/app-lib/src/api/friends.rs @@ -1,11 +1,15 @@ +use crate::OperationContext; use crate::state::{FriendsSocket, UserFriend}; use ariadne::users::UserStatus; #[tracing::instrument] -pub async fn friends() -> crate::Result> { +pub async fn friends( + context: &OperationContext, +) -> crate::Result> { let state = crate::State::get().await?; let friends = - FriendsSocket::friends(&state.pool, &state.api_semaphore).await?; + FriendsSocket::friends(context, &state.pool, &state.api_semaphore) + .await?; Ok(friends) } @@ -18,19 +22,35 @@ pub async fn friend_statuses() -> crate::Result> { } #[tracing::instrument] -pub async fn add_friend(user_id: &str) -> crate::Result<()> { +pub async fn add_friend( + context: &OperationContext, + user_id: &str, +) -> crate::Result<()> { let state = crate::State::get().await?; - FriendsSocket::add_friend(user_id, &state.pool, &state.api_semaphore) - .await?; + FriendsSocket::add_friend( + context, + user_id, + &state.pool, + &state.api_semaphore, + ) + .await?; Ok(()) } #[tracing::instrument] -pub async fn remove_friend(user_id: &str) -> crate::Result<()> { +pub async fn remove_friend( + context: &OperationContext, + user_id: &str, +) -> crate::Result<()> { let state = crate::State::get().await?; - FriendsSocket::remove_friend(user_id, &state.pool, &state.api_semaphore) - .await?; + FriendsSocket::remove_friend( + context, + user_id, + &state.pool, + &state.api_semaphore, + ) + .await?; Ok(()) } diff --git a/packages/app-lib/src/api/instance/content.rs b/packages/app-lib/src/api/instance/content.rs index 9adb2734cb..01299cb87a 100644 --- a/packages/app-lib/src/api/instance/content.rs +++ b/packages/app-lib/src/api/instance/content.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::{ CacheBehaviour, ContentFile, ContentItem, ContentSet, Dependency, InstanceInstallCandidate, InstanceInstallTarget, LinkedModpackInfo, @@ -7,10 +8,11 @@ use dashmap::DashMap; #[tracing::instrument] pub async fn sync_content_files( + context: &OperationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; - crate::state::sync_content_files(instance_id, &state).await + crate::state::sync_content_files(context, instance_id, &state).await } #[tracing::instrument] @@ -23,11 +25,13 @@ pub async fn list_content_sets( #[tracing::instrument] pub async fn get_projects( + context: &OperationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { let state = State::get().await?; crate::state::get_content_projects( + context, instance_id, None, cache_behaviour, @@ -38,10 +42,12 @@ pub async fn get_projects( #[tracing::instrument] pub async fn get_installed_project_ids( + context: &OperationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; crate::state::get_installed_project_ids_for_instance( + context, instance_id, None, &state, @@ -67,20 +73,30 @@ pub async fn get_install_candidates( #[tracing::instrument] pub async fn get_content_items( + context: &OperationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { let state = State::get().await?; - crate::state::list_content(instance_id, None, cache_behaviour, &state).await + crate::state::list_content( + context, + instance_id, + None, + cache_behaviour, + &state, + ) + .await } #[tracing::instrument] pub async fn get_linked_modpack_content( + context: &OperationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { let state = State::get().await?; crate::state::list_linked_modpack_content( + context, instance_id, None, cache_behaviour, @@ -91,11 +107,13 @@ pub async fn get_linked_modpack_content( #[tracing::instrument] pub async fn get_dependencies_as_content_items( + context: &OperationContext, dependencies: Vec, cache_behaviour: Option, ) -> crate::Result> { let state = State::get().await?; crate::state::dependencies_to_content_items( + context, &dependencies, cache_behaviour, &state.pool, @@ -106,11 +124,13 @@ pub async fn get_dependencies_as_content_items( #[tracing::instrument] pub async fn get_linked_modpack_info( + context: &OperationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { let state = State::get().await?; crate::state::get_linked_modpack_info( + context, instance_id, None, cache_behaviour, diff --git a/packages/app-lib/src/api/instance/export_mrpack.rs b/packages/app-lib/src/api/instance/export_mrpack.rs index 6faada6f25..54d3b2d527 100644 --- a/packages/app-lib/src/api/instance/export_mrpack.rs +++ b/packages/app-lib/src/api/instance/export_mrpack.rs @@ -1,6 +1,7 @@ use super::content::get_projects; use super::get::get; use super::paths::get_full_path; +use crate::OperationContext; use crate::event::LoadingBarType; use crate::event::emit::{emit_loading, init_loading}; use crate::pack::install_from::{ @@ -20,6 +21,7 @@ use tokio::io::AsyncReadExt; #[tracing::instrument(skip_all)] pub async fn export_mrpack( + context: &OperationContext, instance_id: &str, export_path: PathBuf, included_export_candidates: Vec, @@ -54,7 +56,7 @@ pub async fn export_mrpack( let mut writer = ZipFileWriter::with_tokio(&mut file); let version_id = version_id.unwrap_or("1.0.0".to_string()); let mut packfile = - create_mrpack_json(&metadata, version_id, description).await?; + create_mrpack_json(context, &metadata, version_id, description).await?; packfile.files.retain(|f| { is_export_candidate_included( f.path.as_str(), @@ -176,6 +178,7 @@ fn pack_get_relative_path( #[tracing::instrument(skip_all)] pub async fn create_mrpack_json( + context: &OperationContext, metadata: &InstanceMetadata, version_id: String, description: Option, @@ -212,6 +215,7 @@ pub async fn create_mrpack_json( let state = State::get().await?; let projects = get_projects( + context, &metadata.instance.id, Some(CacheBehaviour::MustRevalidate), ) @@ -223,6 +227,7 @@ pub async fn create_mrpack_json( }) .collect::>(); let versions = CachedEntry::get_version_many( + context, &projects.iter().map(|x| &*x.1).collect::>(), None, &state.pool, diff --git a/packages/app-lib/src/api/instance/install.rs b/packages/app-lib/src/api/instance/install.rs index ed308c0e9a..8c5fa45c68 100644 --- a/packages/app-lib/src/api/instance/install.rs +++ b/packages/app-lib/src/api/instance/install.rs @@ -1,6 +1,10 @@ -use crate::state::{JavaVersion, State}; +use crate::{ + OperationContext, + state::{JavaVersion, State}, +}; pub async fn get_optimal_jre_key( + operation_context: &OperationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -17,18 +21,21 @@ pub async fn get_optimal_jre_key( })?; let (minecraft, version_index) = crate::launcher::resolve_minecraft_manifest( + operation_context, &context.applied_content_set.game_version, &state, ) .await?; let version = &minecraft.versions[version_index]; let loader_version = crate::launcher::get_loader_version_from_profile( + operation_context, &context.applied_content_set.game_version, context.applied_content_set.loader, context.applied_content_set.loader_version.as_deref(), ) .await?; let version_info = crate::launcher::download::download_version_info( + operation_context, &state, version, loader_version.as_ref(), diff --git a/packages/app-lib/src/api/instance/lifecycle.rs b/packages/app-lib/src/api/instance/lifecycle.rs index 0139b6b5f7..c362225bb8 100644 --- a/packages/app-lib/src/api/instance/lifecycle.rs +++ b/packages/app-lib/src/api/instance/lifecycle.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::event::InstancePayloadType; use crate::event::emit::emit_instance; use crate::state::instances::adapters::sqlite::instance_rows; @@ -11,6 +12,7 @@ use std::path::Path; #[tracing::instrument] #[allow(clippy::too_many_arguments)] pub(crate) async fn create( + context: &OperationContext, name: String, game_version: String, modloader: ModLoader, @@ -20,6 +22,7 @@ pub(crate) async fn create( ) -> crate::Result { let state = State::get().await?; let instance = crate::state::create_instance( + context, CreateInstance { name, path: None, diff --git a/packages/app-lib/src/api/instance/projects.rs b/packages/app-lib/src/api/instance/projects.rs index 18c3c98e86..026468b87b 100644 --- a/packages/app-lib/src/api/instance/projects.rs +++ b/packages/app-lib/src/api/instance/projects.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::event::emit::{emit_instance, emit_loading, init_loading}; use crate::event::{InstancePayloadType, LoadingBarType}; use crate::state::instances::adapters::sqlite::instance_rows; @@ -20,6 +21,7 @@ pub struct InstallProjectWithDependenciesRequest { #[tracing::instrument] pub async fn update_all_projects( + context: &OperationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -34,6 +36,7 @@ pub async fn update_all_projects( ) .await?; let map = crate::state::instances::commands::update_all_projects( + context, instance_id, &state, ) @@ -46,12 +49,14 @@ pub async fn update_all_projects( #[tracing::instrument] pub async fn update_project( + context: &OperationContext, instance_id: &str, project_path: &str, skip_send_event: Option, ) -> crate::Result { let state = State::get().await?; let path = crate::state::instances::commands::update_project( + context, instance_id, project_path, &state, @@ -67,6 +72,7 @@ pub async fn update_project( #[tracing::instrument] pub async fn add_project_from_version( + context: &OperationContext, instance_id: &str, version_id: &str, reason: fetch::DownloadReason, @@ -75,6 +81,7 @@ pub async fn add_project_from_version( let state = State::get().await?; let project_path = crate::state::instances::commands::add_project_from_version( + context, instance_id, version_id, reason, @@ -90,6 +97,7 @@ pub async fn add_project_from_version( #[tracing::instrument] pub async fn install_project_with_dependencies( + context: &OperationContext, instance_id: &str, request: InstallProjectWithDependenciesRequest, ) -> crate::Result { @@ -98,6 +106,7 @@ pub async fn install_project_with_dependencies( crate::ErrorKind::InputError("Unknown instance".to_string()) })?; let plan = crate::state::instances::commands::resolve_install_plan( + context, instance_id, crate::state::instances::commands::InstanceInstallProjectRequest { project_id: request.project_id, @@ -112,8 +121,10 @@ pub async fn install_project_with_dependencies( let instance_id = metadata.instance.id; let project_ids = plan_project_ids(&plan); let install_plan = plan.clone(); + let context = context.clone(); tokio::spawn(async move { match crate::state::instances::commands::install_resolved_content_plan( + &context, &instance_id, &install_plan, &state, @@ -176,6 +187,7 @@ fn plan_project_ids(plan: &ResolveContentPlan) -> Vec { #[tracing::instrument] pub async fn switch_project_version_with_dependencies( + context: &OperationContext, instance_id: &str, project_path: &str, version_id: &str, @@ -186,6 +198,7 @@ pub async fn switch_project_version_with_dependencies( })?; let path = crate::state::instances::commands::switch_project_version_with_dependencies( + context, instance_id, project_path, version_id, @@ -214,10 +227,14 @@ pub async fn add_project_from_path( } #[tracing::instrument] -pub async fn is_file_on_modrinth(path: &Path) -> crate::Result { +pub async fn is_file_on_modrinth( + context: &OperationContext, + path: &Path, +) -> crate::Result { let state = State::get().await?; let (_, hash) = fetch::sha1_file_async(path).await?; let files = CachedEntry::get_file_many( + context, &[&hash], Some(CacheBehaviour::Bypass), &state.pool, @@ -266,6 +283,7 @@ pub async fn remove_project( #[tracing::instrument] pub async fn update_managed_modrinth_version( + context: &OperationContext, instance_id: &str, version_id: &str, ) -> crate::Result { @@ -310,6 +328,7 @@ pub async fn update_managed_modrinth_version( }; crate::install::install_pack_to_existing_instance( + context, metadata.instance.id, crate::api::pack::install_from::CreatePackLocation::FromVersionId { project_id, @@ -324,6 +343,7 @@ pub async fn update_managed_modrinth_version( #[tracing::instrument] pub async fn repair_managed_modrinth( + context: &OperationContext, instance_id: &str, ) -> crate::Result { let state = State::get().await?; @@ -363,6 +383,7 @@ pub async fn repair_managed_modrinth( }; crate::install::install_pack_to_existing_instance( + context, metadata.instance.id, crate::api::pack::install_from::CreatePackLocation::FromVersionId { project_id, diff --git a/packages/app-lib/src/api/instance/run.rs b/packages/app-lib/src/api/instance/run.rs index e4d1706de3..6ece6b9ea5 100644 --- a/packages/app-lib/src/api/instance/run.rs +++ b/packages/app-lib/src/api/instance/run.rs @@ -1,4 +1,5 @@ use super::content::get_projects; +use crate::OperationContext; use crate::server_address::ServerAddress; use crate::state::{ Credentials, InstanceLink, ProcessMetadata, Settings, State, @@ -20,6 +21,7 @@ pub enum QuickPlayType { #[tracing::instrument] pub async fn run( + operation_context: &OperationContext, instance_id: &str, quick_play_type: QuickPlayType, ) -> crate::Result { @@ -28,18 +30,25 @@ pub async fn run( .await? .ok_or_else(|| crate::ErrorKind::NoCredentialsError.as_error())?; - run_credentials(instance_id, &default_account, quick_play_type).await + run_credentials( + operation_context, + instance_id, + &default_account, + quick_play_type, + ) + .await } #[tracing::instrument(skip(credentials))] async fn run_credentials( + operation_context: &OperationContext, instance_id: &str, credentials: &Credentials, quick_play_type: QuickPlayType, ) -> crate::Result { let state = State::get().await?; let settings = Settings::get(&state.pool).await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( instance_id, &state.pool, @@ -51,7 +60,7 @@ async fn run_credentials( )) })?; - let pre_launch_hooks = context + let pre_launch_hooks = launch_context .launch_overrides .hooks .pre_launch @@ -72,7 +81,7 @@ async fn run_credentials( state .directories .instances_dir() - .join(&context.instance.path), + .join(&launch_context.instance.path), )?; let result = Command::new(command) .args(cmd) @@ -93,29 +102,32 @@ async fn run_credentials( } } - let java_args = context + let java_args = launch_context .launch_overrides .extra_launch_args .clone() .unwrap_or(settings.extra_launch_args); - let wrapper = context + let wrapper = launch_context .launch_overrides .hooks .wrapper .clone() .or(settings.hooks.wrapper) .filter(|hook_command| !hook_command.is_empty()); - let memory = context.launch_overrides.memory.unwrap_or(settings.memory); - let resolution = context + let memory = launch_context + .launch_overrides + .memory + .unwrap_or(settings.memory); + let resolution = launch_context .launch_overrides .game_resolution .unwrap_or(settings.game_resolution); - let env_args = context + let env_args = launch_context .launch_overrides .custom_env_vars .clone() .unwrap_or(settings.custom_env_vars); - let post_exit_hook = context + let post_exit_hook = launch_context .launch_overrides .hooks .post_exit @@ -124,13 +136,13 @@ async fn run_credentials( .filter(|hook_command| !hook_command.is_empty()); let mut mc_set_options: Vec<(String, String)> = vec![]; - if let Some(fullscreen) = context.launch_overrides.force_fullscreen { + if let Some(fullscreen) = launch_context.launch_overrides.force_fullscreen { mc_set_options.push(("fullscreen".to_string(), fullscreen.to_string())); } else if settings.force_fullscreen { mc_set_options.push(("fullscreen".to_string(), "true".to_string())); } - if let Some(project_id) = server_play_project_id(&context.link) + if let Some(project_id) = server_play_project_id(&launch_context.link) && !project_id.trim().is_empty() { let server_id = uuid::Uuid::new_v4().to_string(); @@ -148,6 +160,7 @@ async fn run_credentials( match join_result { Ok(resp) if resp.status().is_success() => { let result = fetch::post_json( + operation_context, concat!( env!("MODRINTH_API_BASE_URL"), "analytics/minecraft-server-play" @@ -181,6 +194,7 @@ async fn run_credentials( crate::minecraft_skins::flush_pending_skin_change().await?; crate::launcher::launch_minecraft( + operation_context, &java_args, &env_args, &mc_set_options, @@ -189,7 +203,7 @@ async fn run_credentials( &resolution, credentials, post_exit_hook, - &context, + &launch_context, quick_play_type, ) .await @@ -224,10 +238,11 @@ pub async fn kill(instance_id: &str) -> crate::Result<()> { #[tracing::instrument] pub async fn try_update_playtime_by_instance_id( + operation_context: &OperationContext, instance_id: &str, ) -> crate::Result<()> { let state = State::get().await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( instance_id, &state.pool, @@ -238,9 +253,9 @@ pub async fn try_update_playtime_by_instance_id( "Tried to update playtime for nonexistent instance {instance_id}!" )) })?; - let updated_recent_playtime = context.instance.recent_time_played; + let updated_recent_playtime = launch_context.instance.recent_time_played; let res = if updated_recent_playtime > 0 { - let modrinth_pack_version_id = match &context.link { + let modrinth_pack_version_id = match &launch_context.link { InstanceLink::ModrinthModpack { version_id, .. } | InstanceLink::ServerProjectModpack { content_version_id: version_id, @@ -258,13 +273,15 @@ pub async fn try_update_playtime_by_instance_id( }; let playtime_update_json = json!({ "seconds": updated_recent_playtime, - "loader": context.applied_content_set.loader.as_str(), - "game_version": &context.applied_content_set.game_version, + "loader": launch_context.applied_content_set.loader.as_str(), + "game_version": &launch_context.applied_content_set.game_version, "parent": modrinth_pack_version_id, }); let mut hashmap: HashMap = HashMap::new(); - for (_, project) in get_projects(instance_id, None).await? { + for (_, project) in + get_projects(operation_context, instance_id, None).await? + { if let Some(metadata) = project.metadata { hashmap .insert(metadata.version_id, playtime_update_json.clone()); @@ -272,6 +289,7 @@ pub async fn try_update_playtime_by_instance_id( } fetch::post_json( + operation_context, concat!(env!("MODRINTH_API_BASE_URL"), "analytics/playtime"), serde_json::to_value(hashmap)?, &state.api_semaphore, @@ -284,7 +302,7 @@ pub async fn try_update_playtime_by_instance_id( if res.is_ok() { crate::state::instances::commands::mark_instance_playtime_submitted( - &context.instance.id, + &launch_context.instance.id, updated_recent_playtime, &state.pool, ) diff --git a/packages/app-lib/src/api/jre.rs b/packages/app-lib/src/api/jre.rs index 4944512591..3de339ac59 100644 --- a/packages/app-lib/src/api/jre.rs +++ b/packages/app-lib/src/api/jre.rs @@ -19,7 +19,7 @@ use sysinfo::{MemoryRefreshKind, RefreshKind}; use crate::util::io; use crate::util::jre::extract_java_version; use crate::{ - LoadingBarType, State, + LoadingBarType, OperationContext, State, util::jre::{self}, }; @@ -59,22 +59,27 @@ pub async fn find_filtered_jres( }) } -pub async fn auto_install_java(java_version: u32) -> crate::Result { - auto_install_java_with_loading(java_version, true).await +pub async fn auto_install_java( + context: &OperationContext, + java_version: u32, +) -> crate::Result { + auto_install_java_with_loading(context, java_version, true).await } pub async fn auto_install_java_with_loading( + context: &OperationContext, java_version: u32, show_loading: bool, ) -> crate::Result { - auto_install_java_inner(java_version, show_loading, None).await + auto_install_java_inner(context, java_version, show_loading, None).await } pub async fn auto_install_java_with_reporter( + context: &OperationContext, java_version: u32, reporter: InstallProgressReporter, ) -> crate::Result { - auto_install_java_inner(java_version, false, Some(reporter)).await + auto_install_java_inner(context, java_version, false, Some(reporter)).await } const JAVA_INSTALL_STEPS: u64 = 4; @@ -111,6 +116,7 @@ fn java_step_progress(current: u64) -> InstallProgress { } async fn auto_install_java_inner( + context: &OperationContext, java_version: u32, show_loading: bool, reporter: Option, @@ -167,6 +173,7 @@ async fn auto_install_java_inner( .await?; } let packages = fetch_json::>( + context, Method::GET, &metadata_url, None, @@ -235,6 +242,7 @@ async fn auto_install_java_inner( }; fetch_advanced_with_progress( + context, Method::GET, &download.download_url, None, @@ -250,6 +258,7 @@ async fn auto_install_java_inner( .await? } else { fetch_advanced( + context, Method::GET, &download.download_url, None, diff --git a/packages/app-lib/src/api/metadata.rs b/packages/app-lib/src/api/metadata.rs index befad43b9f..58e0d236dd 100644 --- a/packages/app-lib/src/api/metadata.rs +++ b/packages/app-lib/src/api/metadata.rs @@ -1,12 +1,15 @@ -use crate::State; use crate::state::CachedEntry; +use crate::{OperationContext, State}; pub use daedalus::minecraft::VersionManifest; pub use daedalus::modded::Manifest; #[tracing::instrument] -pub async fn get_minecraft_versions() -> crate::Result { +pub async fn get_minecraft_versions( + context: &OperationContext, +) -> crate::Result { let state = State::get().await?; let minecraft_versions = CachedEntry::get_minecraft_manifest( + context, None, &state.pool, &state.api_semaphore, @@ -20,11 +23,15 @@ pub async fn get_minecraft_versions() -> crate::Result { } // #[tracing::instrument] -pub async fn get_loader_versions(loader: &str) -> crate::Result { +pub async fn get_loader_versions( + context: &OperationContext, + loader: &str, +) -> crate::Result { let state = State::get().await?; let cache_key = daedalus::modded::loader_manifest_metadata(loader).cache_key; let loaders = CachedEntry::get_loader_manifest( + context, &cache_key, None, &state.pool, diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs index 927a666aaf..67cbd81ed1 100644 --- a/packages/app-lib/src/api/mod.rs +++ b/packages/app-lib/src/api/mod.rs @@ -37,7 +37,7 @@ pub mod data { pub mod prelude { pub use crate::{ - State, + InvocationContext, OperationCause, OperationContext, State, data::*, event::CommandPayload, install, instance, jre, metadata, minecraft_auth, mr_auth, pack, diff --git a/packages/app-lib/src/api/mr_auth.rs b/packages/app-lib/src/api/mr_auth.rs index 42ce41fe5e..68ba3cc40f 100644 --- a/packages/app-lib/src/api/mr_auth.rs +++ b/packages/app-lib/src/api/mr_auth.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::ModrinthCredentials; #[tracing::instrument] @@ -7,11 +8,13 @@ pub fn authenticate_begin_flow() -> &'static str { #[tracing::instrument] pub async fn authenticate_finish_flow( + context: &OperationContext, code: &str, ) -> crate::Result { let state = crate::State::get().await?; let creds = crate::state::finish_login_flow( + context, code, &state.api_semaphore, &state.pool, @@ -21,7 +24,12 @@ pub async fn authenticate_finish_flow( creds.upsert(&state.pool).await?; state .friends_socket - .connect(&state.pool, &state.api_semaphore, &state.process_manager) + .connect( + context, + &state.pool, + &state.api_semaphore, + &state.process_manager, + ) .await?; Ok(creds) @@ -41,11 +49,16 @@ pub async fn logout() -> crate::Result<()> { } #[tracing::instrument] -pub async fn get_credentials() -> crate::Result> { +pub async fn get_credentials( + context: &OperationContext, +) -> crate::Result> { let state = crate::State::get().await?; - let current = - ModrinthCredentials::get_and_refresh(&state.pool, &state.api_semaphore) - .await?; + let current = ModrinthCredentials::get_and_refresh( + context, + &state.pool, + &state.api_semaphore, + ) + .await?; Ok(current) } diff --git a/packages/app-lib/src/api/pack/import/atlauncher.rs b/packages/app-lib/src/api/pack/import/atlauncher.rs index 1caf64f49e..2d525ace4d 100644 --- a/packages/app-lib/src/api/pack/import/atlauncher.rs +++ b/packages/app-lib/src/api/pack/import/atlauncher.rs @@ -125,6 +125,7 @@ pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { #[tracing::instrument] pub async fn import_atlauncher( + context: &crate::OperationContext, atlauncher_base_path: PathBuf, // path to base atlauncher folder instance_folder: String, // instance folder in atlauncher_base_path instance_id: &str, @@ -178,6 +179,7 @@ pub async fn import_atlauncher( let minecraft_folder = atlauncher_instance_path; import_atlauncher_unmanaged( + context, instance_id, minecraft_folder, backup_name, @@ -191,6 +193,7 @@ pub async fn import_atlauncher( } async fn import_atlauncher_unmanaged( + context: &crate::OperationContext, instance_id: &str, minecraft_folder: PathBuf, backup_name: String, @@ -214,6 +217,7 @@ async fn import_atlauncher_unmanaged( let loader_version = if mod_loader != ModLoader::Vanilla { crate::launcher::get_loader_version_from_profile( + context, &game_version, mod_loader, Some(&atinstance.launcher.loader_version.version), @@ -264,6 +268,7 @@ async fn import_atlauncher_unmanaged( // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) let state = State::get().await?; finish_import( + context, instance_id, minecraft_folder, &state.io_semaphore, diff --git a/packages/app-lib/src/api/pack/import/curseforge.rs b/packages/app-lib/src/api/pack/import/curseforge.rs index becc532407..428eacfc32 100644 --- a/packages/app-lib/src/api/pack/import/curseforge.rs +++ b/packages/app-lib/src/api/pack/import/curseforge.rs @@ -49,6 +49,7 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { } pub async fn import_curseforge( + context: &crate::OperationContext, curseforge_instance_folder: PathBuf, // instance's folder instance_id: &str, reporter: InstallProgressReporter, @@ -82,6 +83,7 @@ pub async fn import_curseforge( }) = minecraft_instance.installed_modpack.clone() { let icon_bytes = fetch( + context, &thumbnail_url, None, None, @@ -128,6 +130,7 @@ pub async fn import_curseforge( let loader_version = if mod_loader != ModLoader::Vanilla { crate::launcher::get_loader_version_from_profile( + context, &game_version, mod_loader, loader_version.as_deref(), @@ -188,6 +191,7 @@ pub async fn import_curseforge( // Copy in contained folders as overrides let state = State::get().await?; finish_import( + context, instance_id, curseforge_instance_folder, &state.io_semaphore, diff --git a/packages/app-lib/src/api/pack/import/gdlauncher.rs b/packages/app-lib/src/api/pack/import/gdlauncher.rs index 7a9f164242..34c2ae77aa 100644 --- a/packages/app-lib/src/api/pack/import/gdlauncher.rs +++ b/packages/app-lib/src/api/pack/import/gdlauncher.rs @@ -41,6 +41,7 @@ pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { } pub async fn import_gdlauncher( + context: &crate::OperationContext, gdlauncher_instance_folder: PathBuf, // instance's folder instance_id: &str, reporter: InstallProgressReporter, @@ -80,6 +81,7 @@ pub async fn import_gdlauncher( let loader_version = if mod_loader != ModLoader::Vanilla { crate::launcher::get_loader_version_from_profile( + context, &game_version, mod_loader, loader_version.as_deref(), @@ -116,6 +118,7 @@ pub async fn import_gdlauncher( // Copy in contained folders as overrides let state = State::get().await?; finish_import( + context, instance_id, gdlauncher_instance_folder, &state.io_semaphore, diff --git a/packages/app-lib/src/api/pack/import/mmc.rs b/packages/app-lib/src/api/pack/import/mmc.rs index 662971c366..446f17604d 100644 --- a/packages/app-lib/src/api/pack/import/mmc.rs +++ b/packages/app-lib/src/api/pack/import/mmc.rs @@ -177,6 +177,7 @@ async fn load_instance_cfg(file_path: &Path) -> crate::Result { // #[tracing::instrument] pub async fn import_mmc( + context: &crate::OperationContext, mmc_base_path: PathBuf, // path to base mmc folder instance_folder: String, // instance folder in mmc_base_path instance_id: &str, @@ -235,23 +236,24 @@ pub async fn import_mmc( // Modrinth Managed Pack // Kept separate as we may in the future want to add special handling for modrinth managed packs - import_mmc_unmanaged(instance_id, minecraft_folder, "Imported Modrinth Modpack".to_string(), description, mmc_pack, reporter, details).await?; + import_mmc_unmanaged(context, instance_id, minecraft_folder, "Imported Modrinth Modpack".to_string(), description, mmc_pack, reporter, details).await?; } Some(MMCManagedPackType::Flame | MMCManagedPackType::ATLauncher) => { // For flame/atlauncher managed packs // Treat as unmanaged, but with 'minecraft' folder instead of '.minecraft' - import_mmc_unmanaged(instance_id, minecraft_folder, "Imported Modpack".to_string(), description, mmc_pack, reporter, details).await?; + import_mmc_unmanaged(context, instance_id, minecraft_folder, "Imported Modpack".to_string(), description, mmc_pack, reporter, details).await?; }, Some(_) => { // For managed packs that aren't modrinth, flame, atlauncher // Treat as unmanaged - import_mmc_unmanaged(instance_id, minecraft_folder, "ImportedModpack".to_string(), description, mmc_pack, reporter, details).await?; + import_mmc_unmanaged(context, instance_id, minecraft_folder, "ImportedModpack".to_string(), description, mmc_pack, reporter, details).await?; }, _ => return Err(crate::ErrorKind::InputError("Instance is managed, but managed pack type not specified in instance.cfg".to_string()).into()) } } else { // Directly import unmanaged pack import_mmc_unmanaged( + context, instance_id, minecraft_folder, "Imported Modpack".to_string(), @@ -266,6 +268,7 @@ pub async fn import_mmc( } async fn import_mmc_unmanaged( + context: &crate::OperationContext, instance_id: &str, minecraft_folder: PathBuf, backup_name: String, @@ -315,6 +318,7 @@ async fn import_mmc_unmanaged( .collect(); install_from::set_instance_information( + context, instance_id.to_string(), &description, &backup_name, @@ -327,6 +331,7 @@ async fn import_mmc_unmanaged( // Moves .minecraft folder over (ie: overrides such as resourcepacks, mods, etc) let state = State::get().await?; finish_import( + context, instance_id, minecraft_folder, &state.io_semaphore, diff --git a/packages/app-lib/src/api/pack/import/mod.rs b/packages/app-lib/src/api/pack/import/mod.rs index 4a58cfcbf3..0fffedab49 100644 --- a/packages/app-lib/src/api/pack/import/mod.rs +++ b/packages/app-lib/src/api/pack/import/mod.rs @@ -115,6 +115,7 @@ pub async fn get_importable_instances( } pub(crate) async fn import_instance_with_reporter( + context: &crate::OperationContext, instance_id: &str, launcher_type: ImportLauncherType, base_path: PathBuf, @@ -122,6 +123,7 @@ pub(crate) async fn import_instance_with_reporter( reporter: InstallProgressReporter, ) -> crate::Result<()> { import_instance_inner( + context, instance_id, launcher_type, base_path, @@ -132,6 +134,7 @@ pub(crate) async fn import_instance_with_reporter( } async fn import_instance_inner( + context: &crate::OperationContext, instance_id: &str, launcher_type: ImportLauncherType, base_path: PathBuf, @@ -146,6 +149,7 @@ async fn import_instance_inner( let res = match launcher_type { ImportLauncherType::MultiMC | ImportLauncherType::PrismLauncher => { mmc::import_mmc( + context, base_path, // path to base mmc folder instance_folder, // instance folder in mmc_base_path instance_id, @@ -156,6 +160,7 @@ async fn import_instance_inner( } ImportLauncherType::ATLauncher => { atlauncher::import_atlauncher( + context, base_path, // path to atlauncher folder instance_folder, // instance folder in atlauncher instance_id, @@ -166,6 +171,7 @@ async fn import_instance_inner( } ImportLauncherType::GDLauncher => { gdlauncher::import_gdlauncher( + context, base_path.join("instances").join(instance_folder), // path to gdlauncher folder instance_id, reporter.clone(), @@ -175,6 +181,7 @@ async fn import_instance_inner( } ImportLauncherType::Curseforge => { curseforge::import_curseforge( + context, base_path.join("Instances").join(instance_folder), // path to curseforge folder instance_id, reporter.clone(), @@ -199,6 +206,7 @@ async fn import_instance_inner( { matched = true; Box::pin(import_instance_inner( + context, instance_id, lt, base_path, @@ -408,6 +416,7 @@ pub(crate) async fn copy_dotminecraft_with_reporter( } pub(crate) async fn finish_import( + context: &crate::OperationContext, instance_id: &str, dotminecraft: PathBuf, io_semaphore: &IoSemaphore, @@ -424,6 +433,7 @@ pub(crate) async fn finish_import( .await?; crate::launcher::install_minecraft_for_instance_id_with_reporter( + context, instance_id, false, Some(reporter), diff --git a/packages/app-lib/src/api/pack/install_from.rs b/packages/app-lib/src/api/pack/install_from.rs index e4a86d79f1..171f546116 100644 --- a/packages/app-lib/src/api/pack/install_from.rs +++ b/packages/app-lib/src/api/pack/install_from.rs @@ -164,6 +164,7 @@ pub struct CreatePackDescription { } pub async fn get_instance_from_pack( + context: &crate::OperationContext, location: CreatePackLocation, ) -> crate::Result { match location { @@ -194,6 +195,7 @@ pub async fn get_instance_from_pack( let state = State::get().await?; let (_, hash) = sha1_file_async(&path).await?; match CachedEntry::get_file_many( + context, &[&hash], Some(CacheBehaviour::StaleWhileRevalidateSkipOffline), &state.pool, @@ -217,6 +219,7 @@ pub async fn get_instance_from_pack( let external_files_in_modpack = super::install_mrpack::get_external_files_from_mrpack( + context, &CreatePackFile::Path(path), ) .await?; @@ -234,6 +237,7 @@ pub async fn get_instance_from_pack( #[tracing::instrument(skip(reporter))] #[allow(clippy::too_many_arguments)] pub(crate) async fn generate_pack_from_version_id_with_reporter( + context: &crate::OperationContext, project_id: String, version_id: String, title: String, @@ -246,6 +250,7 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter( let has_icon_url = icon_url.is_some(); let version = CachedEntry::get_version( + context, &version_id, Some(CacheBehaviour::Bypass), &state.pool, @@ -351,14 +356,15 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter( }; let progress = Some(&mut progress as &mut FetchProgressFn<'_>); - let context = InstallErrorContext::new("download modpack file") + let error_context = InstallErrorContext::new("download modpack file") .urls(vec![url.clone()]) .maybe_expected_hash(hash.cloned()) .project_id(project_id.clone()) .version_id(version_id.clone()) .build(); - reporter.set_context(context).await?; + reporter.set_context(error_context).await?; let file = fetch_advanced_with_progress( + context, Method::GET, &url, hash.map(|x| &**x), @@ -378,6 +384,7 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter( .await?; let project = CachedEntry::get_project( + context, &version.project_id, None, &state.pool, @@ -406,6 +413,7 @@ pub(crate) async fn generate_pack_from_version_id_with_reporter( ) .await?; let icon_bytes = fetch( + context, &icon_url, None, None, @@ -484,6 +492,7 @@ pub async fn generate_pack_from_file( /// Sets generated instance attributes to the pack ones. /// This includes the pack name, icon, game version, loader version, and loader pub async fn set_instance_information( + context: &crate::OperationContext, instance_id: String, description: &CreatePackDescription, backup_name: &str, @@ -527,6 +536,7 @@ pub async fn set_instance_information( let mod_loader = mod_loader.unwrap_or(ModLoader::Vanilla); let loader_version = if mod_loader != ModLoader::Vanilla { crate::launcher::get_loader_version_from_profile( + context, game_version, mod_loader, loader_version.cloned().as_deref(), diff --git a/packages/app-lib/src/api/pack/install_mrpack.rs b/packages/app-lib/src/api/pack/install_mrpack.rs index 9eea91b162..0eb8f7736e 100644 --- a/packages/app-lib/src/api/pack/install_mrpack.rs +++ b/packages/app-lib/src/api/pack/install_mrpack.rs @@ -238,6 +238,7 @@ where } pub(crate) async fn get_external_files_from_mrpack( + operation_context: &crate::OperationContext, file: &CreatePackFile, ) -> crate::Result> { let mut zip_reader = MrpackZipReader::new(file).await?; @@ -299,6 +300,7 @@ pub(crate) async fn get_external_files_from_mrpack( .map(|(_, hash)| hash.as_str()) .collect::>(); let recognized_hashes = match CachedEntry::get_file_many( + operation_context, &hashes, None, &state.pool, @@ -396,6 +398,7 @@ where } pub(crate) async fn install_zipped_mrpack_files_with_reporter( + operation_context: &crate::OperationContext, create_pack: CreatePack, ignore_lock: bool, reason: DownloadReason, @@ -565,6 +568,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( } set_instance_information( + operation_context, instance_id.clone(), &description, &pack.name, @@ -629,6 +633,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( .collect::>(); let file_infos_by_hash = Arc::new( CachedEntry::get_file_many( + operation_context, &file_info_hashes, None, &state.pool, @@ -764,6 +769,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( let progress = &mut report_download_progress as &mut FetchProgressFn<'_>; let file = match fetch_mirrors_with_progress( + operation_context, &project .downloads .iter() @@ -1060,6 +1066,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( } crate::launcher::install_minecraft_for_instance_id_with_reporter( + operation_context, &instance_id, false, Some(reporter.clone()), @@ -1088,6 +1095,7 @@ fn modpack_source_kind(version_id: Option<&str>) -> ContentSourceKind { #[tracing::instrument(skip(mrpack_file))] pub async fn remove_all_related_files( + operation_context: &crate::OperationContext, instance_id: String, mrpack_file: CreatePackFile, ) -> crate::Result<()> { @@ -1145,6 +1153,7 @@ pub async fn remove_all_related_files( // First, get project info by hash let file_infos = CachedEntry::get_file_many( + operation_context, &all_hashes.iter().map(|x| &**x).collect::>(), None, &state.pool, diff --git a/packages/app-lib/src/api/tags.rs b/packages/app-lib/src/api/tags.rs index f43ad0a0b0..f1560589ea 100644 --- a/packages/app-lib/src/api/tags.rs +++ b/packages/app-lib/src/api/tags.rs @@ -1,72 +1,94 @@ //! Theseus tag management interface use crate::state::CachedEntry; pub use crate::{ - State, + OperationContext, State, state::{Category, DonationPlatform, GameVersion, Loader}, }; /// Get category tags #[tracing::instrument] -pub async fn get_category_tags() -> crate::Result> { +pub async fn get_category_tags( + context: &OperationContext, +) -> crate::Result> { let state = State::get().await?; - let categories = - CachedEntry::get_categories(None, &state.pool, &state.api_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::NoValueFor("category tags".to_string()) - })?; + let categories = CachedEntry::get_categories( + context, + None, + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| crate::ErrorKind::NoValueFor("category tags".to_string()))?; Ok(categories) } /// Get report type tags #[tracing::instrument] -pub async fn get_report_type_tags() -> crate::Result> { +pub async fn get_report_type_tags( + context: &OperationContext, +) -> crate::Result> { let state = State::get().await?; - let report_types = - CachedEntry::get_report_types(None, &state.pool, &state.api_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::NoValueFor("report type tags".to_string()) - })?; + let report_types = CachedEntry::get_report_types( + context, + None, + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| { + crate::ErrorKind::NoValueFor("report type tags".to_string()) + })?; Ok(report_types) } /// Get loader tags #[tracing::instrument] -pub async fn get_loader_tags() -> crate::Result> { +pub async fn get_loader_tags( + context: &OperationContext, +) -> crate::Result> { let state = State::get().await?; - let loaders = - CachedEntry::get_loaders(None, &state.pool, &state.api_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::NoValueFor("loader tags".to_string()) - })?; + let loaders = CachedEntry::get_loaders( + context, + None, + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| crate::ErrorKind::NoValueFor("loader tags".to_string()))?; Ok(loaders) } /// Get game version tags #[tracing::instrument] -pub async fn get_game_version_tags() -> crate::Result> { +pub async fn get_game_version_tags( + context: &OperationContext, +) -> crate::Result> { let state = State::get().await?; - let game_versions = - CachedEntry::get_game_versions(None, &state.pool, &state.api_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::NoValueFor("game version tags".to_string()) - })?; + let game_versions = CachedEntry::get_game_versions( + context, + None, + &state.pool, + &state.api_semaphore, + ) + .await? + .ok_or_else(|| { + crate::ErrorKind::NoValueFor("game version tags".to_string()) + })?; Ok(game_versions) } /// Get donation platform tags #[tracing::instrument] -pub async fn get_donation_platform_tags() -> crate::Result> -{ +pub async fn get_donation_platform_tags( + context: &OperationContext, +) -> crate::Result> { let state = State::get().await?; let donation_platforms = CachedEntry::get_donation_platforms( + context, None, &state.pool, &state.api_semaphore, diff --git a/packages/app-lib/src/api/worlds.rs b/packages/app-lib/src/api/worlds.rs index 03efec7036..2b58d6162c 100644 --- a/packages/app-lib/src/api/worlds.rs +++ b/packages/app-lib/src/api/worlds.rs @@ -923,6 +923,7 @@ mod servers_data { } pub async fn get_instance_protocol_version( + operation_context: &crate::OperationContext, instance_id: &str, ) -> Result> { let metadata = @@ -951,6 +952,7 @@ pub async fn get_instance_protocol_version( let state = State::get().await?; let (minecraft, version_index) = crate::launcher::resolve_minecraft_manifest( + operation_context, &metadata.applied_content_set.game_version, &state, ) @@ -958,6 +960,7 @@ pub async fn get_instance_protocol_version( let version = &minecraft.versions[version_index]; let loader_version = get_loader_version_from_profile( + operation_context, &metadata.applied_content_set.game_version, metadata.applied_content_set.loader, metadata.applied_content_set.loader_version.as_deref(), diff --git a/packages/app-lib/src/install/runner.rs b/packages/app-lib/src/install/runner.rs index 4dee0cdaeb..6e4e854b31 100644 --- a/packages/app-lib/src/install/runner.rs +++ b/packages/app-lib/src/install/runner.rs @@ -6,7 +6,6 @@ use super::model::{ InstallRequest, InstallRollbackState, InstallTarget, }; use super::{diagnostics, recovery, store}; -use crate::ErrorKind; use crate::api::pack::install_from::{ CreatePackLocation, generate_pack_from_file, generate_pack_from_version_id_with_reporter, get_instance_from_pack, @@ -19,11 +18,13 @@ use crate::state::{ ContentSourceKind, InstanceInstallStage, InstanceLink, ModLoader, State, }; use crate::util::fetch::DownloadReason; +use crate::{ErrorKind, OperationContext}; use std::collections::HashSet; use std::path::PathBuf; use uuid::Uuid; pub async fn create_instance( + context: &OperationContext, name: String, game_version: String, loader: ModLoader, @@ -31,64 +32,89 @@ pub async fn create_instance( icon_path: Option, link: InstanceLink, ) -> crate::Result { - start(InstallRequest::CreateInstance { - name, - game_version, - loader, - loader_version, - icon_path, - link, - }) + start( + context, + InstallRequest::CreateInstance { + name, + game_version, + loader, + loader_version, + icon_path, + link, + }, + ) .await } pub async fn create_modpack_instance( + context: &OperationContext, location: CreatePackLocation, post_install_edit: Option, ) -> crate::Result { - start(InstallRequest::CreateModpackInstance { - location, - post_install_edit, - }) + start( + context, + InstallRequest::CreateModpackInstance { + location, + post_install_edit, + }, + ) .await } pub async fn import_instance( + context: &OperationContext, launcher_type: crate::api::pack::import::ImportLauncherType, base_path: PathBuf, instance_folder: String, ) -> crate::Result { - start(InstallRequest::ImportInstance { - launcher_type, - base_path, - instance_folder, - }) + start( + context, + InstallRequest::ImportInstance { + launcher_type, + base_path, + instance_folder, + }, + ) .await } pub async fn duplicate_instance( + context: &OperationContext, source_instance_id: String, ) -> crate::Result { - start(InstallRequest::DuplicateInstance { source_instance_id }).await + start( + context, + InstallRequest::DuplicateInstance { source_instance_id }, + ) + .await } pub async fn install_existing_instance( + context: &OperationContext, instance_id: String, force: bool, ) -> crate::Result { - start(InstallRequest::InstallExistingInstance { instance_id, force }).await + start( + context, + InstallRequest::InstallExistingInstance { instance_id, force }, + ) + .await } pub async fn install_pack_to_existing_instance( + context: &OperationContext, instance_id: String, location: CreatePackLocation, post_install_edit: Option, ) -> crate::Result { - start(InstallRequest::InstallPackToExistingInstance { - instance_id, - location, - post_install_edit, - }) + start( + context, + InstallRequest::InstallPackToExistingInstance { + instance_id, + location, + post_install_edit, + }, + ) .await } @@ -114,7 +140,10 @@ pub async fn job_support_details(job_id: Uuid) -> crate::Result { diagnostics::build_job_support_details(&job, &state).await } -pub async fn retry_job(job_id: Uuid) -> crate::Result { +pub async fn retry_job( + context: &OperationContext, + job_id: Uuid, +) -> crate::Result { let state = State::get().await?; let mut job = store::get_required(job_id, &state).await?; @@ -138,7 +167,7 @@ pub async fn retry_job(job_id: Uuid) -> crate::Result { job.state.progress.phase = InstallPhaseId::PreparingInstance; job.state.progress.progress = None; job.state.progress.details = InstallPhaseDetails::Empty; - prepare_initial_instance(&mut job.state, &state).await?; + prepare_initial_instance(context, &mut job.state, &state).await?; job.state.record_event(InstallJobEventKind::JobQueued { kind: job.state.request.kind(), }); @@ -151,7 +180,7 @@ pub async fn retry_job(job_id: Uuid) -> crate::Result { ) .await?; emit_install_job(&record.snapshot()).await?; - spawn_job(job_id); + spawn_job(job_id, context.clone()); Ok(record.snapshot()) } @@ -214,19 +243,23 @@ pub async fn dismiss_job(job_id: Uuid) -> crate::Result<()> { store::dismiss(job_id, &state).await } -async fn start(request: InstallRequest) -> crate::Result { +async fn start( + context: &OperationContext, + request: InstallRequest, +) -> crate::Result { let state = State::get().await?; let id = Uuid::new_v4(); let mut job_state = InstallJobState::new(request); - prepare_initial_instance(&mut job_state, &state).await?; + prepare_initial_instance(context, &mut job_state, &state).await?; let record = store::insert(id, &job_state, InstallJobStatus::Queued, &state).await?; emit_install_job(&record.snapshot()).await?; - spawn_job(id); + spawn_job(id, context.clone()); Ok(record.snapshot()) } async fn prepare_initial_instance( + context: &OperationContext, job_state: &mut InstallJobState, state: &State, ) -> crate::Result<()> { @@ -240,6 +273,7 @@ async fn prepare_initial_instance( link, } => { let metadata = crate::api::instance::create( + context, name, game_version, loader, @@ -259,7 +293,7 @@ async fn prepare_initial_instance( location, post_install_edit, } => { - let preview = get_instance_from_pack(location).await?; + let preview = get_instance_from_pack(context, location).await?; let name = post_install_edit .as_ref() .and_then(|edit| edit.name.clone()) @@ -281,6 +315,7 @@ async fn prepare_initial_instance( .or_else(|| preview.link.clone()) .unwrap_or(InstanceLink::Unmanaged); let metadata = crate::api::instance::create( + context, name, preview.game_version, preview.modloader, @@ -300,6 +335,7 @@ async fn prepare_initial_instance( instance_folder, .. } => { let metadata = crate::api::instance::create( + context, instance_folder, "1.19.4".to_string(), ModLoader::Vanilla, @@ -325,6 +361,7 @@ async fn prepare_initial_instance( ) })?; let created = crate::api::instance::create( + context, metadata.instance.name, metadata.applied_content_set.game_version, metadata.applied_content_set.loader, @@ -351,9 +388,9 @@ async fn prepare_initial_instance( Ok(()) } -fn spawn_job(job_id: Uuid) { +fn spawn_job(job_id: Uuid, context: OperationContext) { tokio::spawn(async move { - if let Err(error) = run_job(job_id).await { + if let Err(error) = run_job(&context, job_id).await { tracing::error!( "Install job {job_id} failed to update state: {error}" ); @@ -361,7 +398,10 @@ fn spawn_job(job_id: Uuid) { }); } -async fn run_job(job_id: Uuid) -> crate::Result<()> { +async fn run_job( + context: &OperationContext, + job_id: Uuid, +) -> crate::Result<()> { let state = State::get().await?; let mut job = store::get_required(job_id, &state).await?; @@ -387,7 +427,7 @@ async fn run_job(job_id: Uuid) -> crate::Result<()> { .await?; emit_install_job(&record.snapshot()).await?; - let result = run_request(job_id, &mut job_state, &state).await; + let result = run_request(context, job_id, &mut job_state, &state).await; if let Ok(record) = store::get_required(job_id, &state).await { job_state = record.state; } @@ -468,6 +508,7 @@ async fn run_job(job_id: Uuid) -> crate::Result<()> { } async fn run_request( + context: &OperationContext, job_id: Uuid, job_state: &mut InstallJobState, state: &State, @@ -506,7 +547,7 @@ async fn run_request( }, ) .await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( &instance_id, &state.pool, @@ -516,7 +557,8 @@ async fn run_request( crate::ErrorKind::InputError("Unknown instance".to_string()) })?; crate::launcher::install_minecraft_with_reporter( - &context, + context, + &launch_context, false, Some(InstallProgressReporter::new(job_id, job_state.clone())), ) @@ -542,6 +584,7 @@ async fn run_request( ) .await?; install_pack( + context, job_id, job_state, location, @@ -575,6 +618,7 @@ async fn run_request( ) .await?; crate::api::pack::import::import_instance_with_reporter( + context, &instance_id, launcher_type, base_path, @@ -609,7 +653,7 @@ async fn run_request( InstallPhaseDetails::Empty, ) .await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( &instance_id, &state.pool, @@ -619,7 +663,8 @@ async fn run_request( crate::ErrorKind::InputError("Unknown instance".to_string()) })?; crate::launcher::install_minecraft_with_reporter( - &context, + context, + &launch_context, false, Some(InstallProgressReporter::new(job_id, job_state.clone())), ) @@ -637,7 +682,7 @@ async fn run_request( InstallPhaseDetails::Empty, ) .await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( &instance_id, &state.pool, @@ -647,7 +692,8 @@ async fn run_request( crate::ErrorKind::InputError("Unknown instance".to_string()) })?; crate::launcher::install_minecraft_with_reporter( - &context, + context, + &launch_context, force, Some(InstallProgressReporter::new(job_id, job_state.clone())), ) @@ -661,6 +707,7 @@ async fn run_request( } => { prepare_existing_rollback(job_state, state, &instance_id).await?; let disabled_project_ids = remove_existing_pack_content( + context, job_id, job_state, state, @@ -668,6 +715,7 @@ async fn run_request( ) .await?; install_pack( + context, job_id, job_state, location, @@ -715,6 +763,7 @@ async fn apply_post_install_edit( } async fn remove_existing_pack_content( + context: &OperationContext, job_id: Uuid, job_state: &InstallJobState, state: &State, @@ -761,6 +810,7 @@ async fn remove_existing_pack_content( .collect::>(); let reporter = InstallProgressReporter::new(job_id, job_state.clone()); let old_pack = generate_pack_from_version_id_with_reporter( + context, project_id.clone(), version_id.clone(), metadata.instance.name.clone(), @@ -772,6 +822,7 @@ async fn remove_existing_pack_content( .await?; crate::api::pack::install_mrpack::remove_all_related_files( + context, instance_id.to_string(), old_pack.file, ) @@ -871,6 +922,7 @@ async fn restore_disabled_projects( } async fn install_pack( + context: &OperationContext, job_id: Uuid, job_state: &mut InstallJobState, location: CreatePackLocation, @@ -902,6 +954,7 @@ async fn install_pack( ) .await?; generate_pack_from_version_id_with_reporter( + context, project_id, version_id, title, @@ -925,6 +978,7 @@ async fn install_pack( }; install_zipped_mrpack_files_with_reporter( + context, create_pack, false, reason, diff --git a/packages/app-lib/src/launcher/download.rs b/packages/app-lib/src/launcher/download.rs index acc00c3f73..acb0a0ac0b 100644 --- a/packages/app-lib/src/launcher/download.rs +++ b/packages/app-lib/src/launcher/download.rs @@ -7,6 +7,7 @@ use crate::install::{ use crate::instance::QuickPlayType; use crate::launcher::parse_rules; use crate::{ + OperationContext, event::{ LoadingBarId, emit::{emit_loading, loading_try_for_each_concurrent}, @@ -142,24 +143,33 @@ impl MinecraftDownloadProgress { } async fn fetch_minecraft_file( + operation_context: &OperationContext, st: &State, url: &str, sha1: Option<&str>, expected_size: Option, progress: Option, - context: InstallErrorContext, + error_context: InstallErrorContext, ) -> crate::Result { - let mut context = context; - context.urls.push(url.to_string()); - context.expected_hash = sha1.map(str::to_string); - context.expected_size = expected_size; + let mut error_context = error_context; + error_context.urls.push(url.to_string()); + error_context.expected_hash = sha1.map(str::to_string); + error_context.expected_size = expected_size; if let Some(progress) = &progress { - progress.set_context(context.clone()).await?; + progress.set_context(error_context.clone()).await?; } let Some(progress) = progress else { - return fetch(url, sha1, None, None, &st.fetch_semaphore, &st.pool) - .await; + return fetch( + operation_context, + url, + sha1, + None, + None, + &st.fetch_semaphore, + &st.pool, + ) + .await; }; let last_downloaded = Arc::new(AtomicU64::new(0)); @@ -178,6 +188,7 @@ async fn fetch_minecraft_file( }; let bytes = match fetch_advanced_with_progress( + operation_context, Method::GET, url, sha1, @@ -194,7 +205,7 @@ async fn fetch_minecraft_file( { Ok(bytes) => bytes, Err(error) => { - progress.persist_failure_context(context).await; + progress.persist_failure_context(error_context).await; return Err(error); } }; @@ -384,6 +395,7 @@ fn missing_initial_minecraft_bytes( #[tracing::instrument(skip(st, version))] pub async fn download_minecraft( + context: &OperationContext, st: &State, version: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -415,6 +427,7 @@ pub async fn download_minecraft( // 5 let assets_index = download_assets_index( + context, st, version, loading_bar, @@ -441,10 +454,10 @@ pub async fn download_minecraft( tokio::try_join! { // Total loading sums to 90/60 - download_client(st, version, loading_bar, force, progress.clone()), // 9 - download_log_config(st, version, loading_bar, force, progress.clone()), - download_assets(st, version.assets == "legacy", &assets_index, loading_bar, amount, force, progress.clone()), // 40 - download_libraries(st, version.libraries.as_slice(), &version.id, loading_bar, amount, java_arch, force, minecraft_updated, progress.clone()) // 40 + download_client(context, st, version, loading_bar, force, progress.clone()), // 9 + download_log_config(context, st, version, loading_bar, force, progress.clone()), + download_assets(context, st, version.assets == "legacy", &assets_index, loading_bar, amount, force, progress.clone()), // 40 + download_libraries(context, st, version.libraries.as_slice(), &version.id, loading_bar, amount, java_arch, force, minecraft_updated, progress.clone()) // 40 }?; tracing::info!("Done downloading Minecraft!"); @@ -454,6 +467,7 @@ pub async fn download_minecraft( #[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))] pub async fn download_version_info( + context: &OperationContext, st: &State, version: &GameVersion, loader: Option<&LoaderVersion>, @@ -494,6 +508,7 @@ pub async fn download_version_info( .await?; } let mut info = fetch_json( + context, Method::GET, &version.url, None, @@ -519,6 +534,7 @@ pub async fn download_version_info( .await?; } let partial: d::modded::PartialVersionInfo = fetch_json( + context, Method::GET, &loader.url, None, @@ -548,6 +564,7 @@ pub async fn download_version_info( #[tracing::instrument(skip_all)] pub async fn download_client( + context: &OperationContext, st: &State, version_info: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -572,6 +589,7 @@ pub async fn download_client( if !path.exists() || force { let bytes = fetch_minecraft_file( + context, st, &client_download.url, Some(&client_download.sha1), @@ -598,6 +616,7 @@ pub async fn download_client( #[tracing::instrument(skip_all)] pub async fn download_assets_index( + context: &OperationContext, st: &State, version: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -617,6 +636,7 @@ pub async fn download_assets_index( .and_then(|ref it| Ok(serde_json::from_slice(it)?)) } else { let index = fetch_minecraft_file( + context, st, &version.asset_index.url, None, @@ -645,6 +665,7 @@ pub async fn download_assets_index( #[tracing::instrument(skip(st, index))] pub async fn download_assets( + context: &OperationContext, st: &State, with_legacy: bool, index: &AssetsIndex, @@ -691,6 +712,7 @@ pub async fn download_assets( if should_fetch_object { let resource = fetch_cell .get_or_try_init(|| fetch_minecraft_file( + context, st, &url, Some(hash), @@ -711,6 +733,7 @@ pub async fn download_assets( if should_fetch_legacy { let resource = fetch_cell .get_or_try_init(|| fetch_minecraft_file( + context, st, &url, Some(hash), @@ -740,6 +763,7 @@ pub async fn download_assets( #[tracing::instrument(skip(st, libraries))] #[allow(clippy::too_many_arguments)] pub async fn download_libraries( + context: &OperationContext, st: &State, libraries: &[Library], version: &str, @@ -796,6 +820,7 @@ pub async fn download_libraries( if let Some(native) = classifiers.get(&parsed_key) { let data = fetch_minecraft_file( + context, st, &native.url, Some(&native.sha1), @@ -851,6 +876,7 @@ pub async fn download_libraries( && !artifact.url.is_empty() { let bytes = fetch_minecraft_file( + context, st, &artifact.url, Some(&artifact.sha1), @@ -896,6 +922,7 @@ pub async fn download_libraries( // // See DEV-479. match fetch( + context, &url, None, None, @@ -938,6 +965,7 @@ pub async fn download_libraries( #[tracing::instrument(skip_all)] pub async fn download_log_config( + context: &OperationContext, st: &State, version_info: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -962,6 +990,7 @@ pub async fn download_log_config( if !path.exists() || force { let bytes = fetch_minecraft_file( + context, st, &log_download.url, Some(&log_download.sha1), diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index d178335cfd..fc3967a120 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -156,6 +156,7 @@ pub async fn get_java_version_from_launch_context( } pub async fn get_loader_version_from_profile( + operation_context: &crate::OperationContext, game_version: &str, loader: ModLoader, loader_version: Option<&str>, @@ -172,8 +173,11 @@ pub async fn get_loader_version_from_profile( id => it.id == *id, }; - let versions = - crate::api::metadata::get_loader_versions(loader.as_meta_str()).await?; + let versions = crate::api::metadata::get_loader_versions( + operation_context, + loader.as_meta_str(), + ) + .await?; if let Some(loaders) = loader_versions_for_game_version(&versions, game_version) @@ -218,10 +222,12 @@ fn loader_versions_for_game_version<'a>( /// game version. If the version isn't found in the cache, forces a manifest /// refresh to pick up newly-released versions. pub async fn resolve_minecraft_manifest( + operation_context: &crate::OperationContext, game_version: &str, state: &State, ) -> crate::Result<(d::minecraft::VersionManifest, usize)> { - let minecraft = crate::api::metadata::get_minecraft_versions().await?; + let minecraft = + crate::api::metadata::get_minecraft_versions(operation_context).await?; if let Some(idx) = minecraft .versions @@ -234,6 +240,7 @@ pub async fn resolve_minecraft_manifest( // Version not found in cache — force a manifest refresh in case it was // released after the cache was populated. let refreshed = crate::state::CachedEntry::get_minecraft_manifest( + operation_context, Some(crate::state::CacheBehaviour::MustRevalidate), &state.pool, &state.api_semaphore, @@ -262,12 +269,13 @@ async fn get_instance_full_path(instance_path: &str) -> crate::Result { } pub async fn install_minecraft_with_reporter( - context: &InstanceLaunchContext, + operation_context: &crate::OperationContext, + launch_context: &InstanceLaunchContext, repairing: bool, reporter: Option, ) -> crate::Result<()> { - let instance = &context.instance; - let content_set = &context.applied_content_set; + let instance = &launch_context.instance; + let content_set = &launch_context.applied_content_set; let phase_details = InstallPhaseDetails::Minecraft { game_version: content_set.game_version.clone(), loader: content_set.loader, @@ -309,8 +317,12 @@ pub async fn install_minecraft_with_reporter( ) .await?; } - let (minecraft, version_index) = - resolve_minecraft_manifest(&content_set.game_version, &state).await?; + let (minecraft, version_index) = resolve_minecraft_manifest( + operation_context, + &content_set.game_version, + &state, + ) + .await?; let version = &minecraft.versions[version_index]; let minecraft_updated = version_index <= minecraft @@ -332,6 +344,7 @@ pub async fn install_minecraft_with_reporter( } let mut loader_version = get_loader_version_from_profile( + operation_context, &content_set.game_version, content_set.loader, content_set.loader_version.as_deref(), @@ -341,6 +354,7 @@ pub async fn install_minecraft_with_reporter( // If no loader version is selected, try to select the stable version! if content_set.loader != ModLoader::Vanilla && loader_version.is_none() { loader_version = get_loader_version_from_profile( + operation_context, &content_set.game_version, content_set.loader, Some("stable"), @@ -362,6 +376,7 @@ pub async fn install_minecraft_with_reporter( // Download version info (5) let mut version_info = download::download_version_info( + operation_context, &state, version, loader_version.as_ref(), @@ -392,18 +407,25 @@ pub async fn install_minecraft_with_reporter( .await?; } let (java_version, set_java) = if let Some(java_version) = - get_java_version_from_launch_context(context, &version_info).await? + get_java_version_from_launch_context(launch_context, &version_info) + .await? { (std::path::PathBuf::from(java_version.path), false) } else { let path = if let Some(reporter) = &reporter { crate::api::jre::auto_install_java_with_reporter( + operation_context, key, reporter.clone(), ) .await? } else { - crate::api::jre::auto_install_java_with_loading(key, true).await? + crate::api::jre::auto_install_java_with_loading( + operation_context, + key, + true, + ) + .await? }; (path, true) @@ -443,6 +465,7 @@ pub async fn install_minecraft_with_reporter( .await?; } download::download_minecraft( + operation_context, &state, &version_info, loading_bar.as_ref(), @@ -622,12 +645,13 @@ pub async fn install_minecraft_with_reporter( } pub async fn install_minecraft_for_instance_id_with_reporter( + operation_context: &crate::OperationContext, instance_id: &str, repairing: bool, reporter: Option, ) -> crate::Result<()> { let state = State::get().await?; - let context = + let launch_context = crate::state::instances::commands::get_instance_launch_context( instance_id, &state.pool, @@ -639,7 +663,13 @@ pub async fn install_minecraft_for_instance_id_with_reporter( )) })?; - install_minecraft_with_reporter(&context, repairing, reporter).await + install_minecraft_with_reporter( + operation_context, + &launch_context, + repairing, + reporter, + ) + .await } pub async fn read_protocol_version_from_jar( @@ -698,6 +728,7 @@ fn link_project_and_version( #[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn launch_minecraft( + operation_context: &crate::OperationContext, java_args: &[String], env_args: &[(String, String)], mc_set_options: &[(String, String)], @@ -706,11 +737,11 @@ pub async fn launch_minecraft( resolution: &WindowSize, credentials: &Credentials, post_exit_hook: Option, - context: &InstanceLaunchContext, + launch_context: &InstanceLaunchContext, mut quick_play_type: QuickPlayType, ) -> crate::Result { - let instance = &context.instance; - let content_set = &context.applied_content_set; + let instance = &launch_context.instance; + let content_set = &launch_context.applied_content_set; if instance.install_stage == InstanceInstallStage::PackInstalling || instance.install_stage == InstanceInstallStage::MinecraftInstalling @@ -732,8 +763,12 @@ pub async fn launch_minecraft( let instance_path = get_instance_full_path(&instance.path).await?; - let (minecraft, version_index) = - resolve_minecraft_manifest(&content_set.game_version, &state).await?; + let (minecraft, version_index) = resolve_minecraft_manifest( + operation_context, + &content_set.game_version, + &state, + ) + .await?; let version = &minecraft.versions[version_index]; let minecraft_updated = version_index <= minecraft @@ -743,6 +778,7 @@ pub async fn launch_minecraft( .unwrap_or(0); let loader_version = get_loader_version_from_profile( + operation_context, &content_set.game_version, content_set.loader, content_set.loader_version.as_deref(), @@ -763,6 +799,7 @@ pub async fn launch_minecraft( }); let mut version_info = download::download_version_info( + operation_context, &state, version, loader_version.as_ref(), @@ -780,6 +817,7 @@ pub async fn launch_minecraft( .unwrap_or(0); if requires_logging_info { version_info = download::download_version_info( + operation_context, &state, version, loader_version.as_ref(), @@ -791,11 +829,18 @@ pub async fn launch_minecraft( } } - let _ = - download_log_config(&state, &version_info, None, false, None).await?; + let _ = download_log_config( + operation_context, + &state, + &version_info, + None, + false, + None, + ) + .await?; let java_version = - get_java_version_from_launch_context(context, &version_info) + get_java_version_from_launch_context(launch_context, &version_info) .await? .ok_or_else(|| { crate::ErrorKind::LauncherError( @@ -1048,6 +1093,7 @@ pub async fn launch_minecraft( state .process_manager .insert_new_process( + operation_context, &instance.id, &instance.path, &instance.name, @@ -1062,7 +1108,7 @@ pub async fn launch_minecraft( let instance_created_time = instance.created.to_rfc3339(); let instance_modified_time = instance.modified.to_rfc3339(); let (link_project_id, link_version_id) = - link_project_and_version(&context.link); + link_project_and_version(&launch_context.link); let system_properties = [ ("modrinth.process.startTime", Some(&process_start_time)), ("modrinth.profile.created", Some(&instance_created_time)), diff --git a/packages/app-lib/src/lib.rs b/packages/app-lib/src/lib.rs index 79b0e997a6..564010617c 100644 --- a/packages/app-lib/src/lib.rs +++ b/packages/app-lib/src/lib.rs @@ -16,6 +16,7 @@ mod event; pub mod install; mod launcher; mod logger; +mod operation; mod state; pub use api::*; @@ -25,6 +26,9 @@ pub use event::{ emit::init_loading, }; pub use logger::start_logger; +pub use operation::{ + InvocationContext, OperationCause, OperationContext, REQUEST_CONTEXT_HEADER, +}; pub use state::State; pub use util::fetch::DownloadReason; diff --git a/packages/app-lib/src/operation.rs b/packages/app-lib/src/operation.rs new file mode 100644 index 0000000000..98ab29aa6d --- /dev/null +++ b/packages/app-lib/src/operation.rs @@ -0,0 +1,293 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use uuid::Uuid; + +pub const REQUEST_CONTEXT_HEADER: &str = "Modrinth-App-Request-Context"; + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum OperationCause { + #[serde(rename = "app/startup")] + AppStartup, + #[serde(rename = "navigation/home")] + NavigationHome, + #[serde(rename = "navigation/library")] + NavigationLibrary, + #[serde(rename = "navigation/browse")] + NavigationBrowse, + #[serde(rename = "navigation/project")] + NavigationProject, + #[serde(rename = "navigation/instance/overview")] + NavigationInstanceOverview, + #[serde(rename = "navigation/instance/content")] + NavigationInstanceContent, + #[serde(rename = "navigation/instance/logs")] + NavigationInstanceLogs, + #[serde(rename = "navigation/servers")] + NavigationServers, + #[serde(rename = "navigation/server/manage")] + NavigationServerManage, + #[serde(rename = "navigation/server/content")] + NavigationServerContent, + #[serde(rename = "instance/refresh/user")] + InstanceRefreshUser, + #[serde(rename = "instance/refresh/filesystem_watch")] + InstanceRefreshFilesystemWatch, + #[serde(rename = "instance/update/all")] + InstanceUpdateAll, + #[serde(rename = "instance/update/single")] + InstanceUpdateSingle, + #[serde(rename = "instance/install")] + InstanceInstall, + #[serde(rename = "cache/revalidate")] + CacheRevalidate, + #[serde(rename = "auth/session_refresh")] + AuthSessionRefresh, + #[serde(rename = "background/friends")] + BackgroundFriends, + #[serde(rename = "minecraft/launch")] + MinecraftLaunch, + #[serde(rename = "app/update_check")] + AppUpdateCheck, + #[serde(rename = "unattributed")] + Unattributed, +} + +impl OperationCause { + pub const fn as_str(self) -> &'static str { + match self { + Self::AppStartup => "app/startup", + Self::NavigationHome => "navigation/home", + Self::NavigationLibrary => "navigation/library", + Self::NavigationBrowse => "navigation/browse", + Self::NavigationProject => "navigation/project", + Self::NavigationInstanceOverview => "navigation/instance/overview", + Self::NavigationInstanceContent => "navigation/instance/content", + Self::NavigationInstanceLogs => "navigation/instance/logs", + Self::NavigationServers => "navigation/servers", + Self::NavigationServerManage => "navigation/server/manage", + Self::NavigationServerContent => "navigation/server/content", + Self::InstanceRefreshUser => "instance/refresh/user", + Self::InstanceRefreshFilesystemWatch => { + "instance/refresh/filesystem_watch" + } + Self::InstanceUpdateAll => "instance/update/all", + Self::InstanceUpdateSingle => "instance/update/single", + Self::InstanceInstall => "instance/install", + Self::CacheRevalidate => "cache/revalidate", + Self::AuthSessionRefresh => "auth/session_refresh", + Self::BackgroundFriends => "background/friends", + Self::MinecraftLaunch => "minecraft/launch", + Self::AppUpdateCheck => "app/update_check", + Self::Unattributed => "unattributed", + } + } +} + +impl fmt::Display for OperationCause { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str(self.as_str()) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] +#[error("invalid operation cause: {0}")] +pub struct InvalidOperationCause(String); + +impl FromStr for OperationCause { + type Err = InvalidOperationCause; + + fn from_str(value: &str) -> Result { + let cause = match value { + "app/startup" => Self::AppStartup, + "navigation/home" => Self::NavigationHome, + "navigation/library" => Self::NavigationLibrary, + "navigation/browse" => Self::NavigationBrowse, + "navigation/project" => Self::NavigationProject, + "navigation/instance/overview" => Self::NavigationInstanceOverview, + "navigation/instance/content" => Self::NavigationInstanceContent, + "navigation/instance/logs" => Self::NavigationInstanceLogs, + "navigation/servers" => Self::NavigationServers, + "navigation/server/manage" => Self::NavigationServerManage, + "navigation/server/content" => Self::NavigationServerContent, + "instance/refresh/user" => Self::InstanceRefreshUser, + "instance/refresh/filesystem_watch" => { + Self::InstanceRefreshFilesystemWatch + } + "instance/update/all" => Self::InstanceUpdateAll, + "instance/update/single" => Self::InstanceUpdateSingle, + "instance/install" => Self::InstanceInstall, + "cache/revalidate" => Self::CacheRevalidate, + "auth/session_refresh" => Self::AuthSessionRefresh, + "background/friends" => Self::BackgroundFriends, + "minecraft/launch" => Self::MinecraftLaunch, + "app/update_check" => Self::AppUpdateCheck, + "unattributed" => Self::Unattributed, + _ => return Err(InvalidOperationCause(value.to_string())), + }; + + Ok(cause) + } +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct InvocationContext { + pub cause: OperationCause, +} + +impl InvocationContext { + pub fn into_operation_context(self) -> OperationContext { + OperationContext::new(self.cause) + } +} + +#[derive(Clone, Debug)] +pub struct OperationContext { + pub id: Uuid, + pub parent_id: Option, + pub cause: OperationCause, +} + +impl OperationContext { + pub fn new(cause: OperationCause) -> Self { + Self { + id: Uuid::new_v4(), + parent_id: None, + cause, + } + } + + pub fn child(&self, cause: OperationCause) -> Self { + Self { + id: Uuid::new_v4(), + parent_id: Some(self.id), + cause, + } + } + + pub const fn cause(&self) -> OperationCause { + self.cause + } + + pub const fn request_context_header(&self) -> &'static str { + self.cause.as_str() + } + + pub fn referer(&self) -> String { + format!("https://tauri.modrinth.app/_rc/{}", self.cause.as_str()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn causes_accept_only_the_stable_taxonomy() { + assert_eq!( + "navigation/instance/content".parse(), + Ok(OperationCause::NavigationInstanceContent) + ); + assert!("Navigation/Project".parse::().is_err()); + assert!( + "navigation/project/user-value" + .parse::() + .is_err() + ); + assert!("navigation//project".parse::().is_err()); + assert!("navigation/project/".parse::().is_err()); + assert!("a".repeat(256).parse::().is_err()); + } + + #[test] + fn serde_does_not_normalize_invalid_causes() { + assert_eq!( + serde_json::from_str::( + r#""navigation/instance/content""# + ) + .unwrap(), + OperationCause::NavigationInstanceContent + ); + assert!( + serde_json::from_str::( + r#""Navigation/Instance/Content""# + ) + .is_err() + ); + } + + #[test] + fn child_context_links_to_its_parent() { + let parent = OperationContext::new(OperationCause::InstanceInstall); + let child = parent.child(OperationCause::CacheRevalidate); + + assert_ne!(parent.id, child.id); + assert_eq!(child.parent_id, Some(parent.id)); + assert_eq!(child.cause(), OperationCause::CacheRevalidate); + } + + #[test] + fn invocation_context_accepts_only_a_cause() { + assert!( + serde_json::from_str::( + r#"{"cause":"navigation/home","id":"frontend-id"}"# + ) + .is_err() + ); + } + + #[test] + fn headers_have_exact_semantic_values() { + let context = + OperationContext::new(OperationCause::NavigationInstanceContent); + + assert_eq!( + context.referer(), + "https://tauri.modrinth.app/_rc/navigation/instance/content" + ); + assert_eq!( + context.request_context_header(), + "navigation/instance/content" + ); + } + + #[test] + fn concurrent_roots_remain_distinct() { + let first = OperationContext::new(OperationCause::NavigationBrowse); + let second = OperationContext::new(OperationCause::MinecraftLaunch); + + assert_ne!(first.id, second.id); + assert_ne!(first.cause(), second.cause()); + assert_eq!(first.parent_id, None); + assert_eq!(second.parent_id, None); + } + + #[tokio::test] + async fn spawned_watcher_and_cache_work_retains_explicit_contexts() { + let watcher = OperationContext::new( + OperationCause::InstanceRefreshFilesystemWatch, + ); + let cache = watcher.child(OperationCause::CacheRevalidate); + let watcher_id = watcher.id; + + let watcher_task = tokio::spawn(async move { + (watcher.id, watcher.parent_id, watcher.cause()) + }); + let cache_task = + tokio::spawn(async move { (cache.parent_id, cache.cause()) }); + + assert_eq!( + watcher_task.await.unwrap(), + ( + watcher_id, + None, + OperationCause::InstanceRefreshFilesystemWatch + ) + ); + assert_eq!( + cache_task.await.unwrap(), + (Some(watcher_id), OperationCause::CacheRevalidate) + ); + } +} diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index 99ae6d3457..6f0416eac0 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -1,5 +1,6 @@ use crate::state::ProjectType; use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async}; +use crate::{OperationCause, OperationContext}; use chrono::{DateTime, Utc}; use dashmap::DashSet; use reqwest::Method; @@ -804,17 +805,19 @@ macro_rules! impl_cache_methods { paste::paste! { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn []( + context: &OperationContext, id: &str, cache_behaviour: Option, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result> { - Ok(Self::[](&[id], cache_behaviour, pool, fetch_semaphore).await?.into_iter().next()) + Ok(Self::[](context, &[id], cache_behaviour, pool, fetch_semaphore).await?.into_iter().next()) } #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn []( + context: &OperationContext, ids: &[&str], cache_behaviour: Option, pool: &SqlitePool, @@ -822,7 +825,7 @@ macro_rules! impl_cache_methods { ) -> crate::Result> { let entry = - CachedEntry::get_many(CacheValueType::$variant, ids, cache_behaviour, pool, fetch_semaphore).await?; + CachedEntry::get_many(context, CacheValueType::$variant, ids, cache_behaviour, pool, fetch_semaphore).await?; Ok(entry.into_iter().filter_map(|x| if let Some(CacheValue::$variant(value)) = x.data { Some(value) @@ -843,13 +846,14 @@ macro_rules! impl_cache_method_singular { paste::paste! { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn [] ( + context: &OperationContext, cache_behaviour: Option, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result> { let entry = - CachedEntry::get(CacheValueType::$variant, DEFAULT_ID, cache_behaviour, pool, fetch_semaphore).await?; + CachedEntry::get(context, CacheValueType::$variant, DEFAULT_ID, cache_behaviour, pool, fetch_semaphore).await?; if let Some(CacheValue::$variant(value)) = entry.map(|x| x.data).flatten() { Ok(Some(value)) @@ -890,6 +894,7 @@ impl_cache_method_singular!( impl CachedEntry { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get( + context: &OperationContext, type_: CacheValueType, key: &str, cache_behaviour: Option, @@ -897,6 +902,7 @@ impl CachedEntry { fetch_semaphore: &FetchSemaphore, ) -> crate::Result> { Ok(Self::get_many( + context, type_, &[key], cache_behaviour, @@ -910,6 +916,7 @@ impl CachedEntry { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get_many( + context: &OperationContext, type_: CacheValueType, keys: &[&str], cache_behaviour: Option, @@ -1017,6 +1024,7 @@ impl CachedEntry { if !remaining_keys.is_empty() { let res = Self::fetch_many( + context, type_, remaining_keys.clone(), fetch_semaphore, @@ -1057,11 +1065,13 @@ impl CachedEntry { || cache_behaviour == CacheBehaviour::StaleWhileRevalidateSkipOffline) { + let context = context.child(OperationCause::CacheRevalidate); tokio::task::spawn(async move { // TODO: if possible- find a way to do this without invoking state get let state = crate::state::State::get().await?; let values = Self::fetch_many( + &context, type_, expired_keys, &state.api_semaphore, @@ -1084,12 +1094,14 @@ impl CachedEntry { } async fn fetch_many( + context: &OperationContext, type_: CacheValueType, keys: DashSet, fetch_semaphore: &FetchSemaphore, pool: &SqlitePool, ) -> crate::Result> { async fn fetch_many_batched( + context: &OperationContext, method: Method, api_url: &str, url: &str, @@ -1112,6 +1124,7 @@ impl CachedEntry { let res = futures::future::try_join_all(urls.iter().map(|url| { fetch_json::>( + context, method.clone(), url, None, @@ -1129,6 +1142,7 @@ impl CachedEntry { macro_rules! fetch_original_values { ($type:ident, $api_url:expr, $url_suffix:expr, $uri_path:expr, $cache_variant:path) => {{ let mut results = fetch_many_batched( + context, Method::GET, $api_url, &format!("{}?ids=", $url_suffix), @@ -1192,6 +1206,7 @@ impl CachedEntry { vec![( $cache_variant( fetch_json( + context, Method::GET, &*format!("{}{}", $api_url, $url_suffix), None, @@ -1247,6 +1262,7 @@ impl CachedEntry { } CacheValueType::Team => { let mut teams = fetch_many_batched::>( + context, Method::GET, env!("MODRINTH_API_URL_V3"), "teams?ids=", @@ -1287,6 +1303,7 @@ impl CachedEntry { } CacheValueType::Organization => { let mut orgs = fetch_many_batched::( + context, Method::GET, env!("MODRINTH_API_URL_V3"), "organizations?ids=", @@ -1343,6 +1360,7 @@ impl CachedEntry { } CacheValueType::File => { let mut versions = fetch_json::>( + context, Method::POST, concat!(env!("MODRINTH_API_URL"), "version_files"), None, @@ -1419,6 +1437,7 @@ impl CachedEntry { futures::future::try_join_all(fetch_urls.iter().map( |(_, _, url)| { fetch_json( + context, Method::GET, url, None, @@ -1644,6 +1663,7 @@ impl CachedEntry { let variation = fetch_json::< HashMap>, >( + context, Method::POST, concat!( env!("MODRINTH_API_URL"), @@ -1779,6 +1799,7 @@ impl CachedEntry { futures::future::try_join_all(fetch_urls.iter().map( |(_, url)| { fetch_json( + context, Method::GET, url, None, @@ -1821,6 +1842,7 @@ impl CachedEntry { ); match fetch_json::>( + context, Method::GET, &url, None, @@ -1873,6 +1895,7 @@ impl CachedEntry { futures::future::try_join_all(fetch_urls.iter().map( |(_, url)| { fetch_json( + context, Method::GET, url, None, @@ -2075,11 +2098,13 @@ impl CachedEntry { /// Get modpack file hashes from cache pub async fn get_modpack_files( + context: &OperationContext, version_id: &str, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result> { let entry = Self::get( + context, CacheValueType::ModpackFiles, version_id, None, @@ -2102,12 +2127,14 @@ impl CachedEntry { /// Get versions for a project (without changelogs for fast loading) #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get_project_versions( + context: &OperationContext, project_id: &str, cache_behaviour: Option, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result>> { let entry = Self::get( + context, CacheValueType::ProjectVersions, project_id, cache_behaviour, diff --git a/packages/app-lib/src/state/friends.rs b/packages/app-lib/src/state/friends.rs index 9e59febebd..d3a8c6ceda 100644 --- a/packages/app-lib/src/state/friends.rs +++ b/packages/app-lib/src/state/friends.rs @@ -5,6 +5,7 @@ use crate::event::emit::{emit_friend, emit_notification}; use crate::state::tunnel::InternalTunnelSocket; use crate::state::{ProcessManager, TunnelSocket}; use crate::util::fetch::{FetchSemaphore, fetch_advanced, fetch_json}; +use crate::{OperationCause, OperationContext}; use ariadne::ids::UserId; use ariadne::networking::message::{ ClientToServerMessage, ServerToClientMessage, @@ -68,12 +69,14 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn connect( &self, + context: &OperationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, process_manager: &ProcessManager, ) -> crate::Result<()> { let credentials = - ModrinthCredentials::get_and_refresh(exec, semaphore).await?; + ModrinthCredentials::get_and_refresh(context, exec, semaphore) + .await?; if let Some(credentials) = credentials { let mut request = format!( @@ -255,6 +258,7 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn socket_loop() -> crate::Result<()> { let state = crate::State::get().await?; + let context = OperationContext::new(OperationCause::BackgroundFriends); tokio::task::spawn(async move { let mut last_connection = Utc::now(); @@ -272,6 +276,7 @@ impl FriendsSocket { let _ = state .friends_socket .connect( + &context, &state.pool, &state.api_semaphore, &state.process_manager, @@ -321,10 +326,12 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn friends( + context: &OperationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result> { fetch_json( + context, Method::GET, concat!(env!("MODRINTH_API_URL_V3"), "friends"), None, @@ -346,11 +353,13 @@ impl FriendsSocket { #[tracing::instrument(skip(exec, semaphore))] pub async fn add_friend( + context: &OperationContext, user_id: &str, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result<()> { let result = fetch_advanced( + context, Method::POST, &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")), None, @@ -380,11 +389,13 @@ impl FriendsSocket { #[tracing::instrument(skip(exec, semaphore))] pub async fn remove_friend( + context: &OperationContext, user_id: &str, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result<()> { fetch_advanced( + context, Method::DELETE, &format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")), None, diff --git a/packages/app-lib/src/state/instances/commands/apply_content_install.rs b/packages/app-lib/src/state/instances/commands/apply_content_install.rs index 0491bcbb7e..6e17d11dca 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_install.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_install.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::instances::{ ContentRequirement, ContentSourceKind, Instance, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -45,6 +46,7 @@ pub(crate) struct InstanceInstallProjectRequest { } struct CachedEntryContentProvider<'a> { + context: &'a OperationContext, state: &'a State, cache_behaviour: Option, } @@ -57,6 +59,7 @@ impl ContentMetadataProvider for CachedEntryContentProvider<'_> { ) -> Result, ResolveError> { let version = CachedEntry::get_version( + self.context, version_id, self.cache_behaviour, &self.state.pool, @@ -73,6 +76,7 @@ impl ContentMetadataProvider for CachedEntryContentProvider<'_> { project_id: &str, ) -> Result, ResolveError> { let versions = CachedEntry::get_project_versions( + self.context, project_id, self.cache_behaviour, &self.state.pool, @@ -157,6 +161,7 @@ fn target_preferences( } pub(crate) async fn resolve_install_plan( + context: &OperationContext, instance_id: &str, request: InstanceInstallProjectRequest, state: &State, @@ -171,12 +176,14 @@ pub(crate) async fn resolve_install_plan( })?; let existing_project_ids = crate::state::get_installed_project_ids_for_instance( + context, instance_id, None, state, ) .await?; let provider = CachedEntryContentProvider { + context, state, cache_behaviour: Some(CacheBehaviour::MustRevalidate), }; @@ -200,11 +207,13 @@ pub(crate) async fn resolve_install_plan( } pub(crate) async fn install_resolved_content_plan( + context: &OperationContext, instance_id: &str, plan: &ResolveContentPlan, state: &State, ) -> crate::Result<()> { add_resolved_content( + context, instance_id, &plan.primary, DownloadReason::Standalone, @@ -213,6 +222,7 @@ pub(crate) async fn install_resolved_content_plan( .await?; for dependency in &plan.dependencies { add_resolved_content( + context, instance_id, dependency, DownloadReason::Dependency, @@ -225,12 +235,14 @@ pub(crate) async fn install_resolved_content_plan( } pub(crate) async fn switch_project_version_with_dependencies( + context: &OperationContext, instance_id: &str, project_path: &str, version_id: &str, state: &State, ) -> crate::Result { let version = CachedEntry::get_version( + context, version_id, Some(CacheBehaviour::MustRevalidate), &state.pool, @@ -246,6 +258,7 @@ pub(crate) async fn switch_project_version_with_dependencies( .map(ContentType::from) .unwrap_or(ContentType::Mod); let plan = resolve_install_plan( + context, instance_id, InstanceInstallProjectRequest { project_id: version.project_id, @@ -259,6 +272,7 @@ pub(crate) async fn switch_project_version_with_dependencies( let was_disabled = project_path.ends_with(".disabled"); let mut new_path = add_project_from_version( + context, instance_id, &plan.primary.version_id, DownloadReason::Update, @@ -276,6 +290,7 @@ pub(crate) async fn switch_project_version_with_dependencies( for dependency in &plan.dependencies { add_resolved_content( + context, instance_id, dependency, DownloadReason::Dependency, @@ -299,12 +314,14 @@ pub(crate) async fn switch_project_version_with_dependencies( } async fn add_resolved_content( + context: &OperationContext, instance_id: &str, content: &ResolvedContent, reason: DownloadReason, state: &State, ) -> crate::Result { add_project_from_version( + context, instance_id, &content.version_id, reason, @@ -342,6 +359,7 @@ pub(crate) async fn resolve_content_scope( } pub(crate) async fn add_project_from_version( + context: &OperationContext, instance_id: &str, version_id: &str, reason: DownloadReason, @@ -350,6 +368,7 @@ pub(crate) async fn add_project_from_version( state: &State, ) -> crate::Result { let downloaded = download_project_version( + context, instance_id, version_id, reason, @@ -363,6 +382,7 @@ pub(crate) async fn add_project_from_version( } pub(crate) async fn download_project_version( + context: &OperationContext, instance_id: &str, version_id: &str, reason: DownloadReason, @@ -380,6 +400,7 @@ pub(crate) async fn download_project_version( )) })?; let version = CachedEntry::get_version( + context, version_id, None, &state.pool, @@ -408,6 +429,7 @@ pub(crate) async fn download_project_version( dependent_on: dependent_on_version_id, }; let bytes = fetch::fetch( + context, &file.url, file.hashes.get("sha1").map(|hash| hash.as_str()), Some(&download_meta), diff --git a/packages/app-lib/src/state/instances/commands/apply_content_update.rs b/packages/app-lib/src/state/instances/commands/apply_content_update.rs index 09aa358921..f974f0ff55 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_update.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_update.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::instances::{ ContentEntry, ContentSet, ContentSourceKind, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -63,11 +64,13 @@ struct ResolvedDependency { } pub(crate) async fn update_project( + context: &OperationContext, instance_id: &str, project_path: &str, state: &State, ) -> crate::Result { let updates = check_content_updates( + context, instance_id, Some(CacheBehaviour::MustRevalidate), state, @@ -82,16 +85,19 @@ pub(crate) async fn update_project( ) })?; - apply_content_update(instance_id, project_path, &update, state).await + apply_content_update(context, instance_id, project_path, &update, state) + .await } async fn apply_content_update( + context: &OperationContext, instance_id: &str, project_path: &str, update: &ContentUpdate, state: &State, ) -> crate::Result { let mut new_path = add_project_from_version( + context, instance_id, &update.update_version_id, DownloadReason::Update, @@ -122,6 +128,7 @@ async fn apply_content_update( } pub(crate) async fn update_all_projects( + context: &OperationContext, instance_id: &str, state: &State, ) -> crate::Result> { @@ -132,12 +139,17 @@ pub(crate) async fn update_all_projects( 0, ) .await?; - let plan = plan_bulk_update(instance_id, state).await?; + let plan = plan_bulk_update(context, instance_id, state).await?; let download_total = plan.project_updates.len() + plan.dependency_additions.len(); - let downloads = - download_planned_projects(instance_id, &plan, download_total, state) - .await?; + let downloads = download_planned_projects( + context, + instance_id, + &plan, + download_total, + state, + ) + .await?; let mut changed = HashMap::new(); emit_bulk_update_progress( @@ -198,6 +210,7 @@ pub(crate) async fn update_all_projects( } async fn download_planned_projects( + context: &OperationContext, instance_id: &str, plan: &BulkUpdatePlan, total: usize, @@ -226,6 +239,7 @@ async fn download_planned_projects( match download { PlannedDownload::ProjectUpdate(update) => { let downloaded = download_project_version( + context, instance_id, &update.update_version_id, DownloadReason::Update, @@ -240,6 +254,7 @@ async fn download_planned_projects( } PlannedDownload::DependencyAddition(dependency) => { let downloaded = download_project_version( + context, instance_id, &dependency.version_id, DownloadReason::Dependency, @@ -292,11 +307,12 @@ async fn emit_bulk_update_progress( } async fn plan_bulk_update( + context: &OperationContext, instance_id: &str, state: &State, ) -> crate::Result { let updateable_paths = - bulk_updateable_project_paths(instance_id, state).await?; + bulk_updateable_project_paths(context, instance_id, state).await?; if updateable_paths.is_empty() { return Ok(BulkUpdatePlan { project_updates: Vec::new(), @@ -305,6 +321,7 @@ async fn plan_bulk_update( } let updates = check_content_updates( + context, instance_id, Some(CacheBehaviour::MustRevalidate), state, @@ -364,6 +381,7 @@ async fn plan_bulk_update( let version_id_refs = version_ids.iter().map(|id| id.as_str()).collect::>(); let versions = CachedEntry::get_version_many( + context, &version_id_refs, Some(CacheBehaviour::MustRevalidate), &state.pool, @@ -388,7 +406,8 @@ async fn plan_bulk_update( }) .collect::>(); let planned_dependencies = - dependency_closure(planned_versions, &content_set, state).await?; + dependency_closure(context, planned_versions, &content_set, state) + .await?; let dependency_additions = planned_dependencies .values() .filter(|dependency| { @@ -415,10 +434,12 @@ async fn plan_bulk_update( } async fn bulk_updateable_project_paths( + context: &OperationContext, instance_id: &str, state: &State, ) -> crate::Result> { let items = super::list_content::list_content( + context, instance_id, None, Some(CacheBehaviour::MustRevalidate), @@ -476,6 +497,7 @@ fn installed_project_from_row( } async fn dependency_closure( + context: &OperationContext, root_versions: Vec, content_set: &ContentSet, state: &State, @@ -497,6 +519,7 @@ async fn dependency_closure( } let Some(dependency_version) = resolve_dependency_version( + context, dependency, content_set, state, @@ -536,6 +559,7 @@ fn is_required_dependency( } async fn resolve_dependency_version( + context: &OperationContext, dependency: &Dependency, content_set: &ContentSet, state: &State, @@ -543,15 +567,19 @@ async fn resolve_dependency_version( project_versions_cache: &mut HashMap>>, ) -> crate::Result> { if let Some(version_id) = &dependency.version_id { - return cached_version(version_id, version_cache, state).await; + return cached_version(context, version_id, version_cache, state).await; } let Some(project_id) = &dependency.project_id else { return Ok(None); }; - let Some(mut versions) = - cached_project_versions(project_id, project_versions_cache, state) - .await? + let Some(mut versions) = cached_project_versions( + context, + project_id, + project_versions_cache, + state, + ) + .await? else { return Ok(None); }; @@ -562,12 +590,14 @@ async fn resolve_dependency_version( } async fn cached_version( + context: &OperationContext, version_id: &str, version_cache: &mut HashMap>, state: &State, ) -> crate::Result> { if !version_cache.contains_key(version_id) { let version = CachedEntry::get_version( + context, version_id, Some(CacheBehaviour::MustRevalidate), &state.pool, @@ -581,12 +611,14 @@ async fn cached_version( } async fn cached_project_versions( + context: &OperationContext, project_id: &str, project_versions_cache: &mut HashMap>>, state: &State, ) -> crate::Result>> { if !project_versions_cache.contains_key(project_id) { let versions = CachedEntry::get_project_versions( + context, project_id, Some(CacheBehaviour::MustRevalidate), &state.pool, diff --git a/packages/app-lib/src/state/instances/commands/check_content_updates.rs b/packages/app-lib/src/state/instances/commands/check_content_updates.rs index 771ae806b9..f9d173b69c 100644 --- a/packages/app-lib/src/state/instances/commands/check_content_updates.rs +++ b/packages/app-lib/src/state/instances/commands/check_content_updates.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::instances::{ ContentEntry, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -27,6 +28,7 @@ struct UpdateCandidate { } pub(crate) async fn check_content_updates( + context: &OperationContext, instance_id: &str, cache_behaviour: Option, state: &State, @@ -53,12 +55,13 @@ pub(crate) async fn check_content_updates( entry.file_id.as_deref().map(|file_id| (file_id, entry)) }) .collect::>(); - let files = sync_instance_content_files(&instance, state).await?; + let files = sync_instance_content_files(context, &instance, state).await?; let hashes = files .iter() .map(|file| file.sha1.as_str()) .collect::>(); let file_info = CachedEntry::get_file_many( + context, &hashes, cache_behaviour, &state.pool, @@ -91,7 +94,8 @@ pub(crate) async fn check_content_updates( } let installed_channels = - installed_update_channels(&candidates, cache_behaviour, state).await?; + installed_update_channels(context, &candidates, cache_behaviour, state) + .await?; let update_keys = candidates .iter() .map(|candidate| { @@ -112,6 +116,7 @@ pub(crate) async fn check_content_updates( .map(|key| key.as_str()) .collect::>(); let updates = CachedEntry::get_file_update_many( + context, &update_key_refs, cache_behaviour, &state.pool, @@ -159,6 +164,7 @@ pub(crate) async fn check_content_updates( } async fn installed_update_channels( + context: &OperationContext, candidates: &[UpdateCandidate], cache_behaviour: Option, state: &State, @@ -168,6 +174,7 @@ async fn installed_update_channels( .map(|candidate| candidate.current_version_id.as_str()) .collect::>(); let versions = CachedEntry::get_version_many( + context, &version_ids, cache_behaviour, &state.pool, diff --git a/packages/app-lib/src/state/instances/commands/create_instance.rs b/packages/app-lib/src/state/instances/commands/create_instance.rs index 7567ec815c..ba9e500caa 100644 --- a/packages/app-lib/src/state/instances/commands/create_instance.rs +++ b/packages/app-lib/src/state/instances/commands/create_instance.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::launcher::get_loader_version_from_profile; use crate::state::instances::{ ContentSet, ContentSetStatus, ContentSourceKind, Instance, @@ -27,6 +28,7 @@ pub struct CreateInstance { } pub(crate) async fn create_instance( + context: &OperationContext, input: CreateInstance, state: &State, ) -> crate::Result { @@ -45,6 +47,7 @@ pub(crate) async fn create_instance( let loader_version = if input.loader != ModLoader::Vanilla { get_loader_version_from_profile( + context, &input.game_version, input.loader, input.loader_version.as_deref(), @@ -56,7 +59,8 @@ pub(crate) async fn create_instance( }; let icon_path = - resolve_icon_path(input.icon_path.as_deref(), state).await?; + resolve_icon_path(context, input.icon_path.as_deref(), state) + .await?; let now = Utc::now(); let instance_id = format!("local:{}", Uuid::new_v4()); let content_set_id = format!("content-set:{}", Uuid::new_v4()); @@ -167,6 +171,7 @@ async fn path_available( } async fn resolve_icon_path( + context: &OperationContext, icon_path: Option<&str>, state: &State, ) -> crate::Result> { @@ -178,6 +183,7 @@ async fn resolve_icon_path( || icon.starts_with("http://") { let fetched = fetch::fetch( + context, icon, None, None, diff --git a/packages/app-lib/src/state/instances/commands/list_content.rs b/packages/app-lib/src/state/instances/commands/list_content.rs index 4272f2990a..700dafe037 100644 --- a/packages/app-lib/src/state/instances/commands/list_content.rs +++ b/packages/app-lib/src/state/instances/commands/list_content.rs @@ -1,7 +1,6 @@ use super::sync_content_files::{ project_type_for_file, sync_instance_content_files, }; -use crate::State; use crate::pack::install_from::{PackFileHash, PackFormat}; use crate::state::instances::adapters::sqlite; use crate::state::instances::{ @@ -17,6 +16,7 @@ use crate::state::{ use crate::util::fetch::{ DownloadMeta, DownloadReason, FetchSemaphore, fetch_mirrors, sha1_async, }; +use crate::{OperationContext, State}; use async_zip::base::read::seek::ZipFileReader; use dashmap::DashMap; use sqlx::SqlitePool; @@ -59,6 +59,7 @@ pub(crate) async fn list_content_sets( } pub(crate) async fn get_content_projects( + context: &OperationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -72,6 +73,7 @@ pub(crate) async fn get_content_projects( .await?; content_projects_for_scope( + context, &resolved, cache_behaviour, state, @@ -81,12 +83,14 @@ pub(crate) async fn get_content_projects( } pub(crate) async fn get_installed_project_ids_for_instance( + context: &OperationContext, instance_id: &str, content_set_id: Option<&str>, state: &State, ) -> crate::Result> { let projects = - get_content_projects(instance_id, content_set_id, None, state).await?; + get_content_projects(context, instance_id, content_set_id, None, state) + .await?; Ok(projects .into_iter() @@ -191,6 +195,7 @@ fn instance_matches_targets( } pub(crate) async fn list_content( + context: &OperationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -215,6 +220,7 @@ pub(crate) async fn list_content( match linked_modpack_ids(&link) { Some((_, version_id)) => { get_cached_modpack_identifiers( + context, &version_id, &state.pool, &state.api_semaphore, @@ -240,12 +246,18 @@ pub(crate) async fn list_content( } else { ContentFilter::All }; - let files = - content_projects_for_scope(&resolved, cache_behaviour, state, filter) - .await?; + let files = content_projects_for_scope( + context, + &resolved, + cache_behaviour, + state, + filter, + ) + .await?; let files = files.into_iter().collect::>(); content_files_to_content_items( + context, &resolved.instance, &files, cache_behaviour, @@ -255,6 +267,7 @@ pub(crate) async fn list_content( } pub(crate) async fn list_linked_modpack_content( + context: &OperationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -273,6 +286,7 @@ pub(crate) async fn list_linked_modpack_content( .await?; if is_imported_modpack_scope(&link) { let files = content_projects_for_scope( + context, &resolved, cache_behaviour, state, @@ -286,6 +300,7 @@ pub(crate) async fn list_linked_modpack_content( let files = files.into_iter().collect::>(); return content_files_to_content_items( + context, &resolved.instance, &files, cache_behaviour, @@ -298,6 +313,7 @@ pub(crate) async fn list_linked_modpack_content( return Ok(Vec::new()); }; let ids = match get_modpack_identifiers( + context, &version_id, &resolved.content_set, &state.pool, @@ -312,6 +328,7 @@ pub(crate) async fn list_linked_modpack_content( } }; let files = content_projects_for_scope( + context, &resolved, cache_behaviour, state, @@ -321,6 +338,7 @@ pub(crate) async fn list_linked_modpack_content( let files = files.into_iter().collect::>(); content_files_to_content_items( + context, &resolved.instance, &files, cache_behaviour, @@ -330,6 +348,7 @@ pub(crate) async fn list_linked_modpack_content( } pub(crate) async fn get_linked_modpack_info( + context: &OperationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -349,18 +368,21 @@ pub(crate) async fn get_linked_modpack_info( }; let (project, version, all_versions) = tokio::try_join!( CachedEntry::get_project( + context, &project_id, cache_behaviour, &state.pool, &state.api_semaphore, ), CachedEntry::get_version( + context, &version_id, cache_behaviour, &state.pool, &state.api_semaphore, ), CachedEntry::get_project_versions( + context, &project_id, cache_behaviour, &state.pool, @@ -375,12 +397,14 @@ pub(crate) async fn get_linked_modpack_info( let (project, all_versions) = if version.project_id != project_id { let (modpack_project, modpack_versions) = tokio::try_join!( CachedEntry::get_project( + context, &version.project_id, cache_behaviour, &state.pool, &state.api_semaphore, ), CachedEntry::get_project_versions( + context, &version.project_id, cache_behaviour, &state.pool, @@ -398,6 +422,7 @@ pub(crate) async fn get_linked_modpack_info( })?; let owner = if let Some(org_id) = &project.organization { let org = CachedEntry::get_organization( + context, org_id, cache_behaviour, &state.pool, @@ -412,6 +437,7 @@ pub(crate) async fn get_linked_modpack_info( }) } else { let team = CachedEntry::get_team( + context, &project.team, cache_behaviour, &state.pool, @@ -447,6 +473,7 @@ pub(crate) async fn get_linked_modpack_info( } pub(crate) async fn dependencies_to_content_items( + context: &OperationContext, dependencies: &[Dependency], cache_behaviour: Option, pool: &SqlitePool, @@ -464,6 +491,7 @@ pub(crate) async fn dependencies_to_content_items( .filter_map(|dependency| dependency.version_id.clone()) .collect::>(); let meta = resolve_metadata( + context, &project_ids, &version_ids, cache_behaviour, @@ -586,12 +614,14 @@ async fn resolve_content_scope_with_instance( } async fn content_projects_for_scope( + context: &OperationContext, resolved: &ResolvedContentScope, cache_behaviour: Option, state: &State, filter: ContentFilter<'_>, ) -> crate::Result> { - let files = sync_instance_content_files(&resolved.instance, state).await?; + let files = + sync_instance_content_files(context, &resolved.instance, state).await?; let entries = sqlite::content_rows::get_content_entries( &resolved.content_set.id, &state.pool, @@ -608,6 +638,7 @@ async fn content_projects_for_scope( .map(|file| file.sha1.as_str()) .collect::>(); let file_info = CachedEntry::get_file_many( + context, &hashes, cache_behaviour, &state.pool, @@ -619,6 +650,7 @@ async fn content_projects_for_scope( .map(|file| (file.hash.clone(), file)) .collect::>(); let installed_channels = get_installed_update_channels( + context, &file_info_by_hash, cache_behaviour, &state.pool, @@ -647,6 +679,7 @@ async fn content_projects_for_scope( let update_key_refs = update_keys.iter().map(String::as_str).collect::>(); let file_updates = CachedEntry::get_file_update_many( + context, &update_key_refs, cache_behaviour, &state.pool, @@ -746,6 +779,7 @@ async fn content_projects_for_scope( } async fn get_installed_update_channels( + context: &OperationContext, file_info_by_hash: &HashMap, cache_behaviour: Option, pool: &SqlitePool, @@ -760,6 +794,7 @@ async fn get_installed_update_channels( } let version_id_refs = version_ids.iter().copied().collect::>(); let versions = CachedEntry::get_version_many( + context, &version_id_refs, cache_behaviour, pool, @@ -809,6 +844,7 @@ fn file_update_cache_key( } async fn content_files_to_content_items( + context: &OperationContext, instance: &Instance, files: &[(String, ContentFile)], cache_behaviour: Option, @@ -831,6 +867,7 @@ async fn content_files_to_content_items( }) .collect::>(); let meta = resolve_metadata( + context, &project_ids, &version_ids, cache_behaviour, @@ -916,6 +953,7 @@ struct ResolvedMetadata { } async fn resolve_metadata( + context: &OperationContext, project_ids: &HashSet, version_ids: &HashSet, cache_behaviour: Option, @@ -934,6 +972,7 @@ async fn resolve_metadata( Ok(Vec::new()) } else { CachedEntry::get_project_many( + context, &project_id_refs, cache_behaviour, pool, @@ -947,6 +986,7 @@ async fn resolve_metadata( Ok(Vec::new()) } else { CachedEntry::get_version_many( + context, &version_id_refs, cache_behaviour, pool, @@ -977,6 +1017,7 @@ async fn resolve_metadata( Ok(Vec::new()) } else { CachedEntry::get_team_many( + context, &team_id_refs, cache_behaviour, pool, @@ -990,6 +1031,7 @@ async fn resolve_metadata( Ok(Vec::new()) } else { CachedEntry::get_organization_many( + context, &org_id_refs, cache_behaviour, pool, @@ -1171,13 +1213,18 @@ impl ModpackIdentifiers { } async fn get_cached_modpack_identifiers( + context: &OperationContext, version_id: &str, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result> { - let Some(cached) = - CachedEntry::get_modpack_files(version_id, pool, fetch_semaphore) - .await? + let Some(cached) = CachedEntry::get_modpack_files( + context, + version_id, + pool, + fetch_semaphore, + ) + .await? else { return Ok(None); }; @@ -1193,14 +1240,19 @@ async fn get_cached_modpack_identifiers( } async fn get_modpack_identifiers( + context: &OperationContext, version_id: &str, content_set: &ContentSet, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, ) -> crate::Result { - if let Some(cached) = - CachedEntry::get_modpack_files(version_id, pool, fetch_semaphore) - .await? + if let Some(cached) = CachedEntry::get_modpack_files( + context, + version_id, + pool, + fetch_semaphore, + ) + .await? { if !cached.project_ids.is_empty() { return Ok(ModpackIdentifiers { @@ -1214,9 +1266,14 @@ async fn get_modpack_identifiers( .iter() .map(String::as_str) .collect::>(); - let files = - CachedEntry::get_file_many(&hash_refs, None, pool, fetch_semaphore) - .await?; + let files = CachedEntry::get_file_many( + context, + &hash_refs, + None, + pool, + fetch_semaphore, + ) + .await?; let project_ids = files .iter() .map(|file| file.project_id.clone()) @@ -1237,14 +1294,19 @@ async fn get_modpack_identifiers( }); } - let version = - CachedEntry::get_version(version_id, None, pool, fetch_semaphore) - .await? - .ok_or_else(|| { - crate::ErrorKind::InputError(format!( - "Modpack version {version_id} not found" - )) - })?; + let version = CachedEntry::get_version( + context, + version_id, + None, + pool, + fetch_semaphore, + ) + .await? + .ok_or_else(|| { + crate::ErrorKind::InputError(format!( + "Modpack version {version_id} not found" + )) + })?; let primary_file = version .files .iter() @@ -1262,6 +1324,7 @@ async fn get_modpack_identifiers( dependent_on: Some(version_id.to_string()), }; let mrpack_bytes = fetch_mirrors( + context, &[&primary_file.url], primary_file.hashes.get("sha1").map(String::as_str), Some(&download_meta), diff --git a/packages/app-lib/src/state/instances/commands/sync_content_files.rs b/packages/app-lib/src/state/instances/commands/sync_content_files.rs index 3686558393..4b428cce3d 100644 --- a/packages/app-lib/src/state/instances/commands/sync_content_files.rs +++ b/packages/app-lib/src/state/instances/commands/sync_content_files.rs @@ -1,12 +1,13 @@ -use crate::State; use crate::state::instances::adapters::{filesystem, sqlite}; use crate::state::instances::{Instance, InstanceFile}; use crate::state::{CachedEntry, ProjectType}; +use crate::{OperationContext, State}; use chrono::Utc; use std::collections::HashMap; use uuid::Uuid; pub(crate) async fn sync_content_files( + context: &OperationContext, instance_id: &str, state: &State, ) -> crate::Result> { @@ -17,10 +18,11 @@ pub(crate) async fn sync_content_files( crate::ErrorKind::InputError("Unknown instance".to_string()) })?; - sync_instance_content_files(&instance, state).await + sync_instance_content_files(context, &instance, state).await } pub(crate) async fn sync_instance_content_files( + context: &OperationContext, instance: &Instance, state: &State, ) -> crate::Result> { @@ -33,6 +35,7 @@ pub(crate) async fn sync_instance_content_files( .map(|file| file.hash_cache_key.as_str()) .collect::>(); let hashes = CachedEntry::get_file_hash_many( + context, &cache_keys, None, &state.pool, diff --git a/packages/app-lib/src/state/instances/watcher.rs b/packages/app-lib/src/state/instances/watcher.rs index 08647a5dc3..c223aa68bc 100644 --- a/packages/app-lib/src/state/instances/watcher.rs +++ b/packages/app-lib/src/state/instances/watcher.rs @@ -1,10 +1,10 @@ -use crate::State; use crate::event::InstancePayloadType; use crate::event::emit::{emit_instance, emit_warning}; use crate::state::{ DirectoryInfo, InstanceInstallStage, ProjectType, attached_world_data, }; use crate::worlds::WorldType; +use crate::{OperationCause, OperationContext, State}; use notify::{RecommendedWatcher, RecursiveMode}; use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer}; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -34,6 +34,14 @@ pub async fn init_watcher() -> crate::Result { tracing::info!(parent: &span, "Initing watcher"); while let Some(res) = rx.recv().await { let _span = span.enter(); + let context = OperationContext::new( + OperationCause::InstanceRefreshFilesystemWatch, + ); + tracing::debug!( + operation_id = %context.id, + operation_cause = %context.cause(), + "Processing filesystem watcher refresh" + ); match res { Ok(events) => { diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index 18fa7edbb8..a2ca90da8d 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -1,5 +1,6 @@ //! Theseus state management system use crate::util::fetch::{FetchSemaphore, IoSemaphore}; +use crate::{OperationCause, OperationContext}; use std::sync::Arc; use std::sync::atomic::AtomicBool; use tokio::sync::{OnceCell, Semaphore}; @@ -96,6 +97,7 @@ pub struct State { impl State { pub async fn init(app_identifier: String) -> crate::Result<()> { + let context = OperationContext::new(OperationCause::AppStartup); let state = LAUNCHER_STATE .get_or_try_init(move || Self::initialize_state(app_identifier)) .await?; @@ -114,11 +116,13 @@ impl State { ) .await; + let auth_context = + context.child(OperationCause::AuthSessionRefresh); let res = tokio::try_join!( state.discord_rpc.clear_to_default(true), instances::refresh_all_instances(), Settings::migrate(&state.pool), - ModrinthCredentials::refresh_all(), + ModrinthCredentials::refresh_all(&auth_context), ); if let Err(e) = res { @@ -128,6 +132,7 @@ impl State { let _ = state .friends_socket .connect( + &context.child(OperationCause::BackgroundFriends), &state.pool, &state.api_semaphore, &state.process_manager, diff --git a/packages/app-lib/src/state/mr_auth.rs b/packages/app-lib/src/state/mr_auth.rs index 09b735ea53..5e18a8c331 100644 --- a/packages/app-lib/src/state/mr_auth.rs +++ b/packages/app-lib/src/state/mr_auth.rs @@ -1,3 +1,4 @@ +use crate::OperationContext; use crate::state::{CacheBehaviour, CachedEntry}; use crate::util::fetch::{FetchSemaphore, fetch_advanced}; use chrono::{DateTime, Duration, TimeZone, Utc}; @@ -16,6 +17,7 @@ pub struct ModrinthCredentials { impl ModrinthCredentials { pub async fn get_and_refresh( + context: &OperationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result> { @@ -29,6 +31,7 @@ impl ModrinthCredentials { } let resp = fetch_advanced( + context, Method::POST, concat!(env!("MODRINTH_API_URL"), "session/refresh"), None, @@ -173,13 +176,16 @@ impl ModrinthCredentials { Ok(()) } - pub(crate) async fn refresh_all() -> crate::Result<()> { + pub(crate) async fn refresh_all( + context: &OperationContext, + ) -> crate::Result<()> { let state = crate::State::get().await?; let all = Self::get_all(&state.pool).await?; let user_ids = all.into_iter().map(|x| x.0).collect::>(); CachedEntry::get_user_many( + context, &user_ids.iter().map(|x| &**x).collect::>(), Some(CacheBehaviour::Bypass), &state.pool, @@ -196,6 +202,7 @@ pub const fn get_login_url() -> &'static str { } pub async fn finish_login_flow( + context: &OperationContext, code: &str, semaphore: &FetchSemaphore, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, @@ -206,7 +213,7 @@ pub async fn finish_login_flow( // needed. TODO not do this for the reasons outlined at // https://oauth.net/2/grant-types/implicit/ - let info = fetch_info(code, semaphore, exec).await?; + let info = fetch_info(context, code, semaphore, exec).await?; Ok(ModrinthCredentials { session: code.to_string(), @@ -217,11 +224,13 @@ pub async fn finish_login_flow( } async fn fetch_info( + context: &OperationContext, token: &str, semaphore: &FetchSemaphore, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result { let result = fetch_advanced( + context, Method::GET, concat!(env!("MODRINTH_API_URL"), "user"), None, diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index 669dac581f..fcbbdfa947 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -102,6 +102,7 @@ impl ProcessManager { #[allow(clippy::too_many_arguments)] pub async fn insert_new_process( &self, + operation_context: &crate::OperationContext, instance_id: &str, instance_path: &str, instance_name: &str, @@ -214,6 +215,7 @@ impl ProcessManager { } tokio::spawn(Process::sequential_process_manager( + operation_context.clone(), instance_id.to_string(), instance_path.to_string(), post_exit_command, @@ -747,6 +749,7 @@ impl Process { // Also, as the process ends, it spawns the follow-up process if it exists // By convention, ExitStatus is last command's exit status, and we exit on the first non-zero exit status async fn sequential_process_manager( + operation_context: crate::OperationContext, instance_id: String, instance_path: String, post_exit_command: Option, @@ -833,6 +836,7 @@ impl Process { tokio::spawn(async move { if let Err(e) = crate::api::instance::try_update_playtime_by_instance_id( + &operation_context, &playtime_instance_id, ) .await diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index 0662a3d5c4..99522132e1 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -2,6 +2,9 @@ use super::io::{self, IOError}; use crate::event::LoadingBarId; use crate::event::emit::emit_loading; +use crate::operation::{ + OperationCause, OperationContext, REQUEST_CONTEXT_HEADER, +}; use crate::{ErrorKind, LabrinthError}; use bytes::Bytes; use chrono::{DateTime, TimeDelta, Utc}; @@ -333,6 +336,7 @@ fn duration_seconds_ceil(duration: Duration) -> u64 { fn reqwest_client_builder() -> reqwest::ClientBuilder { reqwest::Client::builder() + .referer(false) .connect_timeout(time::Duration::from_secs(15)) .read_timeout(time::Duration::from_secs(30)) .tcp_keepalive(Some(time::Duration::from_secs(10))) @@ -353,6 +357,43 @@ pub static REQWEST_CLIENT: LazyLock = LazyLock::new(|| { .expect("client configuration should be valid") }); +fn is_approved_modrinth_service_url(url: &str) -> bool { + let Ok(url) = url::Url::parse(url) else { + return false; + }; + + [ + env!("MODRINTH_API_BASE_URL"), + env!("MODRINTH_API_URL"), + env!("MODRINTH_API_URL_V3"), + ] + .into_iter() + .filter_map(|approved| url::Url::parse(approved).ok()) + .any(|approved| url.origin() == approved.origin()) +} + +fn attach_operation_headers( + request: reqwest::RequestBuilder, + url: &str, + context: &OperationContext, +) -> reqwest::RequestBuilder { + if !is_approved_modrinth_service_url(url) { + return request; + } + + if cfg!(debug_assertions) && context.cause() == OperationCause::Unattributed + { + tracing::warn!( + operation_id = %context.id, + "Unattributed operation context reached the Modrinth HTTP boundary" + ); + } + + request + .header(reqwest::header::REFERER, context.referer()) + .header(REQUEST_CONTEXT_HEADER, context.request_context_header()) +} + const FETCH_ATTEMPTS: usize = 2; pub type FetchProgressFn<'a> = dyn FnMut( @@ -364,6 +405,7 @@ pub type FetchProgressFn<'a> = dyn FnMut( #[tracing::instrument(skip(semaphore))] pub async fn fetch( + context: &OperationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -372,6 +414,7 @@ pub async fn fetch( exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result { fetch_advanced( + context, Method::GET, url, sha1, @@ -388,6 +431,7 @@ pub async fn fetch( #[tracing::instrument(skip(semaphore))] pub async fn fetch_with_client( + context: &OperationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -397,6 +441,7 @@ pub async fn fetch_with_client( client: &reqwest::Client, ) -> crate::Result { fetch_advanced_with_client( + context, Method::GET, url, sha1, @@ -414,6 +459,7 @@ pub async fn fetch_with_client( #[tracing::instrument(skip(semaphore, progress))] pub async fn fetch_with_client_progress( + context: &OperationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -424,6 +470,7 @@ pub async fn fetch_with_client_progress( progress: Option<&mut FetchProgressFn<'_>>, ) -> crate::Result { fetch_advanced_with_client_and_progress( + context, Method::GET, url, sha1, @@ -442,6 +489,7 @@ pub async fn fetch_with_client_progress( #[tracing::instrument(skip(json_body, semaphore))] pub async fn fetch_json( + context: &OperationContext, method: Method, url: &str, sha1: Option<&str>, @@ -454,8 +502,8 @@ where T: DeserializeOwned, { let result = fetch_advanced( - method, url, sha1, json_body, None, None, None, uri_path, semaphore, - exec, + context, method, url, sha1, json_body, None, None, None, uri_path, + semaphore, exec, ) .await?; let value = serde_json::from_slice(&result)?; @@ -467,6 +515,7 @@ where #[tracing::instrument(skip(json_body, semaphore))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced( + context: &OperationContext, method: Method, url: &str, sha1: Option<&str>, @@ -479,6 +528,7 @@ pub async fn fetch_advanced( exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result { fetch_advanced_with_client( + context, method, url, sha1, @@ -497,6 +547,7 @@ pub async fn fetch_advanced( #[tracing::instrument(skip(json_body, semaphore, progress))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced_with_progress( + context: &OperationContext, method: Method, url: &str, sha1: Option<&str>, @@ -510,6 +561,7 @@ pub async fn fetch_advanced_with_progress( progress: Option<&mut FetchProgressFn<'_>>, ) -> crate::Result { fetch_advanced_with_client_and_progress( + context, method, url, sha1, @@ -530,6 +582,7 @@ pub async fn fetch_advanced_with_progress( #[tracing::instrument(skip(json_body, semaphore))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced_with_client( + context: &OperationContext, method: Method, url: &str, sha1: Option<&str>, @@ -543,6 +596,7 @@ pub async fn fetch_advanced_with_client( client: &reqwest::Client, ) -> crate::Result { fetch_advanced_with_client_and_progress( + context, method, url, sha1, @@ -562,6 +616,7 @@ pub async fn fetch_advanced_with_client( #[tracing::instrument(skip(json_body, semaphore, client, progress))] #[allow(clippy::too_many_arguments)] async fn fetch_advanced_with_client_and_progress( + context: &OperationContext, method: Method, url: &str, sha1: Option<&str>, @@ -608,7 +663,11 @@ async fn fetch_advanced_with_client_and_progress( .into()); } - let mut req = client.request(method.clone(), url); + let mut req = attach_operation_headers( + client.request(method.clone(), url), + url, + context, + ); if let Some(body) = json_body.clone() { req = req.json(&body); @@ -755,6 +814,7 @@ async fn fetch_advanced_with_client_and_progress( /// Downloads a file from specified mirrors #[tracing::instrument(skip(semaphore))] pub async fn fetch_mirrors( + context: &OperationContext, mirrors: &[&str], sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -770,6 +830,7 @@ pub async fn fetch_mirrors( for (index, mirror) in mirrors.iter().enumerate() { let result = fetch_with_client( + context, mirror, sha1, download_meta, @@ -790,6 +851,7 @@ pub async fn fetch_mirrors( #[tracing::instrument(skip(semaphore, progress))] pub async fn fetch_mirrors_with_progress( + context: &OperationContext, mirrors: &[&str], sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -806,6 +868,7 @@ pub async fn fetch_mirrors_with_progress( for (index, mirror) in mirrors.iter().enumerate() { let result = fetch_with_client_progress( + context, mirror, sha1, download_meta, @@ -828,6 +891,7 @@ pub async fn fetch_mirrors_with_progress( /// Posts a JSON to a URL #[tracing::instrument(skip(json_body, semaphore))] pub async fn post_json( + context: &OperationContext, url: &str, json_body: serde_json::Value, semaphore: &FetchSemaphore, @@ -835,7 +899,12 @@ pub async fn post_json( ) -> crate::Result<()> { let _permit = semaphore.0.acquire().await?; - let mut req = INSECURE_REQWEST_CLIENT.post(url).json(&json_body); + let mut req = attach_operation_headers( + INSECURE_REQWEST_CLIENT.post(url), + url, + context, + ) + .json(&json_body); if let Some(creds) = crate::state::ModrinthCredentials::get_active(exec).await? @@ -938,6 +1007,113 @@ pub async fn sha1_async(bytes: Bytes) -> crate::Result { Ok(hash) } +#[cfg(test)] +mod operation_context_tests { + use super::*; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::net::TcpListener; + + fn built_request( + url: &str, + context: &OperationContext, + ) -> reqwest::Request { + attach_operation_headers(INSECURE_REQWEST_CLIENT.get(url), url, context) + .build() + .unwrap() + } + + #[test] + fn approved_origins_receive_operation_headers() { + let context = + OperationContext::new(OperationCause::NavigationInstanceContent); + let request = built_request( + concat!(env!("MODRINTH_API_URL"), "project/example"), + &context, + ); + + assert_eq!( + request.headers().get(reqwest::header::REFERER).unwrap(), + "https://tauri.modrinth.app/_rc/navigation/instance/content" + ); + assert_eq!( + request.headers().get(REQUEST_CONTEXT_HEADER).unwrap(), + "navigation/instance/content" + ); + } + + #[test] + fn third_party_origins_do_not_receive_operation_headers() { + let context = OperationContext::new(OperationCause::InstanceInstall); + let request = built_request("https://example.com/file.jar", &context); + + assert!(!request.headers().contains_key(reqwest::header::REFERER)); + assert!(!request.headers().contains_key(REQUEST_CONTEXT_HEADER)); + } + + #[test] + fn similarly_prefixed_hosts_are_not_approved() { + let context = OperationContext::new(OperationCause::NavigationBrowse); + let approved = url::Url::parse(env!("MODRINTH_API_URL")).unwrap(); + let url = format!( + "{}://{}.example.com/project", + approved.scheme(), + approved.host_str().unwrap() + ); + let request = built_request(&url, &context); + + assert!(!request.headers().contains_key(reqwest::header::REFERER)); + assert!(!request.headers().contains_key(REQUEST_CONTEXT_HEADER)); + } + + #[test] + fn every_retry_attempt_retains_the_same_context() { + let context = OperationContext::new(OperationCause::CacheRevalidate); + let url = concat!(env!("MODRINTH_API_URL_V3"), "projects"); + + for _ in 0..=FETCH_ATTEMPTS { + let request = built_request(url, &context); + assert_eq!( + request.headers().get(REQUEST_CONTEXT_HEADER).unwrap(), + context.request_context_header() + ); + assert_eq!( + request.headers().get(reqwest::header::REFERER).unwrap(), + context.referer().as_str() + ); + } + } + + #[tokio::test] + async fn disabling_automatic_referer_keeps_redirects_enabled() { + let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); + let address = listener.local_addr().unwrap(); + + let server = tokio::spawn(async move { + for response in [ + "HTTP/1.1 302 Found\r\nLocation: /final\r\nContent-Length: 0\r\nConnection: close\r\n\r\n", + "HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok", + ] { + let (mut stream, _) = listener.accept().await.unwrap(); + let mut request = vec![0; 1024]; + stream.read(&mut request).await.unwrap(); + stream.write_all(response.as_bytes()).await.unwrap(); + } + }); + + let response = reqwest_client_builder() + .build() + .unwrap() + .get(format!("http://{address}/redirect")) + .send() + .await + .unwrap(); + + assert_eq!(response.url().path(), "/final"); + assert_eq!(response.bytes().await.unwrap(), "ok"); + server.await.unwrap(); + } +} + pub async fn sha1_file_async( path: impl AsRef, ) -> crate::Result<(u64, String)> { From d9d3bd1daa06aeb0e8f3f6b88de4b7906d507cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Tue, 21 Jul 2026 13:09:44 -0400 Subject: [PATCH 2/2] chore(app): simplify context propagation --- apps/app/src/api/cache.rs | 6 +- apps/app/src/api/friends.rs | 6 +- apps/app/src/api/install.rs | 16 +- apps/app/src/api/instance.rs | 36 +-- apps/app/src/api/jre.rs | 2 +- apps/app/src/api/metadata.rs | 4 +- apps/app/src/api/mod.rs | 6 - apps/app/src/api/mr_auth.rs | 4 +- apps/app/src/api/tags.rs | 10 +- apps/app/src/api/worlds.rs | 6 +- packages/app-lib/src/api/cache.rs | 8 +- packages/app-lib/src/api/friends.rs | 8 +- packages/app-lib/src/api/instance/content.rs | 16 +- .../app-lib/src/api/instance/export_mrpack.rs | 6 +- packages/app-lib/src/api/instance/install.rs | 10 +- .../app-lib/src/api/instance/lifecycle.rs | 4 +- packages/app-lib/src/api/instance/projects.rs | 18 +- packages/app-lib/src/api/instance/run.rs | 18 +- packages/app-lib/src/api/jre.rs | 10 +- packages/app-lib/src/api/metadata.rs | 6 +- packages/app-lib/src/api/mod.rs | 2 +- packages/app-lib/src/api/mr_auth.rs | 6 +- .../app-lib/src/api/pack/import/atlauncher.rs | 4 +- .../app-lib/src/api/pack/import/curseforge.rs | 2 +- .../app-lib/src/api/pack/import/gdlauncher.rs | 2 +- packages/app-lib/src/api/pack/import/mmc.rs | 4 +- packages/app-lib/src/api/pack/import/mod.rs | 6 +- packages/app-lib/src/api/pack/install_from.rs | 6 +- .../app-lib/src/api/pack/install_mrpack.rs | 18 +- packages/app-lib/src/api/tags.rs | 12 +- packages/app-lib/src/api/worlds.rs | 6 +- packages/app-lib/src/install/runner.rs | 30 +- packages/app-lib/src/launcher/download.rs | 22 +- packages/app-lib/src/launcher/mod.rs | 45 +-- packages/app-lib/src/lib.rs | 4 +- packages/app-lib/src/operation.rs | 261 ++---------------- packages/app-lib/src/state/cache.rs | 22 +- packages/app-lib/src/state/friends.rs | 12 +- .../commands/apply_content_install.rs | 16 +- .../commands/apply_content_update.rs | 22 +- .../commands/check_content_updates.rs | 6 +- .../instances/commands/create_instance.rs | 6 +- .../state/instances/commands/list_content.rs | 26 +- .../instances/commands/sync_content_files.rs | 6 +- .../app-lib/src/state/instances/watcher.rs | 10 +- packages/app-lib/src/state/mod.rs | 9 +- packages/app-lib/src/state/mr_auth.rs | 10 +- packages/app-lib/src/state/process.rs | 8 +- packages/app-lib/src/util/fetch.rs | 67 +++-- 49 files changed, 306 insertions(+), 544 deletions(-) diff --git a/apps/app/src/api/cache.rs b/apps/app/src/api/cache.rs index 2e376ab492..29aa970551 100644 --- a/apps/app/src/api/cache.rs +++ b/apps/app/src/api/cache.rs @@ -12,7 +12,7 @@ macro_rules! impl_cache_methods { invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::cache::[](&context, id, cache_behaviour).await?) } @@ -23,7 +23,7 @@ macro_rules! impl_cache_methods { invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let ids = ids.iter().map(|x| &**x).collect::>(); let entries = theseus::cache::[](&context, &*ids, cache_behaviour).await?; @@ -82,7 +82,7 @@ pub async fn get_project_versions( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result>> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::cache::get_project_versions( &context, project_id, diff --git a/apps/app/src/api/friends.rs b/apps/app/src/api/friends.rs index 12b42437dc..7b9ad48ebb 100644 --- a/apps/app/src/api/friends.rs +++ b/apps/app/src/api/friends.rs @@ -16,7 +16,7 @@ pub fn init() -> TauriPlugin { pub async fn friends( invocation_context: theseus::InvocationContext, ) -> crate::api::Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::friends::friends(&context).await?) } @@ -30,7 +30,7 @@ pub async fn add_friend( user_id: &str, invocation_context: theseus::InvocationContext, ) -> crate::api::Result<()> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::friends::add_friend(&context, user_id).await?) } @@ -39,6 +39,6 @@ pub async fn remove_friend( user_id: &str, invocation_context: theseus::InvocationContext, ) -> crate::api::Result<()> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::friends::remove_friend(&context, user_id).await?) } diff --git a/apps/app/src/api/install.rs b/apps/app/src/api/install.rs index df30c9be61..a9a671dfee 100644 --- a/apps/app/src/api/install.rs +++ b/apps/app/src/api/install.rs @@ -69,7 +69,7 @@ pub async fn install_get_modpack_preview( location: CreatePackLocation, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok( theseus::pack::install_from::get_instance_from_pack(&context, location) .await?, @@ -81,7 +81,7 @@ pub async fn install_create_instance( request: InstallCreateInstanceRequest, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::create_instance( &context, request.name.trim().to_string(), @@ -103,7 +103,7 @@ pub async fn install_create_modpack_instance( post_install_edit: Option, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::create_modpack_instance( &context, location, @@ -119,7 +119,7 @@ pub async fn install_import_instance( instance_folder: String, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::import_instance( &context, launcher_type, @@ -134,7 +134,7 @@ pub async fn install_duplicate_instance( source_instance_id: String, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok( theseus::install::duplicate_instance(&context, source_instance_id) .await?, @@ -147,7 +147,7 @@ pub async fn install_existing_instance( force: bool, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::install_existing_instance( &context, instance_id, @@ -163,7 +163,7 @@ pub async fn install_pack_to_existing_instance( post_install_edit: Option, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::install_pack_to_existing_instance( &context, instance_id, @@ -190,7 +190,7 @@ pub async fn install_job_retry( job_id: Uuid, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::install::retry_job(&context, job_id).await?) } diff --git a/apps/app/src/api/instance.rs b/apps/app/src/api/instance.rs index c77a416c75..75f93d8bb1 100644 --- a/apps/app/src/api/instance.rs +++ b/apps/app/src/api/instance.rs @@ -439,7 +439,7 @@ pub async fn instance_get_projects( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok( theseus::instance::get_projects(&context, instance_id, cache_behaviour) .await?, @@ -451,7 +451,7 @@ pub async fn instance_get_installed_project_ids( instance_id: &str, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok( theseus::instance::get_installed_project_ids(&context, instance_id) .await?, @@ -488,7 +488,7 @@ pub async fn instance_get_content_items( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::get_content_items( &context, instance_id, @@ -503,7 +503,7 @@ pub async fn instance_get_dependencies_as_content_items( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::get_dependencies_as_content_items( &context, dependencies, @@ -518,7 +518,7 @@ pub async fn instance_get_linked_modpack_info( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::get_linked_modpack_info( &context, instance_id, @@ -533,7 +533,7 @@ pub async fn instance_get_linked_modpack_content( cache_behaviour: Option, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::get_linked_modpack_content( &context, instance_id, @@ -560,7 +560,7 @@ pub async fn instance_get_optimal_jre_key( instance_id: &str, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::get_optimal_jre_key(&context, instance_id).await?) } @@ -570,7 +570,7 @@ pub async fn instance_check_installed( project_id: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let check_project_id = project_id; if let Ok(projects) = @@ -592,7 +592,7 @@ pub async fn instance_update_all( instance_id: &str, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::update_all_projects(&context, instance_id).await?) } @@ -602,7 +602,7 @@ pub async fn instance_update_project( project_path: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::update_project( &context, instance_id, @@ -620,7 +620,7 @@ pub async fn instance_add_project_from_version( dependent_on_version_id: Option, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::add_project_from_version( &context, instance_id, @@ -637,7 +637,7 @@ pub async fn instance_install_project_with_dependencies( request: InstallProjectWithDependenciesRequest, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::install_project_with_dependencies( &context, instance_id, @@ -653,7 +653,7 @@ pub async fn instance_switch_project_version_with_dependencies( version_id: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::switch_project_version_with_dependencies( &context, instance_id, @@ -682,7 +682,7 @@ pub async fn instance_is_file_on_modrinth( project_path: &Path, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::is_file_on_modrinth(&context, project_path).await?) } @@ -715,7 +715,7 @@ pub async fn instance_update_managed_modrinth_version( version_id: String, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::instance::update_managed_modrinth_version( &context, &instance_id, @@ -729,7 +729,7 @@ pub async fn instance_repair_managed_modrinth( instance_id: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok( theseus::instance::repair_managed_modrinth(&context, instance_id) .await?, @@ -746,7 +746,7 @@ pub async fn instance_export_mrpack( name: Option, invocation_context: theseus::InvocationContext, ) -> Result<()> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; theseus::instance::export_mrpack( &context, instance_id, @@ -773,7 +773,7 @@ pub async fn instance_run( server_address: Option, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let quick_play = match server_address { Some(addr) => QuickPlayType::Server(ServerAddress::Unresolved(addr)), None => QuickPlayType::None, diff --git a/apps/app/src/api/jre.rs b/apps/app/src/api/jre.rs index ac383a4594..cdcc560a57 100644 --- a/apps/app/src/api/jre.rs +++ b/apps/app/src/api/jre.rs @@ -57,7 +57,7 @@ pub async fn jre_auto_install_java( java_version: u32, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(jre::auto_install_java(&context, java_version).await?) } diff --git a/apps/app/src/api/metadata.rs b/apps/app/src/api/metadata.rs index 67bb7dc6a7..fb4f13c32c 100644 --- a/apps/app/src/api/metadata.rs +++ b/apps/app/src/api/metadata.rs @@ -16,7 +16,7 @@ pub fn init() -> tauri::plugin::TauriPlugin { pub async fn metadata_get_game_versions( invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::metadata::get_minecraft_versions(&context).await?) } @@ -26,6 +26,6 @@ pub async fn metadata_get_loader_versions( loader: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::metadata::get_loader_versions(&context, loader).await?) } diff --git a/apps/app/src/api/mod.rs b/apps/app/src/api/mod.rs index 3ec0d32703..2d45357cbf 100644 --- a/apps/app/src/api/mod.rs +++ b/apps/app/src/api/mod.rs @@ -31,12 +31,6 @@ mod oauth_utils; pub type Result = std::result::Result; -pub(crate) fn operation_context( - invocation_context: theseus::InvocationContext, -) -> theseus::OperationContext { - invocation_context.into_operation_context() -} - // // Main returnable Theseus GUI error // // Needs to be Serializable to be returned to the JavaScript side // #[derive(Error, Debug, Serialize)] diff --git a/apps/app/src/api/mr_auth.rs b/apps/app/src/api/mr_auth.rs index f7c92e3981..93e6d7bccc 100644 --- a/apps/app/src/api/mr_auth.rs +++ b/apps/app/src/api/mr_auth.rs @@ -24,7 +24,7 @@ pub async fn modrinth_login( app: tauri::AppHandle, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let (auth_code_recv_socket_tx, auth_code_recv_socket) = oneshot::channel(); let auth_code = tokio::spawn(oauth_utils::auth_code_reply::listen( auth_code_recv_socket_tx, @@ -79,7 +79,7 @@ pub async fn logout() -> Result<()> { pub async fn get( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::mr_auth::get_credentials(&context).await?) } diff --git a/apps/app/src/api/tags.rs b/apps/app/src/api/tags.rs index 4a3839b2ac..61cadf6a94 100644 --- a/apps/app/src/api/tags.rs +++ b/apps/app/src/api/tags.rs @@ -18,7 +18,7 @@ pub fn init() -> tauri::plugin::TauriPlugin { pub async fn tags_get_categories( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::tags::get_category_tags(&context).await?) } @@ -27,7 +27,7 @@ pub async fn tags_get_categories( pub async fn tags_get_report_types( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::tags::get_report_type_tags(&context).await?) } @@ -36,7 +36,7 @@ pub async fn tags_get_report_types( pub async fn tags_get_loaders( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::tags::get_loader_tags(&context).await?) } @@ -45,7 +45,7 @@ pub async fn tags_get_loaders( pub async fn tags_get_game_versions( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::tags::get_game_version_tags(&context).await?) } @@ -54,6 +54,6 @@ pub async fn tags_get_game_versions( pub async fn tags_get_donation_platforms( invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(theseus::tags::get_donation_platform_tags(&context).await?) } diff --git a/apps/app/src/api/worlds.rs b/apps/app/src/api/worlds.rs index b1e2aa3426..0f5a1d3771 100644 --- a/apps/app/src/api/worlds.rs +++ b/apps/app/src/api/worlds.rs @@ -198,7 +198,7 @@ pub async fn get_instance_protocol_version( instance_id: &str, invocation_context: theseus::InvocationContext, ) -> Result> { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; Ok(worlds::get_instance_protocol_version(&context, instance_id).await?) } @@ -216,7 +216,7 @@ pub async fn start_join_singleplayer_world( world: String, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let process = instance::run( &context, instance_id, @@ -233,7 +233,7 @@ pub async fn start_join_server( address: &str, invocation_context: theseus::InvocationContext, ) -> Result { - let context = crate::api::operation_context(invocation_context); + let context = invocation_context; let process = instance::run( &context, instance_id, diff --git a/packages/app-lib/src/api/cache.rs b/packages/app-lib/src/api/cache.rs index 8bd8b60ad3..80c2de7956 100644 --- a/packages/app-lib/src/api/cache.rs +++ b/packages/app-lib/src/api/cache.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::{ CacheBehaviour, CacheValueType, CachedEntry, Organization, Project, ProjectV3, SearchResults, SearchResultsV3, TeamMember, User, Version, @@ -10,7 +10,7 @@ macro_rules! impl_cache_methods { paste::paste! { #[tracing::instrument] pub async fn []( - context: &OperationContext, + context: &InvocationContext, id: &str, cache_behaviour: Option, ) -> crate::Result> @@ -21,7 +21,7 @@ macro_rules! impl_cache_methods { #[tracing::instrument] pub async fn []( - context: &OperationContext, + context: &InvocationContext, ids: &[&str], cache_behaviour: Option, ) -> crate::Result> @@ -61,7 +61,7 @@ pub async fn purge_cache_types( /// Uses the cache system with the ProjectVersions cache type. #[tracing::instrument] pub async fn get_project_versions( - context: &OperationContext, + context: &InvocationContext, project_id: &str, cache_behaviour: Option, ) -> crate::Result>> { diff --git a/packages/app-lib/src/api/friends.rs b/packages/app-lib/src/api/friends.rs index 2a3e54a9b6..1028561ff2 100644 --- a/packages/app-lib/src/api/friends.rs +++ b/packages/app-lib/src/api/friends.rs @@ -1,10 +1,10 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::{FriendsSocket, UserFriend}; use ariadne::users::UserStatus; #[tracing::instrument] pub async fn friends( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = crate::State::get().await?; let friends = @@ -23,7 +23,7 @@ pub async fn friend_statuses() -> crate::Result> { #[tracing::instrument] pub async fn add_friend( - context: &OperationContext, + context: &InvocationContext, user_id: &str, ) -> crate::Result<()> { let state = crate::State::get().await?; @@ -40,7 +40,7 @@ pub async fn add_friend( #[tracing::instrument] pub async fn remove_friend( - context: &OperationContext, + context: &InvocationContext, user_id: &str, ) -> crate::Result<()> { let state = crate::State::get().await?; diff --git a/packages/app-lib/src/api/instance/content.rs b/packages/app-lib/src/api/instance/content.rs index 01299cb87a..8e140632c9 100644 --- a/packages/app-lib/src/api/instance/content.rs +++ b/packages/app-lib/src/api/instance/content.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::{ CacheBehaviour, ContentFile, ContentItem, ContentSet, Dependency, InstanceInstallCandidate, InstanceInstallTarget, LinkedModpackInfo, @@ -8,7 +8,7 @@ use dashmap::DashMap; #[tracing::instrument] pub async fn sync_content_files( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -25,7 +25,7 @@ pub async fn list_content_sets( #[tracing::instrument] pub async fn get_projects( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { @@ -42,7 +42,7 @@ pub async fn get_projects( #[tracing::instrument] pub async fn get_installed_project_ids( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -73,7 +73,7 @@ pub async fn get_install_candidates( #[tracing::instrument] pub async fn get_content_items( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { @@ -90,7 +90,7 @@ pub async fn get_content_items( #[tracing::instrument] pub async fn get_linked_modpack_content( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { @@ -107,7 +107,7 @@ pub async fn get_linked_modpack_content( #[tracing::instrument] pub async fn get_dependencies_as_content_items( - context: &OperationContext, + context: &InvocationContext, dependencies: Vec, cache_behaviour: Option, ) -> crate::Result> { @@ -124,7 +124,7 @@ pub async fn get_dependencies_as_content_items( #[tracing::instrument] pub async fn get_linked_modpack_info( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, cache_behaviour: Option, ) -> crate::Result> { diff --git a/packages/app-lib/src/api/instance/export_mrpack.rs b/packages/app-lib/src/api/instance/export_mrpack.rs index 54d3b2d527..5f0a53b208 100644 --- a/packages/app-lib/src/api/instance/export_mrpack.rs +++ b/packages/app-lib/src/api/instance/export_mrpack.rs @@ -1,7 +1,7 @@ use super::content::get_projects; use super::get::get; use super::paths::get_full_path; -use crate::OperationContext; +use crate::InvocationContext; use crate::event::LoadingBarType; use crate::event::emit::{emit_loading, init_loading}; use crate::pack::install_from::{ @@ -21,7 +21,7 @@ use tokio::io::AsyncReadExt; #[tracing::instrument(skip_all)] pub async fn export_mrpack( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, export_path: PathBuf, included_export_candidates: Vec, @@ -178,7 +178,7 @@ fn pack_get_relative_path( #[tracing::instrument(skip_all)] pub async fn create_mrpack_json( - context: &OperationContext, + context: &InvocationContext, metadata: &InstanceMetadata, version_id: String, description: Option, diff --git a/packages/app-lib/src/api/instance/install.rs b/packages/app-lib/src/api/instance/install.rs index 8c5fa45c68..36ed097b92 100644 --- a/packages/app-lib/src/api/instance/install.rs +++ b/packages/app-lib/src/api/instance/install.rs @@ -1,10 +1,10 @@ use crate::{ - OperationContext, + InvocationContext, state::{JavaVersion, State}, }; pub async fn get_optimal_jre_key( - operation_context: &OperationContext, + invocation_context: &InvocationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -21,21 +21,21 @@ pub async fn get_optimal_jre_key( })?; let (minecraft, version_index) = crate::launcher::resolve_minecraft_manifest( - operation_context, + invocation_context, &context.applied_content_set.game_version, &state, ) .await?; let version = &minecraft.versions[version_index]; let loader_version = crate::launcher::get_loader_version_from_profile( - operation_context, + invocation_context, &context.applied_content_set.game_version, context.applied_content_set.loader, context.applied_content_set.loader_version.as_deref(), ) .await?; let version_info = crate::launcher::download::download_version_info( - operation_context, + invocation_context, &state, version, loader_version.as_ref(), diff --git a/packages/app-lib/src/api/instance/lifecycle.rs b/packages/app-lib/src/api/instance/lifecycle.rs index c362225bb8..ffdace8ae6 100644 --- a/packages/app-lib/src/api/instance/lifecycle.rs +++ b/packages/app-lib/src/api/instance/lifecycle.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::event::InstancePayloadType; use crate::event::emit::emit_instance; use crate::state::instances::adapters::sqlite::instance_rows; @@ -12,7 +12,7 @@ use std::path::Path; #[tracing::instrument] #[allow(clippy::too_many_arguments)] pub(crate) async fn create( - context: &OperationContext, + context: &InvocationContext, name: String, game_version: String, modloader: ModLoader, diff --git a/packages/app-lib/src/api/instance/projects.rs b/packages/app-lib/src/api/instance/projects.rs index 026468b87b..285849ab05 100644 --- a/packages/app-lib/src/api/instance/projects.rs +++ b/packages/app-lib/src/api/instance/projects.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::event::emit::{emit_instance, emit_loading, init_loading}; use crate::event::{InstancePayloadType, LoadingBarType}; use crate::state::instances::adapters::sqlite::instance_rows; @@ -21,7 +21,7 @@ pub struct InstallProjectWithDependenciesRequest { #[tracing::instrument] pub async fn update_all_projects( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, ) -> crate::Result> { let state = State::get().await?; @@ -49,7 +49,7 @@ pub async fn update_all_projects( #[tracing::instrument] pub async fn update_project( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, project_path: &str, skip_send_event: Option, @@ -72,7 +72,7 @@ pub async fn update_project( #[tracing::instrument] pub async fn add_project_from_version( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, version_id: &str, reason: fetch::DownloadReason, @@ -97,7 +97,7 @@ pub async fn add_project_from_version( #[tracing::instrument] pub async fn install_project_with_dependencies( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, request: InstallProjectWithDependenciesRequest, ) -> crate::Result { @@ -187,7 +187,7 @@ fn plan_project_ids(plan: &ResolveContentPlan) -> Vec { #[tracing::instrument] pub async fn switch_project_version_with_dependencies( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, project_path: &str, version_id: &str, @@ -228,7 +228,7 @@ pub async fn add_project_from_path( #[tracing::instrument] pub async fn is_file_on_modrinth( - context: &OperationContext, + context: &InvocationContext, path: &Path, ) -> crate::Result { let state = State::get().await?; @@ -283,7 +283,7 @@ pub async fn remove_project( #[tracing::instrument] pub async fn update_managed_modrinth_version( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, version_id: &str, ) -> crate::Result { @@ -343,7 +343,7 @@ pub async fn update_managed_modrinth_version( #[tracing::instrument] pub async fn repair_managed_modrinth( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, ) -> crate::Result { let state = State::get().await?; diff --git a/packages/app-lib/src/api/instance/run.rs b/packages/app-lib/src/api/instance/run.rs index 6ece6b9ea5..a9f1a2d92c 100644 --- a/packages/app-lib/src/api/instance/run.rs +++ b/packages/app-lib/src/api/instance/run.rs @@ -1,5 +1,5 @@ use super::content::get_projects; -use crate::OperationContext; +use crate::InvocationContext; use crate::server_address::ServerAddress; use crate::state::{ Credentials, InstanceLink, ProcessMetadata, Settings, State, @@ -21,7 +21,7 @@ pub enum QuickPlayType { #[tracing::instrument] pub async fn run( - operation_context: &OperationContext, + invocation_context: &InvocationContext, instance_id: &str, quick_play_type: QuickPlayType, ) -> crate::Result { @@ -31,7 +31,7 @@ pub async fn run( .ok_or_else(|| crate::ErrorKind::NoCredentialsError.as_error())?; run_credentials( - operation_context, + invocation_context, instance_id, &default_account, quick_play_type, @@ -41,7 +41,7 @@ pub async fn run( #[tracing::instrument(skip(credentials))] async fn run_credentials( - operation_context: &OperationContext, + invocation_context: &InvocationContext, instance_id: &str, credentials: &Credentials, quick_play_type: QuickPlayType, @@ -160,7 +160,7 @@ async fn run_credentials( match join_result { Ok(resp) if resp.status().is_success() => { let result = fetch::post_json( - operation_context, + invocation_context, concat!( env!("MODRINTH_API_BASE_URL"), "analytics/minecraft-server-play" @@ -194,7 +194,7 @@ async fn run_credentials( crate::minecraft_skins::flush_pending_skin_change().await?; crate::launcher::launch_minecraft( - operation_context, + invocation_context, &java_args, &env_args, &mc_set_options, @@ -238,7 +238,7 @@ pub async fn kill(instance_id: &str) -> crate::Result<()> { #[tracing::instrument] pub async fn try_update_playtime_by_instance_id( - operation_context: &OperationContext, + invocation_context: &InvocationContext, instance_id: &str, ) -> crate::Result<()> { let state = State::get().await?; @@ -280,7 +280,7 @@ pub async fn try_update_playtime_by_instance_id( let mut hashmap: HashMap = HashMap::new(); for (_, project) in - get_projects(operation_context, instance_id, None).await? + get_projects(invocation_context, instance_id, None).await? { if let Some(metadata) = project.metadata { hashmap @@ -289,7 +289,7 @@ pub async fn try_update_playtime_by_instance_id( } fetch::post_json( - operation_context, + invocation_context, concat!(env!("MODRINTH_API_BASE_URL"), "analytics/playtime"), serde_json::to_value(hashmap)?, &state.api_semaphore, diff --git a/packages/app-lib/src/api/jre.rs b/packages/app-lib/src/api/jre.rs index 3de339ac59..16236e9205 100644 --- a/packages/app-lib/src/api/jre.rs +++ b/packages/app-lib/src/api/jre.rs @@ -19,7 +19,7 @@ use sysinfo::{MemoryRefreshKind, RefreshKind}; use crate::util::io; use crate::util::jre::extract_java_version; use crate::{ - LoadingBarType, OperationContext, State, + InvocationContext, LoadingBarType, State, util::jre::{self}, }; @@ -60,14 +60,14 @@ pub async fn find_filtered_jres( } pub async fn auto_install_java( - context: &OperationContext, + context: &InvocationContext, java_version: u32, ) -> crate::Result { auto_install_java_with_loading(context, java_version, true).await } pub async fn auto_install_java_with_loading( - context: &OperationContext, + context: &InvocationContext, java_version: u32, show_loading: bool, ) -> crate::Result { @@ -75,7 +75,7 @@ pub async fn auto_install_java_with_loading( } pub async fn auto_install_java_with_reporter( - context: &OperationContext, + context: &InvocationContext, java_version: u32, reporter: InstallProgressReporter, ) -> crate::Result { @@ -116,7 +116,7 @@ fn java_step_progress(current: u64) -> InstallProgress { } async fn auto_install_java_inner( - context: &OperationContext, + context: &InvocationContext, java_version: u32, show_loading: bool, reporter: Option, diff --git a/packages/app-lib/src/api/metadata.rs b/packages/app-lib/src/api/metadata.rs index 58e0d236dd..1754a2c625 100644 --- a/packages/app-lib/src/api/metadata.rs +++ b/packages/app-lib/src/api/metadata.rs @@ -1,11 +1,11 @@ use crate::state::CachedEntry; -use crate::{OperationContext, State}; +use crate::{InvocationContext, State}; pub use daedalus::minecraft::VersionManifest; pub use daedalus::modded::Manifest; #[tracing::instrument] pub async fn get_minecraft_versions( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result { let state = State::get().await?; let minecraft_versions = CachedEntry::get_minecraft_manifest( @@ -24,7 +24,7 @@ pub async fn get_minecraft_versions( // #[tracing::instrument] pub async fn get_loader_versions( - context: &OperationContext, + context: &InvocationContext, loader: &str, ) -> crate::Result { let state = State::get().await?; diff --git a/packages/app-lib/src/api/mod.rs b/packages/app-lib/src/api/mod.rs index 67cbd81ed1..9e5f326ef8 100644 --- a/packages/app-lib/src/api/mod.rs +++ b/packages/app-lib/src/api/mod.rs @@ -37,7 +37,7 @@ pub mod data { pub mod prelude { pub use crate::{ - InvocationContext, OperationCause, OperationContext, State, + InvocationContext, State, data::*, event::CommandPayload, install, instance, jre, metadata, minecraft_auth, mr_auth, pack, diff --git a/packages/app-lib/src/api/mr_auth.rs b/packages/app-lib/src/api/mr_auth.rs index 68ba3cc40f..56d27efa26 100644 --- a/packages/app-lib/src/api/mr_auth.rs +++ b/packages/app-lib/src/api/mr_auth.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::ModrinthCredentials; #[tracing::instrument] @@ -8,7 +8,7 @@ pub fn authenticate_begin_flow() -> &'static str { #[tracing::instrument] pub async fn authenticate_finish_flow( - context: &OperationContext, + context: &InvocationContext, code: &str, ) -> crate::Result { let state = crate::State::get().await?; @@ -50,7 +50,7 @@ pub async fn logout() -> crate::Result<()> { #[tracing::instrument] pub async fn get_credentials( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = crate::State::get().await?; let current = ModrinthCredentials::get_and_refresh( diff --git a/packages/app-lib/src/api/pack/import/atlauncher.rs b/packages/app-lib/src/api/pack/import/atlauncher.rs index 2d525ace4d..89b8961842 100644 --- a/packages/app-lib/src/api/pack/import/atlauncher.rs +++ b/packages/app-lib/src/api/pack/import/atlauncher.rs @@ -125,7 +125,7 @@ pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool { #[tracing::instrument] pub async fn import_atlauncher( - context: &crate::OperationContext, + context: &crate::InvocationContext, atlauncher_base_path: PathBuf, // path to base atlauncher folder instance_folder: String, // instance folder in atlauncher_base_path instance_id: &str, @@ -193,7 +193,7 @@ pub async fn import_atlauncher( } async fn import_atlauncher_unmanaged( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: &str, minecraft_folder: PathBuf, backup_name: String, diff --git a/packages/app-lib/src/api/pack/import/curseforge.rs b/packages/app-lib/src/api/pack/import/curseforge.rs index 428eacfc32..234f64228f 100644 --- a/packages/app-lib/src/api/pack/import/curseforge.rs +++ b/packages/app-lib/src/api/pack/import/curseforge.rs @@ -49,7 +49,7 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool { } pub async fn import_curseforge( - context: &crate::OperationContext, + context: &crate::InvocationContext, curseforge_instance_folder: PathBuf, // instance's folder instance_id: &str, reporter: InstallProgressReporter, diff --git a/packages/app-lib/src/api/pack/import/gdlauncher.rs b/packages/app-lib/src/api/pack/import/gdlauncher.rs index 34c2ae77aa..6cccb665de 100644 --- a/packages/app-lib/src/api/pack/import/gdlauncher.rs +++ b/packages/app-lib/src/api/pack/import/gdlauncher.rs @@ -41,7 +41,7 @@ pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool { } pub async fn import_gdlauncher( - context: &crate::OperationContext, + context: &crate::InvocationContext, gdlauncher_instance_folder: PathBuf, // instance's folder instance_id: &str, reporter: InstallProgressReporter, diff --git a/packages/app-lib/src/api/pack/import/mmc.rs b/packages/app-lib/src/api/pack/import/mmc.rs index 446f17604d..60ef0e56a2 100644 --- a/packages/app-lib/src/api/pack/import/mmc.rs +++ b/packages/app-lib/src/api/pack/import/mmc.rs @@ -177,7 +177,7 @@ async fn load_instance_cfg(file_path: &Path) -> crate::Result { // #[tracing::instrument] pub async fn import_mmc( - context: &crate::OperationContext, + context: &crate::InvocationContext, mmc_base_path: PathBuf, // path to base mmc folder instance_folder: String, // instance folder in mmc_base_path instance_id: &str, @@ -268,7 +268,7 @@ pub async fn import_mmc( } async fn import_mmc_unmanaged( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: &str, minecraft_folder: PathBuf, backup_name: String, diff --git a/packages/app-lib/src/api/pack/import/mod.rs b/packages/app-lib/src/api/pack/import/mod.rs index 0fffedab49..b96375d44f 100644 --- a/packages/app-lib/src/api/pack/import/mod.rs +++ b/packages/app-lib/src/api/pack/import/mod.rs @@ -115,7 +115,7 @@ pub async fn get_importable_instances( } pub(crate) async fn import_instance_with_reporter( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: &str, launcher_type: ImportLauncherType, base_path: PathBuf, @@ -134,7 +134,7 @@ pub(crate) async fn import_instance_with_reporter( } async fn import_instance_inner( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: &str, launcher_type: ImportLauncherType, base_path: PathBuf, @@ -416,7 +416,7 @@ pub(crate) async fn copy_dotminecraft_with_reporter( } pub(crate) async fn finish_import( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: &str, dotminecraft: PathBuf, io_semaphore: &IoSemaphore, diff --git a/packages/app-lib/src/api/pack/install_from.rs b/packages/app-lib/src/api/pack/install_from.rs index 171f546116..5d65af2341 100644 --- a/packages/app-lib/src/api/pack/install_from.rs +++ b/packages/app-lib/src/api/pack/install_from.rs @@ -164,7 +164,7 @@ pub struct CreatePackDescription { } pub async fn get_instance_from_pack( - context: &crate::OperationContext, + context: &crate::InvocationContext, location: CreatePackLocation, ) -> crate::Result { match location { @@ -237,7 +237,7 @@ pub async fn get_instance_from_pack( #[tracing::instrument(skip(reporter))] #[allow(clippy::too_many_arguments)] pub(crate) async fn generate_pack_from_version_id_with_reporter( - context: &crate::OperationContext, + context: &crate::InvocationContext, project_id: String, version_id: String, title: String, @@ -492,7 +492,7 @@ pub async fn generate_pack_from_file( /// Sets generated instance attributes to the pack ones. /// This includes the pack name, icon, game version, loader version, and loader pub async fn set_instance_information( - context: &crate::OperationContext, + context: &crate::InvocationContext, instance_id: String, description: &CreatePackDescription, backup_name: &str, diff --git a/packages/app-lib/src/api/pack/install_mrpack.rs b/packages/app-lib/src/api/pack/install_mrpack.rs index 0eb8f7736e..b866075d18 100644 --- a/packages/app-lib/src/api/pack/install_mrpack.rs +++ b/packages/app-lib/src/api/pack/install_mrpack.rs @@ -238,7 +238,7 @@ where } pub(crate) async fn get_external_files_from_mrpack( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, file: &CreatePackFile, ) -> crate::Result> { let mut zip_reader = MrpackZipReader::new(file).await?; @@ -300,7 +300,7 @@ pub(crate) async fn get_external_files_from_mrpack( .map(|(_, hash)| hash.as_str()) .collect::>(); let recognized_hashes = match CachedEntry::get_file_many( - operation_context, + invocation_context, &hashes, None, &state.pool, @@ -398,7 +398,7 @@ where } pub(crate) async fn install_zipped_mrpack_files_with_reporter( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, create_pack: CreatePack, ignore_lock: bool, reason: DownloadReason, @@ -568,7 +568,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( } set_instance_information( - operation_context, + invocation_context, instance_id.clone(), &description, &pack.name, @@ -633,7 +633,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( .collect::>(); let file_infos_by_hash = Arc::new( CachedEntry::get_file_many( - operation_context, + invocation_context, &file_info_hashes, None, &state.pool, @@ -769,7 +769,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( let progress = &mut report_download_progress as &mut FetchProgressFn<'_>; let file = match fetch_mirrors_with_progress( - operation_context, + invocation_context, &project .downloads .iter() @@ -1066,7 +1066,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter( } crate::launcher::install_minecraft_for_instance_id_with_reporter( - operation_context, + invocation_context, &instance_id, false, Some(reporter.clone()), @@ -1095,7 +1095,7 @@ fn modpack_source_kind(version_id: Option<&str>) -> ContentSourceKind { #[tracing::instrument(skip(mrpack_file))] pub async fn remove_all_related_files( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, instance_id: String, mrpack_file: CreatePackFile, ) -> crate::Result<()> { @@ -1153,7 +1153,7 @@ pub async fn remove_all_related_files( // First, get project info by hash let file_infos = CachedEntry::get_file_many( - operation_context, + invocation_context, &all_hashes.iter().map(|x| &**x).collect::>(), None, &state.pool, diff --git a/packages/app-lib/src/api/tags.rs b/packages/app-lib/src/api/tags.rs index f1560589ea..3d36e35351 100644 --- a/packages/app-lib/src/api/tags.rs +++ b/packages/app-lib/src/api/tags.rs @@ -1,14 +1,14 @@ //! Theseus tag management interface use crate::state::CachedEntry; pub use crate::{ - OperationContext, State, + InvocationContext, State, state::{Category, DonationPlatform, GameVersion, Loader}, }; /// Get category tags #[tracing::instrument] pub async fn get_category_tags( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = State::get().await?; let categories = CachedEntry::get_categories( @@ -26,7 +26,7 @@ pub async fn get_category_tags( /// Get report type tags #[tracing::instrument] pub async fn get_report_type_tags( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = State::get().await?; let report_types = CachedEntry::get_report_types( @@ -46,7 +46,7 @@ pub async fn get_report_type_tags( /// Get loader tags #[tracing::instrument] pub async fn get_loader_tags( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = State::get().await?; let loaders = CachedEntry::get_loaders( @@ -64,7 +64,7 @@ pub async fn get_loader_tags( /// Get game version tags #[tracing::instrument] pub async fn get_game_version_tags( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = State::get().await?; let game_versions = CachedEntry::get_game_versions( @@ -84,7 +84,7 @@ pub async fn get_game_version_tags( /// Get donation platform tags #[tracing::instrument] pub async fn get_donation_platform_tags( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result> { let state = State::get().await?; let donation_platforms = CachedEntry::get_donation_platforms( diff --git a/packages/app-lib/src/api/worlds.rs b/packages/app-lib/src/api/worlds.rs index 2b58d6162c..7d394b19da 100644 --- a/packages/app-lib/src/api/worlds.rs +++ b/packages/app-lib/src/api/worlds.rs @@ -923,7 +923,7 @@ mod servers_data { } pub async fn get_instance_protocol_version( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, instance_id: &str, ) -> Result> { let metadata = @@ -952,7 +952,7 @@ pub async fn get_instance_protocol_version( let state = State::get().await?; let (minecraft, version_index) = crate::launcher::resolve_minecraft_manifest( - operation_context, + invocation_context, &metadata.applied_content_set.game_version, &state, ) @@ -960,7 +960,7 @@ pub async fn get_instance_protocol_version( let version = &minecraft.versions[version_index]; let loader_version = get_loader_version_from_profile( - operation_context, + invocation_context, &metadata.applied_content_set.game_version, metadata.applied_content_set.loader, metadata.applied_content_set.loader_version.as_deref(), diff --git a/packages/app-lib/src/install/runner.rs b/packages/app-lib/src/install/runner.rs index 6e4e854b31..43d5451c59 100644 --- a/packages/app-lib/src/install/runner.rs +++ b/packages/app-lib/src/install/runner.rs @@ -18,13 +18,13 @@ use crate::state::{ ContentSourceKind, InstanceInstallStage, InstanceLink, ModLoader, State, }; use crate::util::fetch::DownloadReason; -use crate::{ErrorKind, OperationContext}; +use crate::{ErrorKind, InvocationContext}; use std::collections::HashSet; use std::path::PathBuf; use uuid::Uuid; pub async fn create_instance( - context: &OperationContext, + context: &InvocationContext, name: String, game_version: String, loader: ModLoader, @@ -47,7 +47,7 @@ pub async fn create_instance( } pub async fn create_modpack_instance( - context: &OperationContext, + context: &InvocationContext, location: CreatePackLocation, post_install_edit: Option, ) -> crate::Result { @@ -62,7 +62,7 @@ pub async fn create_modpack_instance( } pub async fn import_instance( - context: &OperationContext, + context: &InvocationContext, launcher_type: crate::api::pack::import::ImportLauncherType, base_path: PathBuf, instance_folder: String, @@ -79,7 +79,7 @@ pub async fn import_instance( } pub async fn duplicate_instance( - context: &OperationContext, + context: &InvocationContext, source_instance_id: String, ) -> crate::Result { start( @@ -90,7 +90,7 @@ pub async fn duplicate_instance( } pub async fn install_existing_instance( - context: &OperationContext, + context: &InvocationContext, instance_id: String, force: bool, ) -> crate::Result { @@ -102,7 +102,7 @@ pub async fn install_existing_instance( } pub async fn install_pack_to_existing_instance( - context: &OperationContext, + context: &InvocationContext, instance_id: String, location: CreatePackLocation, post_install_edit: Option, @@ -141,7 +141,7 @@ pub async fn job_support_details(job_id: Uuid) -> crate::Result { } pub async fn retry_job( - context: &OperationContext, + context: &InvocationContext, job_id: Uuid, ) -> crate::Result { let state = State::get().await?; @@ -244,7 +244,7 @@ pub async fn dismiss_job(job_id: Uuid) -> crate::Result<()> { } async fn start( - context: &OperationContext, + context: &InvocationContext, request: InstallRequest, ) -> crate::Result { let state = State::get().await?; @@ -259,7 +259,7 @@ async fn start( } async fn prepare_initial_instance( - context: &OperationContext, + context: &InvocationContext, job_state: &mut InstallJobState, state: &State, ) -> crate::Result<()> { @@ -388,7 +388,7 @@ async fn prepare_initial_instance( Ok(()) } -fn spawn_job(job_id: Uuid, context: OperationContext) { +fn spawn_job(job_id: Uuid, context: InvocationContext) { tokio::spawn(async move { if let Err(error) = run_job(&context, job_id).await { tracing::error!( @@ -399,7 +399,7 @@ fn spawn_job(job_id: Uuid, context: OperationContext) { } async fn run_job( - context: &OperationContext, + context: &InvocationContext, job_id: Uuid, ) -> crate::Result<()> { let state = State::get().await?; @@ -508,7 +508,7 @@ async fn run_job( } async fn run_request( - context: &OperationContext, + context: &InvocationContext, job_id: Uuid, job_state: &mut InstallJobState, state: &State, @@ -763,7 +763,7 @@ async fn apply_post_install_edit( } async fn remove_existing_pack_content( - context: &OperationContext, + context: &InvocationContext, job_id: Uuid, job_state: &InstallJobState, state: &State, @@ -922,7 +922,7 @@ async fn restore_disabled_projects( } async fn install_pack( - context: &OperationContext, + context: &InvocationContext, job_id: Uuid, job_state: &mut InstallJobState, location: CreatePackLocation, diff --git a/packages/app-lib/src/launcher/download.rs b/packages/app-lib/src/launcher/download.rs index acb0a0ac0b..abc2a6254b 100644 --- a/packages/app-lib/src/launcher/download.rs +++ b/packages/app-lib/src/launcher/download.rs @@ -7,7 +7,7 @@ use crate::install::{ use crate::instance::QuickPlayType; use crate::launcher::parse_rules; use crate::{ - OperationContext, + InvocationContext, event::{ LoadingBarId, emit::{emit_loading, loading_try_for_each_concurrent}, @@ -143,7 +143,7 @@ impl MinecraftDownloadProgress { } async fn fetch_minecraft_file( - operation_context: &OperationContext, + invocation_context: &InvocationContext, st: &State, url: &str, sha1: Option<&str>, @@ -161,7 +161,7 @@ async fn fetch_minecraft_file( let Some(progress) = progress else { return fetch( - operation_context, + invocation_context, url, sha1, None, @@ -188,7 +188,7 @@ async fn fetch_minecraft_file( }; let bytes = match fetch_advanced_with_progress( - operation_context, + invocation_context, Method::GET, url, sha1, @@ -395,7 +395,7 @@ fn missing_initial_minecraft_bytes( #[tracing::instrument(skip(st, version))] pub async fn download_minecraft( - context: &OperationContext, + context: &InvocationContext, st: &State, version: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -467,7 +467,7 @@ pub async fn download_minecraft( #[tracing::instrument(skip_all, fields(version = version.id.as_str(), loader = ?loader))] pub async fn download_version_info( - context: &OperationContext, + context: &InvocationContext, st: &State, version: &GameVersion, loader: Option<&LoaderVersion>, @@ -564,7 +564,7 @@ pub async fn download_version_info( #[tracing::instrument(skip_all)] pub async fn download_client( - context: &OperationContext, + context: &InvocationContext, st: &State, version_info: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -616,7 +616,7 @@ pub async fn download_client( #[tracing::instrument(skip_all)] pub async fn download_assets_index( - context: &OperationContext, + context: &InvocationContext, st: &State, version: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, @@ -665,7 +665,7 @@ pub async fn download_assets_index( #[tracing::instrument(skip(st, index))] pub async fn download_assets( - context: &OperationContext, + context: &InvocationContext, st: &State, with_legacy: bool, index: &AssetsIndex, @@ -763,7 +763,7 @@ pub async fn download_assets( #[tracing::instrument(skip(st, libraries))] #[allow(clippy::too_many_arguments)] pub async fn download_libraries( - context: &OperationContext, + context: &InvocationContext, st: &State, libraries: &[Library], version: &str, @@ -965,7 +965,7 @@ pub async fn download_libraries( #[tracing::instrument(skip_all)] pub async fn download_log_config( - context: &OperationContext, + context: &InvocationContext, st: &State, version_info: &GameVersionInfo, loading_bar: Option<&LoadingBarId>, diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index fc3967a120..3712488695 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -156,7 +156,7 @@ pub async fn get_java_version_from_launch_context( } pub async fn get_loader_version_from_profile( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, game_version: &str, loader: ModLoader, loader_version: Option<&str>, @@ -174,7 +174,7 @@ pub async fn get_loader_version_from_profile( }; let versions = crate::api::metadata::get_loader_versions( - operation_context, + invocation_context, loader.as_meta_str(), ) .await?; @@ -222,12 +222,13 @@ fn loader_versions_for_game_version<'a>( /// game version. If the version isn't found in the cache, forces a manifest /// refresh to pick up newly-released versions. pub async fn resolve_minecraft_manifest( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, game_version: &str, state: &State, ) -> crate::Result<(d::minecraft::VersionManifest, usize)> { let minecraft = - crate::api::metadata::get_minecraft_versions(operation_context).await?; + crate::api::metadata::get_minecraft_versions(invocation_context) + .await?; if let Some(idx) = minecraft .versions @@ -240,7 +241,7 @@ pub async fn resolve_minecraft_manifest( // Version not found in cache — force a manifest refresh in case it was // released after the cache was populated. let refreshed = crate::state::CachedEntry::get_minecraft_manifest( - operation_context, + invocation_context, Some(crate::state::CacheBehaviour::MustRevalidate), &state.pool, &state.api_semaphore, @@ -269,7 +270,7 @@ async fn get_instance_full_path(instance_path: &str) -> crate::Result { } pub async fn install_minecraft_with_reporter( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, launch_context: &InstanceLaunchContext, repairing: bool, reporter: Option, @@ -318,7 +319,7 @@ pub async fn install_minecraft_with_reporter( .await?; } let (minecraft, version_index) = resolve_minecraft_manifest( - operation_context, + invocation_context, &content_set.game_version, &state, ) @@ -344,7 +345,7 @@ pub async fn install_minecraft_with_reporter( } let mut loader_version = get_loader_version_from_profile( - operation_context, + invocation_context, &content_set.game_version, content_set.loader, content_set.loader_version.as_deref(), @@ -354,7 +355,7 @@ pub async fn install_minecraft_with_reporter( // If no loader version is selected, try to select the stable version! if content_set.loader != ModLoader::Vanilla && loader_version.is_none() { loader_version = get_loader_version_from_profile( - operation_context, + invocation_context, &content_set.game_version, content_set.loader, Some("stable"), @@ -376,7 +377,7 @@ pub async fn install_minecraft_with_reporter( // Download version info (5) let mut version_info = download::download_version_info( - operation_context, + invocation_context, &state, version, loader_version.as_ref(), @@ -414,14 +415,14 @@ pub async fn install_minecraft_with_reporter( } else { let path = if let Some(reporter) = &reporter { crate::api::jre::auto_install_java_with_reporter( - operation_context, + invocation_context, key, reporter.clone(), ) .await? } else { crate::api::jre::auto_install_java_with_loading( - operation_context, + invocation_context, key, true, ) @@ -465,7 +466,7 @@ pub async fn install_minecraft_with_reporter( .await?; } download::download_minecraft( - operation_context, + invocation_context, &state, &version_info, loading_bar.as_ref(), @@ -645,7 +646,7 @@ pub async fn install_minecraft_with_reporter( } pub async fn install_minecraft_for_instance_id_with_reporter( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, instance_id: &str, repairing: bool, reporter: Option, @@ -664,7 +665,7 @@ pub async fn install_minecraft_for_instance_id_with_reporter( })?; install_minecraft_with_reporter( - operation_context, + invocation_context, &launch_context, repairing, reporter, @@ -728,7 +729,7 @@ fn link_project_and_version( #[tracing::instrument(skip_all)] #[allow(clippy::too_many_arguments)] pub async fn launch_minecraft( - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, java_args: &[String], env_args: &[(String, String)], mc_set_options: &[(String, String)], @@ -764,7 +765,7 @@ pub async fn launch_minecraft( let instance_path = get_instance_full_path(&instance.path).await?; let (minecraft, version_index) = resolve_minecraft_manifest( - operation_context, + invocation_context, &content_set.game_version, &state, ) @@ -778,7 +779,7 @@ pub async fn launch_minecraft( .unwrap_or(0); let loader_version = get_loader_version_from_profile( - operation_context, + invocation_context, &content_set.game_version, content_set.loader, content_set.loader_version.as_deref(), @@ -799,7 +800,7 @@ pub async fn launch_minecraft( }); let mut version_info = download::download_version_info( - operation_context, + invocation_context, &state, version, loader_version.as_ref(), @@ -817,7 +818,7 @@ pub async fn launch_minecraft( .unwrap_or(0); if requires_logging_info { version_info = download::download_version_info( - operation_context, + invocation_context, &state, version, loader_version.as_ref(), @@ -830,7 +831,7 @@ pub async fn launch_minecraft( } let _ = download_log_config( - operation_context, + invocation_context, &state, &version_info, None, @@ -1093,7 +1094,7 @@ pub async fn launch_minecraft( state .process_manager .insert_new_process( - operation_context, + invocation_context, &instance.id, &instance.path, &instance.name, diff --git a/packages/app-lib/src/lib.rs b/packages/app-lib/src/lib.rs index 564010617c..446d1d8772 100644 --- a/packages/app-lib/src/lib.rs +++ b/packages/app-lib/src/lib.rs @@ -26,9 +26,7 @@ pub use event::{ emit::init_loading, }; pub use logger::start_logger; -pub use operation::{ - InvocationContext, OperationCause, OperationContext, REQUEST_CONTEXT_HEADER, -}; +pub use operation::{InvocationContext, REQUEST_CONTEXT_HEADER}; pub use state::State; pub use util::fetch::DownloadReason; diff --git a/packages/app-lib/src/operation.rs b/packages/app-lib/src/operation.rs index 98ab29aa6d..1af1d1760a 100644 --- a/packages/app-lib/src/operation.rs +++ b/packages/app-lib/src/operation.rs @@ -1,181 +1,30 @@ -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::str::FromStr; -use uuid::Uuid; +use serde::Deserialize; pub const REQUEST_CONTEXT_HEADER: &str = "Modrinth-App-Request-Context"; -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub enum OperationCause { - #[serde(rename = "app/startup")] - AppStartup, - #[serde(rename = "navigation/home")] - NavigationHome, - #[serde(rename = "navigation/library")] - NavigationLibrary, - #[serde(rename = "navigation/browse")] - NavigationBrowse, - #[serde(rename = "navigation/project")] - NavigationProject, - #[serde(rename = "navigation/instance/overview")] - NavigationInstanceOverview, - #[serde(rename = "navigation/instance/content")] - NavigationInstanceContent, - #[serde(rename = "navigation/instance/logs")] - NavigationInstanceLogs, - #[serde(rename = "navigation/servers")] - NavigationServers, - #[serde(rename = "navigation/server/manage")] - NavigationServerManage, - #[serde(rename = "navigation/server/content")] - NavigationServerContent, - #[serde(rename = "instance/refresh/user")] - InstanceRefreshUser, - #[serde(rename = "instance/refresh/filesystem_watch")] - InstanceRefreshFilesystemWatch, - #[serde(rename = "instance/update/all")] - InstanceUpdateAll, - #[serde(rename = "instance/update/single")] - InstanceUpdateSingle, - #[serde(rename = "instance/install")] - InstanceInstall, - #[serde(rename = "cache/revalidate")] - CacheRevalidate, - #[serde(rename = "auth/session_refresh")] - AuthSessionRefresh, - #[serde(rename = "background/friends")] - BackgroundFriends, - #[serde(rename = "minecraft/launch")] - MinecraftLaunch, - #[serde(rename = "app/update_check")] - AppUpdateCheck, - #[serde(rename = "unattributed")] - Unattributed, -} - -impl OperationCause { - pub const fn as_str(self) -> &'static str { - match self { - Self::AppStartup => "app/startup", - Self::NavigationHome => "navigation/home", - Self::NavigationLibrary => "navigation/library", - Self::NavigationBrowse => "navigation/browse", - Self::NavigationProject => "navigation/project", - Self::NavigationInstanceOverview => "navigation/instance/overview", - Self::NavigationInstanceContent => "navigation/instance/content", - Self::NavigationInstanceLogs => "navigation/instance/logs", - Self::NavigationServers => "navigation/servers", - Self::NavigationServerManage => "navigation/server/manage", - Self::NavigationServerContent => "navigation/server/content", - Self::InstanceRefreshUser => "instance/refresh/user", - Self::InstanceRefreshFilesystemWatch => { - "instance/refresh/filesystem_watch" - } - Self::InstanceUpdateAll => "instance/update/all", - Self::InstanceUpdateSingle => "instance/update/single", - Self::InstanceInstall => "instance/install", - Self::CacheRevalidate => "cache/revalidate", - Self::AuthSessionRefresh => "auth/session_refresh", - Self::BackgroundFriends => "background/friends", - Self::MinecraftLaunch => "minecraft/launch", - Self::AppUpdateCheck => "app/update_check", - Self::Unattributed => "unattributed", - } - } -} - -impl fmt::Display for OperationCause { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str(self.as_str()) - } -} - -#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)] -#[error("invalid operation cause: {0}")] -pub struct InvalidOperationCause(String); - -impl FromStr for OperationCause { - type Err = InvalidOperationCause; - - fn from_str(value: &str) -> Result { - let cause = match value { - "app/startup" => Self::AppStartup, - "navigation/home" => Self::NavigationHome, - "navigation/library" => Self::NavigationLibrary, - "navigation/browse" => Self::NavigationBrowse, - "navigation/project" => Self::NavigationProject, - "navigation/instance/overview" => Self::NavigationInstanceOverview, - "navigation/instance/content" => Self::NavigationInstanceContent, - "navigation/instance/logs" => Self::NavigationInstanceLogs, - "navigation/servers" => Self::NavigationServers, - "navigation/server/manage" => Self::NavigationServerManage, - "navigation/server/content" => Self::NavigationServerContent, - "instance/refresh/user" => Self::InstanceRefreshUser, - "instance/refresh/filesystem_watch" => { - Self::InstanceRefreshFilesystemWatch - } - "instance/update/all" => Self::InstanceUpdateAll, - "instance/update/single" => Self::InstanceUpdateSingle, - "instance/install" => Self::InstanceInstall, - "cache/revalidate" => Self::CacheRevalidate, - "auth/session_refresh" => Self::AuthSessionRefresh, - "background/friends" => Self::BackgroundFriends, - "minecraft/launch" => Self::MinecraftLaunch, - "app/update_check" => Self::AppUpdateCheck, - "unattributed" => Self::Unattributed, - _ => return Err(InvalidOperationCause(value.to_string())), - }; - - Ok(cause) - } -} - -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] #[serde(deny_unknown_fields)] pub struct InvocationContext { - pub cause: OperationCause, + pub cause: String, } impl InvocationContext { - pub fn into_operation_context(self) -> OperationContext { - OperationContext::new(self.cause) - } -} - -#[derive(Clone, Debug)] -pub struct OperationContext { - pub id: Uuid, - pub parent_id: Option, - pub cause: OperationCause, -} - -impl OperationContext { - pub fn new(cause: OperationCause) -> Self { - Self { - id: Uuid::new_v4(), - parent_id: None, - cause, - } - } - - pub fn child(&self, cause: OperationCause) -> Self { + pub fn new(cause: impl Into) -> Self { Self { - id: Uuid::new_v4(), - parent_id: Some(self.id), - cause, + cause: cause.into(), } } - pub const fn cause(&self) -> OperationCause { - self.cause + pub fn cause(&self) -> &str { + &self.cause } - pub const fn request_context_header(&self) -> &'static str { - self.cause.as_str() + pub fn request_context_header(&self) -> &str { + &self.cause } pub fn referer(&self) -> String { - format!("https://tauri.modrinth.app/_rc/{}", self.cause.as_str()) + format!("https://tauri.modrinth.app/_rc/{}", self.cause) } } @@ -184,51 +33,15 @@ mod tests { use super::*; #[test] - fn causes_accept_only_the_stable_taxonomy() { - assert_eq!( - "navigation/instance/content".parse(), - Ok(OperationCause::NavigationInstanceContent) - ); - assert!("Navigation/Project".parse::().is_err()); - assert!( - "navigation/project/user-value" - .parse::() - .is_err() - ); - assert!("navigation//project".parse::().is_err()); - assert!("navigation/project/".parse::().is_err()); - assert!("a".repeat(256).parse::().is_err()); - } - - #[test] - fn serde_does_not_normalize_invalid_causes() { - assert_eq!( - serde_json::from_str::( - r#""navigation/instance/content""# - ) - .unwrap(), - OperationCause::NavigationInstanceContent - ); - assert!( - serde_json::from_str::( - r#""Navigation/Instance/Content""# - ) - .is_err() - ); - } + fn accepts_frontend_defined_causes() { + let context: InvocationContext = + serde_json::from_str(r#"{"cause":"new/frontend/cause"}"#).unwrap(); - #[test] - fn child_context_links_to_its_parent() { - let parent = OperationContext::new(OperationCause::InstanceInstall); - let child = parent.child(OperationCause::CacheRevalidate); - - assert_ne!(parent.id, child.id); - assert_eq!(child.parent_id, Some(parent.id)); - assert_eq!(child.cause(), OperationCause::CacheRevalidate); + assert_eq!(context.cause(), "new/frontend/cause"); } #[test] - fn invocation_context_accepts_only_a_cause() { + fn invocation_context_contains_only_a_cause() { assert!( serde_json::from_str::( r#"{"cause":"navigation/home","id":"frontend-id"}"# @@ -238,9 +51,8 @@ mod tests { } #[test] - fn headers_have_exact_semantic_values() { - let context = - OperationContext::new(OperationCause::NavigationInstanceContent); + fn headers_use_the_frontend_cause_unchanged() { + let context = InvocationContext::new("navigation/instance/content"); assert_eq!( context.referer(), @@ -251,43 +63,4 @@ mod tests { "navigation/instance/content" ); } - - #[test] - fn concurrent_roots_remain_distinct() { - let first = OperationContext::new(OperationCause::NavigationBrowse); - let second = OperationContext::new(OperationCause::MinecraftLaunch); - - assert_ne!(first.id, second.id); - assert_ne!(first.cause(), second.cause()); - assert_eq!(first.parent_id, None); - assert_eq!(second.parent_id, None); - } - - #[tokio::test] - async fn spawned_watcher_and_cache_work_retains_explicit_contexts() { - let watcher = OperationContext::new( - OperationCause::InstanceRefreshFilesystemWatch, - ); - let cache = watcher.child(OperationCause::CacheRevalidate); - let watcher_id = watcher.id; - - let watcher_task = tokio::spawn(async move { - (watcher.id, watcher.parent_id, watcher.cause()) - }); - let cache_task = - tokio::spawn(async move { (cache.parent_id, cache.cause()) }); - - assert_eq!( - watcher_task.await.unwrap(), - ( - watcher_id, - None, - OperationCause::InstanceRefreshFilesystemWatch - ) - ); - assert_eq!( - cache_task.await.unwrap(), - (Some(watcher_id), OperationCause::CacheRevalidate) - ); - } } diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index 6f0416eac0..0809404a5b 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -1,6 +1,6 @@ +use crate::InvocationContext; use crate::state::ProjectType; use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async}; -use crate::{OperationCause, OperationContext}; use chrono::{DateTime, Utc}; use dashmap::DashSet; use reqwest::Method; @@ -805,7 +805,7 @@ macro_rules! impl_cache_methods { paste::paste! { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn []( - context: &OperationContext, + context: &InvocationContext, id: &str, cache_behaviour: Option, pool: &SqlitePool, @@ -817,7 +817,7 @@ macro_rules! impl_cache_methods { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn []( - context: &OperationContext, + context: &InvocationContext, ids: &[&str], cache_behaviour: Option, pool: &SqlitePool, @@ -846,7 +846,7 @@ macro_rules! impl_cache_method_singular { paste::paste! { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn [] ( - context: &OperationContext, + context: &InvocationContext, cache_behaviour: Option, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, @@ -894,7 +894,7 @@ impl_cache_method_singular!( impl CachedEntry { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get( - context: &OperationContext, + context: &InvocationContext, type_: CacheValueType, key: &str, cache_behaviour: Option, @@ -916,7 +916,7 @@ impl CachedEntry { #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get_many( - context: &OperationContext, + context: &InvocationContext, type_: CacheValueType, keys: &[&str], cache_behaviour: Option, @@ -1065,7 +1065,7 @@ impl CachedEntry { || cache_behaviour == CacheBehaviour::StaleWhileRevalidateSkipOffline) { - let context = context.child(OperationCause::CacheRevalidate); + let context = context.clone(); tokio::task::spawn(async move { // TODO: if possible- find a way to do this without invoking state get let state = crate::state::State::get().await?; @@ -1094,14 +1094,14 @@ impl CachedEntry { } async fn fetch_many( - context: &OperationContext, + context: &InvocationContext, type_: CacheValueType, keys: DashSet, fetch_semaphore: &FetchSemaphore, pool: &SqlitePool, ) -> crate::Result> { async fn fetch_many_batched( - context: &OperationContext, + context: &InvocationContext, method: Method, api_url: &str, url: &str, @@ -2098,7 +2098,7 @@ impl CachedEntry { /// Get modpack file hashes from cache pub async fn get_modpack_files( - context: &OperationContext, + context: &InvocationContext, version_id: &str, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, @@ -2127,7 +2127,7 @@ impl CachedEntry { /// Get versions for a project (without changelogs for fast loading) #[tracing::instrument(skip(pool, fetch_semaphore))] pub async fn get_project_versions( - context: &OperationContext, + context: &InvocationContext, project_id: &str, cache_behaviour: Option, pool: &SqlitePool, diff --git a/packages/app-lib/src/state/friends.rs b/packages/app-lib/src/state/friends.rs index d3a8c6ceda..7bb3b3b8a1 100644 --- a/packages/app-lib/src/state/friends.rs +++ b/packages/app-lib/src/state/friends.rs @@ -1,11 +1,11 @@ use crate::ErrorKind; +use crate::InvocationContext; use crate::data::ModrinthCredentials; use crate::event::FriendPayload; use crate::event::emit::{emit_friend, emit_notification}; use crate::state::tunnel::InternalTunnelSocket; use crate::state::{ProcessManager, TunnelSocket}; use crate::util::fetch::{FetchSemaphore, fetch_advanced, fetch_json}; -use crate::{OperationCause, OperationContext}; use ariadne::ids::UserId; use ariadne::networking::message::{ ClientToServerMessage, ServerToClientMessage, @@ -69,7 +69,7 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn connect( &self, - context: &OperationContext, + context: &InvocationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, process_manager: &ProcessManager, @@ -258,7 +258,7 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn socket_loop() -> crate::Result<()> { let state = crate::State::get().await?; - let context = OperationContext::new(OperationCause::BackgroundFriends); + let context = InvocationContext::new("background/friends"); tokio::task::spawn(async move { let mut last_connection = Utc::now(); @@ -326,7 +326,7 @@ impl FriendsSocket { #[tracing::instrument(skip_all)] pub async fn friends( - context: &OperationContext, + context: &InvocationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result> { @@ -353,7 +353,7 @@ impl FriendsSocket { #[tracing::instrument(skip(exec, semaphore))] pub async fn add_friend( - context: &OperationContext, + context: &InvocationContext, user_id: &str, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, @@ -389,7 +389,7 @@ impl FriendsSocket { #[tracing::instrument(skip(exec, semaphore))] pub async fn remove_friend( - context: &OperationContext, + context: &InvocationContext, user_id: &str, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, diff --git a/packages/app-lib/src/state/instances/commands/apply_content_install.rs b/packages/app-lib/src/state/instances/commands/apply_content_install.rs index 6e17d11dca..f2dc5c8466 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_install.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_install.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::instances::{ ContentRequirement, ContentSourceKind, Instance, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -46,7 +46,7 @@ pub(crate) struct InstanceInstallProjectRequest { } struct CachedEntryContentProvider<'a> { - context: &'a OperationContext, + context: &'a InvocationContext, state: &'a State, cache_behaviour: Option, } @@ -161,7 +161,7 @@ fn target_preferences( } pub(crate) async fn resolve_install_plan( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, request: InstanceInstallProjectRequest, state: &State, @@ -207,7 +207,7 @@ pub(crate) async fn resolve_install_plan( } pub(crate) async fn install_resolved_content_plan( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, plan: &ResolveContentPlan, state: &State, @@ -235,7 +235,7 @@ pub(crate) async fn install_resolved_content_plan( } pub(crate) async fn switch_project_version_with_dependencies( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, project_path: &str, version_id: &str, @@ -314,7 +314,7 @@ pub(crate) async fn switch_project_version_with_dependencies( } async fn add_resolved_content( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content: &ResolvedContent, reason: DownloadReason, @@ -359,7 +359,7 @@ pub(crate) async fn resolve_content_scope( } pub(crate) async fn add_project_from_version( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, version_id: &str, reason: DownloadReason, @@ -382,7 +382,7 @@ pub(crate) async fn add_project_from_version( } pub(crate) async fn download_project_version( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, version_id: &str, reason: DownloadReason, diff --git a/packages/app-lib/src/state/instances/commands/apply_content_update.rs b/packages/app-lib/src/state/instances/commands/apply_content_update.rs index f974f0ff55..168069cff0 100644 --- a/packages/app-lib/src/state/instances/commands/apply_content_update.rs +++ b/packages/app-lib/src/state/instances/commands/apply_content_update.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::instances::{ ContentEntry, ContentSet, ContentSourceKind, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -64,7 +64,7 @@ struct ResolvedDependency { } pub(crate) async fn update_project( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, project_path: &str, state: &State, @@ -90,7 +90,7 @@ pub(crate) async fn update_project( } async fn apply_content_update( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, project_path: &str, update: &ContentUpdate, @@ -128,7 +128,7 @@ async fn apply_content_update( } pub(crate) async fn update_all_projects( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, state: &State, ) -> crate::Result> { @@ -210,7 +210,7 @@ pub(crate) async fn update_all_projects( } async fn download_planned_projects( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, plan: &BulkUpdatePlan, total: usize, @@ -307,7 +307,7 @@ async fn emit_bulk_update_progress( } async fn plan_bulk_update( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, state: &State, ) -> crate::Result { @@ -434,7 +434,7 @@ async fn plan_bulk_update( } async fn bulk_updateable_project_paths( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, state: &State, ) -> crate::Result> { @@ -497,7 +497,7 @@ fn installed_project_from_row( } async fn dependency_closure( - context: &OperationContext, + context: &InvocationContext, root_versions: Vec, content_set: &ContentSet, state: &State, @@ -559,7 +559,7 @@ fn is_required_dependency( } async fn resolve_dependency_version( - context: &OperationContext, + context: &InvocationContext, dependency: &Dependency, content_set: &ContentSet, state: &State, @@ -590,7 +590,7 @@ async fn resolve_dependency_version( } async fn cached_version( - context: &OperationContext, + context: &InvocationContext, version_id: &str, version_cache: &mut HashMap>, state: &State, @@ -611,7 +611,7 @@ async fn cached_version( } async fn cached_project_versions( - context: &OperationContext, + context: &InvocationContext, project_id: &str, project_versions_cache: &mut HashMap>>, state: &State, diff --git a/packages/app-lib/src/state/instances/commands/check_content_updates.rs b/packages/app-lib/src/state/instances/commands/check_content_updates.rs index f9d173b69c..3ec355e966 100644 --- a/packages/app-lib/src/state/instances/commands/check_content_updates.rs +++ b/packages/app-lib/src/state/instances/commands/check_content_updates.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::instances::{ ContentEntry, InstanceFile, adapters::sqlite::{content_rows, instance_rows}, @@ -28,7 +28,7 @@ struct UpdateCandidate { } pub(crate) async fn check_content_updates( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, cache_behaviour: Option, state: &State, @@ -164,7 +164,7 @@ pub(crate) async fn check_content_updates( } async fn installed_update_channels( - context: &OperationContext, + context: &InvocationContext, candidates: &[UpdateCandidate], cache_behaviour: Option, state: &State, diff --git a/packages/app-lib/src/state/instances/commands/create_instance.rs b/packages/app-lib/src/state/instances/commands/create_instance.rs index ba9e500caa..48c019b55e 100644 --- a/packages/app-lib/src/state/instances/commands/create_instance.rs +++ b/packages/app-lib/src/state/instances/commands/create_instance.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::launcher::get_loader_version_from_profile; use crate::state::instances::{ ContentSet, ContentSetStatus, ContentSourceKind, Instance, @@ -28,7 +28,7 @@ pub struct CreateInstance { } pub(crate) async fn create_instance( - context: &OperationContext, + context: &InvocationContext, input: CreateInstance, state: &State, ) -> crate::Result { @@ -171,7 +171,7 @@ async fn path_available( } async fn resolve_icon_path( - context: &OperationContext, + context: &InvocationContext, icon_path: Option<&str>, state: &State, ) -> crate::Result> { diff --git a/packages/app-lib/src/state/instances/commands/list_content.rs b/packages/app-lib/src/state/instances/commands/list_content.rs index 700dafe037..83f98670f5 100644 --- a/packages/app-lib/src/state/instances/commands/list_content.rs +++ b/packages/app-lib/src/state/instances/commands/list_content.rs @@ -16,7 +16,7 @@ use crate::state::{ use crate::util::fetch::{ DownloadMeta, DownloadReason, FetchSemaphore, fetch_mirrors, sha1_async, }; -use crate::{OperationContext, State}; +use crate::{InvocationContext, State}; use async_zip::base::read::seek::ZipFileReader; use dashmap::DashMap; use sqlx::SqlitePool; @@ -59,7 +59,7 @@ pub(crate) async fn list_content_sets( } pub(crate) async fn get_content_projects( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -83,7 +83,7 @@ pub(crate) async fn get_content_projects( } pub(crate) async fn get_installed_project_ids_for_instance( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content_set_id: Option<&str>, state: &State, @@ -195,7 +195,7 @@ fn instance_matches_targets( } pub(crate) async fn list_content( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -267,7 +267,7 @@ pub(crate) async fn list_content( } pub(crate) async fn list_linked_modpack_content( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -348,7 +348,7 @@ pub(crate) async fn list_linked_modpack_content( } pub(crate) async fn get_linked_modpack_info( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, content_set_id: Option<&str>, cache_behaviour: Option, @@ -473,7 +473,7 @@ pub(crate) async fn get_linked_modpack_info( } pub(crate) async fn dependencies_to_content_items( - context: &OperationContext, + context: &InvocationContext, dependencies: &[Dependency], cache_behaviour: Option, pool: &SqlitePool, @@ -614,7 +614,7 @@ async fn resolve_content_scope_with_instance( } async fn content_projects_for_scope( - context: &OperationContext, + context: &InvocationContext, resolved: &ResolvedContentScope, cache_behaviour: Option, state: &State, @@ -779,7 +779,7 @@ async fn content_projects_for_scope( } async fn get_installed_update_channels( - context: &OperationContext, + context: &InvocationContext, file_info_by_hash: &HashMap, cache_behaviour: Option, pool: &SqlitePool, @@ -844,7 +844,7 @@ fn file_update_cache_key( } async fn content_files_to_content_items( - context: &OperationContext, + context: &InvocationContext, instance: &Instance, files: &[(String, ContentFile)], cache_behaviour: Option, @@ -953,7 +953,7 @@ struct ResolvedMetadata { } async fn resolve_metadata( - context: &OperationContext, + context: &InvocationContext, project_ids: &HashSet, version_ids: &HashSet, cache_behaviour: Option, @@ -1213,7 +1213,7 @@ impl ModpackIdentifiers { } async fn get_cached_modpack_identifiers( - context: &OperationContext, + context: &InvocationContext, version_id: &str, pool: &SqlitePool, fetch_semaphore: &FetchSemaphore, @@ -1240,7 +1240,7 @@ async fn get_cached_modpack_identifiers( } async fn get_modpack_identifiers( - context: &OperationContext, + context: &InvocationContext, version_id: &str, content_set: &ContentSet, pool: &SqlitePool, diff --git a/packages/app-lib/src/state/instances/commands/sync_content_files.rs b/packages/app-lib/src/state/instances/commands/sync_content_files.rs index 4b428cce3d..833dfc6a4d 100644 --- a/packages/app-lib/src/state/instances/commands/sync_content_files.rs +++ b/packages/app-lib/src/state/instances/commands/sync_content_files.rs @@ -1,13 +1,13 @@ use crate::state::instances::adapters::{filesystem, sqlite}; use crate::state::instances::{Instance, InstanceFile}; use crate::state::{CachedEntry, ProjectType}; -use crate::{OperationContext, State}; +use crate::{InvocationContext, State}; use chrono::Utc; use std::collections::HashMap; use uuid::Uuid; pub(crate) async fn sync_content_files( - context: &OperationContext, + context: &InvocationContext, instance_id: &str, state: &State, ) -> crate::Result> { @@ -22,7 +22,7 @@ pub(crate) async fn sync_content_files( } pub(crate) async fn sync_instance_content_files( - context: &OperationContext, + context: &InvocationContext, instance: &Instance, state: &State, ) -> crate::Result> { diff --git a/packages/app-lib/src/state/instances/watcher.rs b/packages/app-lib/src/state/instances/watcher.rs index c223aa68bc..d3ae6cd4c5 100644 --- a/packages/app-lib/src/state/instances/watcher.rs +++ b/packages/app-lib/src/state/instances/watcher.rs @@ -4,7 +4,7 @@ use crate::state::{ DirectoryInfo, InstanceInstallStage, ProjectType, attached_world_data, }; use crate::worlds::WorldType; -use crate::{OperationCause, OperationContext, State}; +use crate::{InvocationContext, State}; use notify::{RecommendedWatcher, RecursiveMode}; use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer}; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -34,12 +34,10 @@ pub async fn init_watcher() -> crate::Result { tracing::info!(parent: &span, "Initing watcher"); while let Some(res) = rx.recv().await { let _span = span.enter(); - let context = OperationContext::new( - OperationCause::InstanceRefreshFilesystemWatch, - ); + let context = + InvocationContext::new("instance/refresh/filesystem_watch"); tracing::debug!( - operation_id = %context.id, - operation_cause = %context.cause(), + invocation_cause = %context.cause(), "Processing filesystem watcher refresh" ); diff --git a/packages/app-lib/src/state/mod.rs b/packages/app-lib/src/state/mod.rs index a2ca90da8d..f1e873bfe0 100644 --- a/packages/app-lib/src/state/mod.rs +++ b/packages/app-lib/src/state/mod.rs @@ -1,6 +1,6 @@ //! Theseus state management system +use crate::InvocationContext; use crate::util::fetch::{FetchSemaphore, IoSemaphore}; -use crate::{OperationCause, OperationContext}; use std::sync::Arc; use std::sync::atomic::AtomicBool; use tokio::sync::{OnceCell, Semaphore}; @@ -97,7 +97,6 @@ pub struct State { impl State { pub async fn init(app_identifier: String) -> crate::Result<()> { - let context = OperationContext::new(OperationCause::AppStartup); let state = LAUNCHER_STATE .get_or_try_init(move || Self::initialize_state(app_identifier)) .await?; @@ -116,8 +115,7 @@ impl State { ) .await; - let auth_context = - context.child(OperationCause::AuthSessionRefresh); + let auth_context = InvocationContext::new("auth/session_refresh"); let res = tokio::try_join!( state.discord_rpc.clear_to_default(true), instances::refresh_all_instances(), @@ -129,10 +127,11 @@ impl State { tracing::error!("Error running discord RPC: {e}"); } + let friends_context = InvocationContext::new("background/friends"); let _ = state .friends_socket .connect( - &context.child(OperationCause::BackgroundFriends), + &friends_context, &state.pool, &state.api_semaphore, &state.process_manager, diff --git a/packages/app-lib/src/state/mr_auth.rs b/packages/app-lib/src/state/mr_auth.rs index 5e18a8c331..026e4b0183 100644 --- a/packages/app-lib/src/state/mr_auth.rs +++ b/packages/app-lib/src/state/mr_auth.rs @@ -1,4 +1,4 @@ -use crate::OperationContext; +use crate::InvocationContext; use crate::state::{CacheBehaviour, CachedEntry}; use crate::util::fetch::{FetchSemaphore, fetch_advanced}; use chrono::{DateTime, Duration, TimeZone, Utc}; @@ -17,7 +17,7 @@ pub struct ModrinthCredentials { impl ModrinthCredentials { pub async fn get_and_refresh( - context: &OperationContext, + context: &InvocationContext, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy, semaphore: &FetchSemaphore, ) -> crate::Result> { @@ -177,7 +177,7 @@ impl ModrinthCredentials { } pub(crate) async fn refresh_all( - context: &OperationContext, + context: &InvocationContext, ) -> crate::Result<()> { let state = crate::State::get().await?; let all = Self::get_all(&state.pool).await?; @@ -202,7 +202,7 @@ pub const fn get_login_url() -> &'static str { } pub async fn finish_login_flow( - context: &OperationContext, + context: &InvocationContext, code: &str, semaphore: &FetchSemaphore, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, @@ -224,7 +224,7 @@ pub async fn finish_login_flow( } async fn fetch_info( - context: &OperationContext, + context: &InvocationContext, token: &str, semaphore: &FetchSemaphore, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index fcbbdfa947..3e97e08662 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -102,7 +102,7 @@ impl ProcessManager { #[allow(clippy::too_many_arguments)] pub async fn insert_new_process( &self, - operation_context: &crate::OperationContext, + invocation_context: &crate::InvocationContext, instance_id: &str, instance_path: &str, instance_name: &str, @@ -215,7 +215,7 @@ impl ProcessManager { } tokio::spawn(Process::sequential_process_manager( - operation_context.clone(), + invocation_context.clone(), instance_id.to_string(), instance_path.to_string(), post_exit_command, @@ -749,7 +749,7 @@ impl Process { // Also, as the process ends, it spawns the follow-up process if it exists // By convention, ExitStatus is last command's exit status, and we exit on the first non-zero exit status async fn sequential_process_manager( - operation_context: crate::OperationContext, + invocation_context: crate::InvocationContext, instance_id: String, instance_path: String, post_exit_command: Option, @@ -836,7 +836,7 @@ impl Process { tokio::spawn(async move { if let Err(e) = crate::api::instance::try_update_playtime_by_instance_id( - &operation_context, + &invocation_context, &playtime_instance_id, ) .await diff --git a/packages/app-lib/src/util/fetch.rs b/packages/app-lib/src/util/fetch.rs index 99522132e1..8812ce6ac2 100644 --- a/packages/app-lib/src/util/fetch.rs +++ b/packages/app-lib/src/util/fetch.rs @@ -2,9 +2,7 @@ use super::io::{self, IOError}; use crate::event::LoadingBarId; use crate::event::emit::emit_loading; -use crate::operation::{ - OperationCause, OperationContext, REQUEST_CONTEXT_HEADER, -}; +use crate::operation::{InvocationContext, REQUEST_CONTEXT_HEADER}; use crate::{ErrorKind, LabrinthError}; use bytes::Bytes; use chrono::{DateTime, TimeDelta, Utc}; @@ -372,20 +370,18 @@ fn is_approved_modrinth_service_url(url: &str) -> bool { .any(|approved| url.origin() == approved.origin()) } -fn attach_operation_headers( +fn attach_invocation_headers( request: reqwest::RequestBuilder, url: &str, - context: &OperationContext, + context: &InvocationContext, ) -> reqwest::RequestBuilder { if !is_approved_modrinth_service_url(url) { return request; } - if cfg!(debug_assertions) && context.cause() == OperationCause::Unattributed - { + if cfg!(debug_assertions) && context.cause() == "unattributed" { tracing::warn!( - operation_id = %context.id, - "Unattributed operation context reached the Modrinth HTTP boundary" + "Unattributed invocation context reached the Modrinth HTTP boundary" ); } @@ -405,7 +401,7 @@ pub type FetchProgressFn<'a> = dyn FnMut( #[tracing::instrument(skip(semaphore))] pub async fn fetch( - context: &OperationContext, + context: &InvocationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -431,7 +427,7 @@ pub async fn fetch( #[tracing::instrument(skip(semaphore))] pub async fn fetch_with_client( - context: &OperationContext, + context: &InvocationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -459,7 +455,7 @@ pub async fn fetch_with_client( #[tracing::instrument(skip(semaphore, progress))] pub async fn fetch_with_client_progress( - context: &OperationContext, + context: &InvocationContext, url: &str, sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -489,7 +485,7 @@ pub async fn fetch_with_client_progress( #[tracing::instrument(skip(json_body, semaphore))] pub async fn fetch_json( - context: &OperationContext, + context: &InvocationContext, method: Method, url: &str, sha1: Option<&str>, @@ -515,7 +511,7 @@ where #[tracing::instrument(skip(json_body, semaphore))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced( - context: &OperationContext, + context: &InvocationContext, method: Method, url: &str, sha1: Option<&str>, @@ -547,7 +543,7 @@ pub async fn fetch_advanced( #[tracing::instrument(skip(json_body, semaphore, progress))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced_with_progress( - context: &OperationContext, + context: &InvocationContext, method: Method, url: &str, sha1: Option<&str>, @@ -582,7 +578,7 @@ pub async fn fetch_advanced_with_progress( #[tracing::instrument(skip(json_body, semaphore))] #[allow(clippy::too_many_arguments)] pub async fn fetch_advanced_with_client( - context: &OperationContext, + context: &InvocationContext, method: Method, url: &str, sha1: Option<&str>, @@ -616,7 +612,7 @@ pub async fn fetch_advanced_with_client( #[tracing::instrument(skip(json_body, semaphore, client, progress))] #[allow(clippy::too_many_arguments)] async fn fetch_advanced_with_client_and_progress( - context: &OperationContext, + context: &InvocationContext, method: Method, url: &str, sha1: Option<&str>, @@ -663,7 +659,7 @@ async fn fetch_advanced_with_client_and_progress( .into()); } - let mut req = attach_operation_headers( + let mut req = attach_invocation_headers( client.request(method.clone(), url), url, context, @@ -814,7 +810,7 @@ async fn fetch_advanced_with_client_and_progress( /// Downloads a file from specified mirrors #[tracing::instrument(skip(semaphore))] pub async fn fetch_mirrors( - context: &OperationContext, + context: &InvocationContext, mirrors: &[&str], sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -851,7 +847,7 @@ pub async fn fetch_mirrors( #[tracing::instrument(skip(semaphore, progress))] pub async fn fetch_mirrors_with_progress( - context: &OperationContext, + context: &InvocationContext, mirrors: &[&str], sha1: Option<&str>, download_meta: Option<&DownloadMeta>, @@ -891,7 +887,7 @@ pub async fn fetch_mirrors_with_progress( /// Posts a JSON to a URL #[tracing::instrument(skip(json_body, semaphore))] pub async fn post_json( - context: &OperationContext, + context: &InvocationContext, url: &str, json_body: serde_json::Value, semaphore: &FetchSemaphore, @@ -899,7 +895,7 @@ pub async fn post_json( ) -> crate::Result<()> { let _permit = semaphore.0.acquire().await?; - let mut req = attach_operation_headers( + let mut req = attach_invocation_headers( INSECURE_REQWEST_CLIENT.post(url), url, context, @@ -1008,24 +1004,27 @@ pub async fn sha1_async(bytes: Bytes) -> crate::Result { } #[cfg(test)] -mod operation_context_tests { +mod invocation_context_tests { use super::*; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::TcpListener; fn built_request( url: &str, - context: &OperationContext, + context: &InvocationContext, ) -> reqwest::Request { - attach_operation_headers(INSECURE_REQWEST_CLIENT.get(url), url, context) - .build() - .unwrap() + attach_invocation_headers( + INSECURE_REQWEST_CLIENT.get(url), + url, + context, + ) + .build() + .unwrap() } #[test] - fn approved_origins_receive_operation_headers() { - let context = - OperationContext::new(OperationCause::NavigationInstanceContent); + fn approved_origins_receive_invocation_headers() { + let context = InvocationContext::new("navigation/instance/content"); let request = built_request( concat!(env!("MODRINTH_API_URL"), "project/example"), &context, @@ -1042,8 +1041,8 @@ mod operation_context_tests { } #[test] - fn third_party_origins_do_not_receive_operation_headers() { - let context = OperationContext::new(OperationCause::InstanceInstall); + fn third_party_origins_do_not_receive_invocation_headers() { + let context = InvocationContext::new("instance/install"); let request = built_request("https://example.com/file.jar", &context); assert!(!request.headers().contains_key(reqwest::header::REFERER)); @@ -1052,7 +1051,7 @@ mod operation_context_tests { #[test] fn similarly_prefixed_hosts_are_not_approved() { - let context = OperationContext::new(OperationCause::NavigationBrowse); + let context = InvocationContext::new("navigation/browse"); let approved = url::Url::parse(env!("MODRINTH_API_URL")).unwrap(); let url = format!( "{}://{}.example.com/project", @@ -1067,7 +1066,7 @@ mod operation_context_tests { #[test] fn every_retry_attempt_retains_the_same_context() { - let context = OperationContext::new(OperationCause::CacheRevalidate); + let context = InvocationContext::new("new/frontend/cause"); let url = concat!(env!("MODRINTH_API_URL_V3"), "projects"); for _ in 0..=FETCH_ATTEMPTS {