mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9d3bd1daa | ||
|
|
96545c825b | ||
|
|
ab0d1eceb4 | ||
|
|
f0f012a8fa | ||
|
|
fe9c616591 | ||
|
|
e197db067c | ||
|
|
dd73d06a50 |
@@ -101,6 +101,10 @@ jobs:
|
||||
~/.cargo/bin
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Ensure sccache directory exists
|
||||
if: needs.skip-if-clean.outputs.internal == 'true'
|
||||
run: sudo mkdir -p /mnt/sccache && sudo chown $(id -u):$(id -g) /mnt/sccache
|
||||
|
||||
- name: Mount sccache disk cache
|
||||
if: needs.skip-if-clean.outputs.internal == 'true'
|
||||
uses: useblacksmith/stickydisk@13af8883542ca949a717e70fef89d15edbb29d88 # v1.2.0
|
||||
|
||||
@@ -109,6 +109,10 @@ jobs:
|
||||
~/.cargo/bin
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Ensure sccache directory exists
|
||||
if: needs.skip-if-clean.outputs.internal == 'true'
|
||||
run: sudo mkdir -p /mnt/sccache && sudo chown $(id -u):$(id -g) /mnt/sccache
|
||||
|
||||
- name: Mount sccache disk cache
|
||||
if: needs.skip-if-clean.outputs.internal == 'true'
|
||||
uses: useblacksmith/stickydisk@13af8883542ca949a717e70fef89d15edbb29d88 # v1.2.0
|
||||
|
||||
Generated
+70
@@ -3856,6 +3856,12 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
@@ -4167,6 +4173,29 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "governor"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dashmap",
|
||||
"futures-sink",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"getrandom 0.3.3",
|
||||
"hashbrown 0.16.0",
|
||||
"nonzero_ext",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"quanta",
|
||||
"rand 0.9.2",
|
||||
"smallvec",
|
||||
"spinning_top",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.13.0"
|
||||
@@ -6300,6 +6329,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
|
||||
|
||||
[[package]]
|
||||
name = "noop_proc_macro"
|
||||
version = "0.3.0"
|
||||
@@ -7900,6 +7935,21 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"raw-cpuid",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quartz_nbt"
|
||||
version = "0.2.9"
|
||||
@@ -8227,6 +8277,15 @@ dependencies = [
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -9838,6 +9897,15 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
@@ -10940,8 +11008,10 @@ dependencies = [
|
||||
"fs4",
|
||||
"futures",
|
||||
"futures-lite 2.6.1",
|
||||
"governor",
|
||||
"heck 0.5.0",
|
||||
"hickory-resolver 0.25.2",
|
||||
"httpdate",
|
||||
"indicatif",
|
||||
"itertools 0.14.0",
|
||||
"modrinth-content-management",
|
||||
|
||||
@@ -89,10 +89,12 @@ fs4 = { version = "0.13.1", default-features = false }
|
||||
futures = "0.3.31"
|
||||
futures-lite = "2.6.1"
|
||||
futures-util = "0.3.31"
|
||||
governor = "0.10.4"
|
||||
heck = "0.5.0"
|
||||
hex = "0.4.3"
|
||||
hickory-resolver = "0.25.2"
|
||||
hmac = "0.12.1"
|
||||
httpdate = "1.0.3"
|
||||
hyper = "1.7.0"
|
||||
hyper-rustls = { version = "0.27.7", default-features = false, features = [
|
||||
"aws-lc-rs",
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<T>(command: string, args: Record<string, unknown> = {}): Promise<T> {
|
||||
return tauriInvoke<T>(command, {
|
||||
...args,
|
||||
invocationContext: { cause: commandCause(command) },
|
||||
})
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
/*
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -6,20 +6,27 @@ macro_rules! impl_cache_methods {
|
||||
$(
|
||||
paste::paste! {
|
||||
#[tauri::command]
|
||||
pub async fn [<get_ $variant:snake>](id: &str, cache_behaviour: Option<CacheBehaviour>) -> Result<Option<$type>>
|
||||
pub async fn [<get_ $variant:snake>](
|
||||
id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Option<$type>>
|
||||
{
|
||||
Ok(theseus::cache::[<get_ $variant:snake>](id, cache_behaviour).await?)
|
||||
let context = invocation_context;
|
||||
Ok(theseus::cache::[<get_ $variant:snake>](&context, id, cache_behaviour).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn [<get_ $variant:snake _many>](
|
||||
ids: Vec<String>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<$type>>
|
||||
{
|
||||
let context = invocation_context;
|
||||
let ids = ids.iter().map(|x| &**x).collect::<Vec<&str>>();
|
||||
let entries =
|
||||
theseus::cache::[<get_ $variant:snake _many>](&*ids, cache_behaviour).await?;
|
||||
theseus::cache::[<get_ $variant:snake _many>](&context, &*ids, cache_behaviour).await?;
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
@@ -73,9 +80,13 @@ pub async fn purge_cache_types(cache_types: Vec<CacheValueType>) -> Result<()> {
|
||||
pub async fn get_project_versions(
|
||||
project_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Option<Vec<Version>>> {
|
||||
Ok(
|
||||
theseus::cache::get_project_versions(project_id, cache_behaviour)
|
||||
.await?,
|
||||
let context = invocation_context;
|
||||
Ok(theseus::cache::get_project_versions(
|
||||
&context,
|
||||
project_id,
|
||||
cache_behaviour,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
@@ -13,8 +13,11 @@ pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn friends() -> crate::api::Result<Vec<UserFriend>> {
|
||||
Ok(theseus::friends::friends().await?)
|
||||
pub async fn friends(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> crate::api::Result<Vec<UserFriend>> {
|
||||
let context = invocation_context;
|
||||
Ok(theseus::friends::friends(&context).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -23,11 +26,19 @@ pub async fn friend_statuses() -> crate::api::Result<Vec<UserStatus>> {
|
||||
}
|
||||
|
||||
#[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 = 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 = invocation_context;
|
||||
Ok(theseus::friends::remove_friend(&context, user_id).await?)
|
||||
}
|
||||
|
||||
@@ -67,15 +67,23 @@ impl InstallPostInstallEditRequest {
|
||||
#[tauri::command]
|
||||
pub async fn install_get_modpack_preview(
|
||||
location: CreatePackLocation,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<InstallModpackPreview> {
|
||||
Ok(theseus::pack::install_from::get_instance_from_pack(location).await?)
|
||||
let 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<InstallJobSnapshot> {
|
||||
let 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<InstallPostInstallEditRequest>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<InstallJobSnapshot> {
|
||||
let 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<InstallJobSnapshot> {
|
||||
let 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<InstallJobSnapshot> {
|
||||
Ok(theseus::install::duplicate_instance(source_instance_id).await?)
|
||||
let 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<InstallJobSnapshot> {
|
||||
Ok(theseus::install::install_existing_instance(instance_id, force).await?)
|
||||
let 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<InstallPostInstallEditRequest>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<InstallJobSnapshot> {
|
||||
let 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<InstallJobSnapshot> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_job_retry(job_id: Uuid) -> Result<InstallJobSnapshot> {
|
||||
Ok(theseus::install::retry_job(job_id).await?)
|
||||
pub async fn install_job_retry(
|
||||
job_id: Uuid,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<InstallJobSnapshot> {
|
||||
let context = invocation_context;
|
||||
Ok(theseus::install::retry_job(&context, job_id).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -437,15 +437,25 @@ pub async fn instance_list() -> Result<Vec<Instance>> {
|
||||
pub async fn instance_get_projects(
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<DashMap<String, ContentFile>> {
|
||||
Ok(theseus::instance::get_projects(instance_id, cache_behaviour).await?)
|
||||
let 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<Vec<String>> {
|
||||
Ok(theseus::instance::get_installed_project_ids(instance_id).await?)
|
||||
let 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<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
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<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
Ok(
|
||||
theseus::instance::get_content_items(instance_id, cache_behaviour)
|
||||
.await?,
|
||||
let 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<Dependency>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
let 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<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Option<LinkedModpackInfo>> {
|
||||
Ok(
|
||||
theseus::instance::get_linked_modpack_info(
|
||||
instance_id,
|
||||
cache_behaviour,
|
||||
)
|
||||
.await?,
|
||||
let 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<CacheBehaviour>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<ContentItem>> {
|
||||
let 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<Option<JavaVersion>> {
|
||||
Ok(theseus::instance::get_optimal_jre_key(instance_id).await?)
|
||||
let 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<bool> {
|
||||
let 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<HashMap<String, String>> {
|
||||
Ok(theseus::instance::update_all_projects(instance_id).await?)
|
||||
let 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<String> {
|
||||
Ok(
|
||||
theseus::instance::update_project(instance_id, project_path, None)
|
||||
.await?,
|
||||
let 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<String>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<String> {
|
||||
let 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<ResolveContentPlan> {
|
||||
let 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<String> {
|
||||
let 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<bool> {
|
||||
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<bool> {
|
||||
let 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<theseus::install::InstallJobSnapshot> {
|
||||
let 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<theseus::install::InstallJobSnapshot> {
|
||||
Ok(theseus::instance::repair_managed_modrinth(instance_id).await?)
|
||||
let 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<String>,
|
||||
description: Option<String>,
|
||||
name: Option<String>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<()> {
|
||||
let 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<String>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<ProcessMetadata> {
|
||||
let 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]
|
||||
|
||||
@@ -53,8 +53,12 @@ pub async fn jre_test_jre(path: PathBuf, major_version: u32) -> Result<bool> {
|
||||
|
||||
// Auto installs java for the given java version
|
||||
#[tauri::command]
|
||||
pub async fn jre_auto_install_java(java_version: u32) -> Result<PathBuf> {
|
||||
Ok(jre::auto_install_java(java_version).await?)
|
||||
pub async fn jre_auto_install_java(
|
||||
java_version: u32,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<PathBuf> {
|
||||
let context = invocation_context;
|
||||
Ok(jre::auto_install_java(&context, java_version).await?)
|
||||
}
|
||||
|
||||
// Gets the maximum memory a system has available.
|
||||
|
||||
@@ -13,12 +13,19 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
|
||||
/// Gets the game versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_game_versions() -> Result<VersionManifest> {
|
||||
Ok(theseus::metadata::get_minecraft_versions().await?)
|
||||
pub async fn metadata_get_game_versions(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<VersionManifest> {
|
||||
let 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<Manifest> {
|
||||
Ok(theseus::metadata::get_loader_versions(loader).await?)
|
||||
pub async fn metadata_get_loader_versions(
|
||||
loader: &str,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Manifest> {
|
||||
let context = invocation_context;
|
||||
Ok(theseus::metadata::get_loader_versions(&context, loader).await?)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
#[tauri::command]
|
||||
pub async fn modrinth_login<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<ModrinthCredentials> {
|
||||
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,
|
||||
@@ -58,7 +60,8 @@ pub async fn modrinth_login<R: Runtime>(
|
||||
));
|
||||
};
|
||||
|
||||
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<Option<ModrinthCredentials>> {
|
||||
Ok(theseus::mr_auth::get_credentials().await?)
|
||||
pub async fn get(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Option<ModrinthCredentials>> {
|
||||
let context = invocation_context;
|
||||
Ok(theseus::mr_auth::get_credentials(&context).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
+25
-10
@@ -15,30 +15,45 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
|
||||
/// Gets cached category tags from the database
|
||||
#[tauri::command]
|
||||
pub async fn tags_get_categories() -> Result<Vec<Category>> {
|
||||
Ok(theseus::tags::get_category_tags().await?)
|
||||
pub async fn tags_get_categories(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<Category>> {
|
||||
let 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<Vec<String>> {
|
||||
Ok(theseus::tags::get_report_type_tags().await?)
|
||||
pub async fn tags_get_report_types(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<String>> {
|
||||
let 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<Vec<Loader>> {
|
||||
Ok(theseus::tags::get_loader_tags().await?)
|
||||
pub async fn tags_get_loaders(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<Loader>> {
|
||||
let 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<Vec<GameVersion>> {
|
||||
Ok(theseus::tags::get_game_version_tags().await?)
|
||||
pub async fn tags_get_game_versions(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<GameVersion>> {
|
||||
let 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<Vec<DonationPlatform>> {
|
||||
Ok(theseus::tags::get_donation_platform_tags().await?)
|
||||
pub async fn tags_get_donation_platforms(
|
||||
invocation_context: theseus::InvocationContext,
|
||||
) -> Result<Vec<DonationPlatform>> {
|
||||
let context = invocation_context;
|
||||
Ok(theseus::tags::get_donation_platform_tags(&context).await?)
|
||||
}
|
||||
|
||||
@@ -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<Option<ProtocolVersion>> {
|
||||
Ok(worlds::get_instance_protocol_version(instance_id).await?)
|
||||
let 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<ProcessMetadata> {
|
||||
let process =
|
||||
instance::run(instance_id, QuickPlayType::Singleplayer(world)).await?;
|
||||
let 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<ProcessMetadata> {
|
||||
let context = invocation_context;
|
||||
let process = instance::run(
|
||||
&context,
|
||||
instance_id,
|
||||
QuickPlayType::Server(ServerAddress::Unresolved(address.to_owned())),
|
||||
)
|
||||
|
||||
@@ -43,8 +43,10 @@ flate2 = { workspace = true }
|
||||
fs4 = { workspace = true, features = ["tokio"] }
|
||||
futures = { workspace = true, features = ["alloc", "async-await"] }
|
||||
futures-lite = { workspace = true }
|
||||
governor = { workspace = true }
|
||||
heck = { workspace = true }
|
||||
hickory-resolver = { workspace = true }
|
||||
httpdate = { workspace = true }
|
||||
indicatif = { workspace = true, optional = true }
|
||||
itertools = { workspace = true }
|
||||
modrinth-content-management = { workspace = true }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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 [<get_ $variant:snake>](
|
||||
context: &InvocationContext,
|
||||
id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Option<$type>>
|
||||
{
|
||||
let state = crate::State::get().await?;
|
||||
Ok(CachedEntry::[<get_ $variant:snake _many>](&[id], cache_behaviour, &state.pool, &state.api_semaphore).await?.into_iter().next())
|
||||
Ok(CachedEntry::[<get_ $variant:snake _many>](context, &[id], cache_behaviour, &state.pool, &state.api_semaphore).await?.into_iter().next())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn [<get_ $variant:snake _many>](
|
||||
context: &InvocationContext,
|
||||
ids: &[&str],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Vec<$type>>
|
||||
{
|
||||
let state = crate::State::get().await?;
|
||||
let entries =
|
||||
CachedEntry::[<get_ $variant:snake _many>](ids, None, &state.pool, &state.api_semaphore).await?;
|
||||
CachedEntry::[<get_ $variant:snake _many>](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: &InvocationContext,
|
||||
project_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Option<Vec<Version>>> {
|
||||
let state = crate::State::get().await?;
|
||||
CachedEntry::get_project_versions(
|
||||
context,
|
||||
project_id,
|
||||
cache_behaviour,
|
||||
&state.pool,
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use crate::InvocationContext;
|
||||
use crate::state::{FriendsSocket, UserFriend};
|
||||
use ariadne::users::UserStatus;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn friends() -> crate::Result<Vec<UserFriend>> {
|
||||
pub async fn friends(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<UserFriend>> {
|
||||
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<Vec<UserStatus>> {
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn add_friend(user_id: &str) -> crate::Result<()> {
|
||||
pub async fn add_friend(
|
||||
context: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> crate::Result<Vec<crate::state::instances::InstanceFile>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<DashMap<String, ContentFile>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Vec<ContentItem>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Vec<ContentItem>> {
|
||||
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: &InvocationContext,
|
||||
dependencies: Vec<Dependency>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Vec<ContentItem>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> crate::Result<Option<LinkedModpackInfo>> {
|
||||
let state = State::get().await?;
|
||||
crate::state::get_linked_modpack_info(
|
||||
context,
|
||||
instance_id,
|
||||
None,
|
||||
cache_behaviour,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::content::get_projects;
|
||||
use super::get::get;
|
||||
use super::paths::get_full_path;
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
export_path: PathBuf,
|
||||
included_export_candidates: Vec<String>,
|
||||
@@ -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: &InvocationContext,
|
||||
metadata: &InstanceMetadata,
|
||||
version_id: String,
|
||||
description: Option<String>,
|
||||
@@ -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::<Vec<_>>();
|
||||
let versions = CachedEntry::get_version_many(
|
||||
context,
|
||||
&projects.iter().map(|x| &*x.1).collect::<Vec<_>>(),
|
||||
None,
|
||||
&state.pool,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use crate::state::{JavaVersion, State};
|
||||
use crate::{
|
||||
InvocationContext,
|
||||
state::{JavaVersion, State},
|
||||
};
|
||||
|
||||
pub async fn get_optimal_jre_key(
|
||||
invocation_context: &InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> crate::Result<Option<JavaVersion>> {
|
||||
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(
|
||||
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(
|
||||
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(
|
||||
invocation_context,
|
||||
&state,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
name: String,
|
||||
game_version: String,
|
||||
modloader: ModLoader,
|
||||
@@ -20,6 +22,7 @@ pub(crate) async fn create(
|
||||
) -> crate::Result<InstanceMetadata> {
|
||||
let state = State::get().await?;
|
||||
let instance = crate::state::create_instance(
|
||||
context,
|
||||
CreateInstance {
|
||||
name,
|
||||
path: None,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
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;
|
||||
@@ -20,6 +21,7 @@ pub struct InstallProjectWithDependenciesRequest {
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn update_all_projects(
|
||||
context: &InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> crate::Result<HashMap<String, String>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
project_path: &str,
|
||||
skip_send_event: Option<bool>,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
request: InstallProjectWithDependenciesRequest,
|
||||
) -> crate::Result<ResolveContentPlan> {
|
||||
@@ -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<String> {
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn switch_project_version_with_dependencies(
|
||||
context: &InvocationContext,
|
||||
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<bool> {
|
||||
pub async fn is_file_on_modrinth(
|
||||
context: &InvocationContext,
|
||||
path: &Path,
|
||||
) -> crate::Result<bool> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
) -> crate::Result<crate::install::InstallJobSnapshot> {
|
||||
@@ -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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> crate::Result<crate::install::InstallJobSnapshot> {
|
||||
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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::content::get_projects;
|
||||
use crate::InvocationContext;
|
||||
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(
|
||||
invocation_context: &InvocationContext,
|
||||
instance_id: &str,
|
||||
quick_play_type: QuickPlayType,
|
||||
) -> crate::Result<ProcessMetadata> {
|
||||
@@ -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(
|
||||
invocation_context,
|
||||
instance_id,
|
||||
&default_account,
|
||||
quick_play_type,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(credentials))]
|
||||
async fn run_credentials(
|
||||
invocation_context: &InvocationContext,
|
||||
instance_id: &str,
|
||||
credentials: &Credentials,
|
||||
quick_play_type: QuickPlayType,
|
||||
) -> crate::Result<ProcessMetadata> {
|
||||
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(
|
||||
invocation_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(
|
||||
invocation_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(
|
||||
invocation_context: &InvocationContext,
|
||||
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<String, serde_json::Value> = HashMap::new();
|
||||
|
||||
for (_, project) in get_projects(instance_id, None).await? {
|
||||
for (_, project) in
|
||||
get_projects(invocation_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(
|
||||
invocation_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,
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ use sysinfo::{MemoryRefreshKind, RefreshKind};
|
||||
use crate::util::io;
|
||||
use crate::util::jre::extract_java_version;
|
||||
use crate::{
|
||||
LoadingBarType, State,
|
||||
InvocationContext, LoadingBarType, 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<PathBuf> {
|
||||
auto_install_java_with_loading(java_version, true).await
|
||||
pub async fn auto_install_java(
|
||||
context: &InvocationContext,
|
||||
java_version: u32,
|
||||
) -> crate::Result<PathBuf> {
|
||||
auto_install_java_with_loading(context, java_version, true).await
|
||||
}
|
||||
|
||||
pub async fn auto_install_java_with_loading(
|
||||
context: &InvocationContext,
|
||||
java_version: u32,
|
||||
show_loading: bool,
|
||||
) -> crate::Result<PathBuf> {
|
||||
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: &InvocationContext,
|
||||
java_version: u32,
|
||||
reporter: InstallProgressReporter,
|
||||
) -> crate::Result<PathBuf> {
|
||||
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: &InvocationContext,
|
||||
java_version: u32,
|
||||
show_loading: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
@@ -167,6 +173,7 @@ async fn auto_install_java_inner(
|
||||
.await?;
|
||||
}
|
||||
let packages = fetch_json::<Vec<Package>>(
|
||||
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,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use crate::State;
|
||||
use crate::state::CachedEntry;
|
||||
use crate::{InvocationContext, State};
|
||||
pub use daedalus::minecraft::VersionManifest;
|
||||
pub use daedalus::modded::Manifest;
|
||||
|
||||
#[tracing::instrument]
|
||||
pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
|
||||
pub async fn get_minecraft_versions(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<VersionManifest> {
|
||||
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<VersionManifest> {
|
||||
}
|
||||
|
||||
// #[tracing::instrument]
|
||||
pub async fn get_loader_versions(loader: &str) -> crate::Result<Manifest> {
|
||||
pub async fn get_loader_versions(
|
||||
context: &InvocationContext,
|
||||
loader: &str,
|
||||
) -> crate::Result<Manifest> {
|
||||
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,
|
||||
|
||||
@@ -37,7 +37,7 @@ pub mod data {
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{
|
||||
State,
|
||||
InvocationContext, State,
|
||||
data::*,
|
||||
event::CommandPayload,
|
||||
install, instance, jre, metadata, minecraft_auth, mr_auth, pack,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
code: &str,
|
||||
) -> crate::Result<ModrinthCredentials> {
|
||||
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<Option<ModrinthCredentials>> {
|
||||
pub async fn get_credentials(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Option<ModrinthCredentials>> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ pub async fn is_valid_atlauncher(instance_folder: PathBuf) -> bool {
|
||||
#[tracing::instrument]
|
||||
|
||||
pub async fn import_atlauncher(
|
||||
context: &crate::InvocationContext,
|
||||
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::InvocationContext,
|
||||
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,
|
||||
|
||||
@@ -49,6 +49,7 @@ pub async fn is_valid_curseforge(instance_folder: PathBuf) -> bool {
|
||||
}
|
||||
|
||||
pub async fn import_curseforge(
|
||||
context: &crate::InvocationContext,
|
||||
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,
|
||||
|
||||
@@ -41,6 +41,7 @@ pub async fn is_valid_gdlauncher(instance_folder: PathBuf) -> bool {
|
||||
}
|
||||
|
||||
pub async fn import_gdlauncher(
|
||||
context: &crate::InvocationContext,
|
||||
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,
|
||||
|
||||
@@ -177,6 +177,7 @@ async fn load_instance_cfg(file_path: &Path) -> crate::Result<MMCInstance> {
|
||||
|
||||
// #[tracing::instrument]
|
||||
pub async fn import_mmc(
|
||||
context: &crate::InvocationContext,
|
||||
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::InvocationContext,
|
||||
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,
|
||||
|
||||
@@ -115,6 +115,7 @@ pub async fn get_importable_instances(
|
||||
}
|
||||
|
||||
pub(crate) async fn import_instance_with_reporter(
|
||||
context: &crate::InvocationContext,
|
||||
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::InvocationContext,
|
||||
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::InvocationContext,
|
||||
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),
|
||||
|
||||
@@ -164,6 +164,7 @@ pub struct CreatePackDescription {
|
||||
}
|
||||
|
||||
pub async fn get_instance_from_pack(
|
||||
context: &crate::InvocationContext,
|
||||
location: CreatePackLocation,
|
||||
) -> crate::Result<CreatePackInstance> {
|
||||
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::InvocationContext,
|
||||
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::InvocationContext,
|
||||
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(),
|
||||
|
||||
@@ -238,6 +238,7 @@ where
|
||||
}
|
||||
|
||||
pub(crate) async fn get_external_files_from_mrpack(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
file: &CreatePackFile,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
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::<Vec<_>>();
|
||||
let recognized_hashes = match CachedEntry::get_file_many(
|
||||
invocation_context,
|
||||
&hashes,
|
||||
None,
|
||||
&state.pool,
|
||||
@@ -396,6 +398,7 @@ where
|
||||
}
|
||||
|
||||
pub(crate) async fn install_zipped_mrpack_files_with_reporter(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
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(
|
||||
invocation_context,
|
||||
instance_id.clone(),
|
||||
&description,
|
||||
&pack.name,
|
||||
@@ -629,6 +633,7 @@ pub(crate) async fn install_zipped_mrpack_files_with_reporter(
|
||||
.collect::<Vec<_>>();
|
||||
let file_infos_by_hash = Arc::new(
|
||||
CachedEntry::get_file_many(
|
||||
invocation_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(
|
||||
invocation_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(
|
||||
invocation_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(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
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(
|
||||
invocation_context,
|
||||
&all_hashes.iter().map(|x| &**x).collect::<Vec<_>>(),
|
||||
None,
|
||||
&state.pool,
|
||||
|
||||
@@ -1,72 +1,94 @@
|
||||
//! Theseus tag management interface
|
||||
use crate::state::CachedEntry;
|
||||
pub use crate::{
|
||||
State,
|
||||
InvocationContext, State,
|
||||
state::{Category, DonationPlatform, GameVersion, Loader},
|
||||
};
|
||||
|
||||
/// Get category tags
|
||||
#[tracing::instrument]
|
||||
pub async fn get_category_tags() -> crate::Result<Vec<Category>> {
|
||||
pub async fn get_category_tags(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<Category>> {
|
||||
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<Vec<String>> {
|
||||
pub async fn get_report_type_tags(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
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<Vec<Loader>> {
|
||||
pub async fn get_loader_tags(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<Loader>> {
|
||||
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<Vec<GameVersion>> {
|
||||
pub async fn get_game_version_tags(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<GameVersion>> {
|
||||
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<Vec<DonationPlatform>>
|
||||
{
|
||||
pub async fn get_donation_platform_tags(
|
||||
context: &InvocationContext,
|
||||
) -> crate::Result<Vec<DonationPlatform>> {
|
||||
let state = State::get().await?;
|
||||
let donation_platforms = CachedEntry::get_donation_platforms(
|
||||
context,
|
||||
None,
|
||||
&state.pool,
|
||||
&state.api_semaphore,
|
||||
|
||||
@@ -923,6 +923,7 @@ mod servers_data {
|
||||
}
|
||||
|
||||
pub async fn get_instance_protocol_version(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
instance_id: &str,
|
||||
) -> Result<Option<ProtocolVersion>> {
|
||||
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(
|
||||
invocation_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(
|
||||
invocation_context,
|
||||
&metadata.applied_content_set.game_version,
|
||||
metadata.applied_content_set.loader,
|
||||
metadata.applied_content_set.loader_version.as_deref(),
|
||||
|
||||
@@ -79,6 +79,9 @@ pub enum ErrorKind {
|
||||
#[error("Too many API errors, try again in {0} minutes")]
|
||||
ApiIsDownError(u32),
|
||||
|
||||
#[error("Too many requests, retry in {}", format_seconds(*.retry_in_seconds))]
|
||||
Ratelimited { retry_in_seconds: u64 },
|
||||
|
||||
#[error("{0}")]
|
||||
LabrinthError(LabrinthError),
|
||||
|
||||
@@ -192,6 +195,27 @@ pub enum ErrorKind {
|
||||
DiscordRichPresenceError(#[from] discord_rich_presence::error::Error),
|
||||
}
|
||||
|
||||
fn format_seconds(seconds: u64) -> String {
|
||||
let plural = |unit: u64| if unit == 1 { "" } else { "s" };
|
||||
|
||||
if seconds <= 59 {
|
||||
return format!("{seconds} second{}", plural(seconds));
|
||||
}
|
||||
|
||||
let minutes = seconds / 60;
|
||||
let rem_seconds = seconds % 60;
|
||||
|
||||
if rem_seconds == 0 {
|
||||
return format!("{minutes} minutes");
|
||||
}
|
||||
|
||||
format!(
|
||||
"{minutes} minute{} and {rem_seconds} second{}",
|
||||
plural(minutes),
|
||||
plural(rem_seconds)
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub raw: Arc<ErrorKind>,
|
||||
|
||||
@@ -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, InvocationContext};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn create_instance(
|
||||
context: &InvocationContext,
|
||||
name: String,
|
||||
game_version: String,
|
||||
loader: ModLoader,
|
||||
@@ -31,64 +32,89 @@ pub async fn create_instance(
|
||||
icon_path: Option<String>,
|
||||
link: InstanceLink,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
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: &InvocationContext,
|
||||
location: CreatePackLocation,
|
||||
post_install_edit: Option<InstallPostInstallEdit>,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
start(InstallRequest::CreateModpackInstance {
|
||||
location,
|
||||
post_install_edit,
|
||||
})
|
||||
start(
|
||||
context,
|
||||
InstallRequest::CreateModpackInstance {
|
||||
location,
|
||||
post_install_edit,
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn import_instance(
|
||||
context: &InvocationContext,
|
||||
launcher_type: crate::api::pack::import::ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
instance_folder: String,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
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: &InvocationContext,
|
||||
source_instance_id: String,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
start(InstallRequest::DuplicateInstance { source_instance_id }).await
|
||||
start(
|
||||
context,
|
||||
InstallRequest::DuplicateInstance { source_instance_id },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn install_existing_instance(
|
||||
context: &InvocationContext,
|
||||
instance_id: String,
|
||||
force: bool,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
start(InstallRequest::InstallExistingInstance { instance_id, force }).await
|
||||
start(
|
||||
context,
|
||||
InstallRequest::InstallExistingInstance { instance_id, force },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn install_pack_to_existing_instance(
|
||||
context: &InvocationContext,
|
||||
instance_id: String,
|
||||
location: CreatePackLocation,
|
||||
post_install_edit: Option<InstallPostInstallEdit>,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
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<String> {
|
||||
diagnostics::build_job_support_details(&job, &state).await
|
||||
}
|
||||
|
||||
pub async fn retry_job(job_id: Uuid) -> crate::Result<InstallJobSnapshot> {
|
||||
pub async fn retry_job(
|
||||
context: &InvocationContext,
|
||||
job_id: Uuid,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
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<InstallJobSnapshot> {
|
||||
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<InstallJobSnapshot> {
|
||||
)
|
||||
.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<InstallJobSnapshot> {
|
||||
async fn start(
|
||||
context: &InvocationContext,
|
||||
request: InstallRequest,
|
||||
) -> crate::Result<InstallJobSnapshot> {
|
||||
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: &InvocationContext,
|
||||
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: InvocationContext) {
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
job_id: Uuid,
|
||||
job_state: &InstallJobState,
|
||||
state: &State,
|
||||
@@ -761,6 +810,7 @@ async fn remove_existing_pack_content(
|
||||
.collect::<HashSet<_>>();
|
||||
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: &InvocationContext,
|
||||
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,
|
||||
@@ -1074,9 +1128,9 @@ fn install_error_code(
|
||||
PreparingJava => "java_error",
|
||||
_ => "metadata_error",
|
||||
},
|
||||
ErrorKind::FetchError(_) | ErrorKind::ApiIsDownError(_) => {
|
||||
"network_error"
|
||||
}
|
||||
ErrorKind::FetchError(_)
|
||||
| ErrorKind::ApiIsDownError(_)
|
||||
| ErrorKind::Ratelimited { .. } => "network_error",
|
||||
ErrorKind::Any(_)
|
||||
if matches!(
|
||||
phase,
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::install::{
|
||||
use crate::instance::QuickPlayType;
|
||||
use crate::launcher::parse_rules;
|
||||
use crate::{
|
||||
InvocationContext,
|
||||
event::{
|
||||
LoadingBarId,
|
||||
emit::{emit_loading, loading_try_for_each_concurrent},
|
||||
@@ -142,24 +143,33 @@ impl MinecraftDownloadProgress {
|
||||
}
|
||||
|
||||
async fn fetch_minecraft_file(
|
||||
invocation_context: &InvocationContext,
|
||||
st: &State,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
expected_size: Option<u64>,
|
||||
progress: Option<MinecraftDownloadProgress>,
|
||||
context: InstallErrorContext,
|
||||
error_context: InstallErrorContext,
|
||||
) -> crate::Result<bytes::Bytes> {
|
||||
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(
|
||||
invocation_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(
|
||||
invocation_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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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),
|
||||
|
||||
@@ -156,6 +156,7 @@ pub async fn get_java_version_from_launch_context(
|
||||
}
|
||||
|
||||
pub async fn get_loader_version_from_profile(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
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(
|
||||
invocation_context,
|
||||
loader.as_meta_str(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(loaders) =
|
||||
loader_versions_for_game_version(&versions, game_version)
|
||||
@@ -218,10 +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(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
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(invocation_context)
|
||||
.await?;
|
||||
|
||||
if let Some(idx) = minecraft
|
||||
.versions
|
||||
@@ -234,6 +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(
|
||||
invocation_context,
|
||||
Some(crate::state::CacheBehaviour::MustRevalidate),
|
||||
&state.pool,
|
||||
&state.api_semaphore,
|
||||
@@ -262,12 +270,13 @@ async fn get_instance_full_path(instance_path: &str) -> crate::Result<PathBuf> {
|
||||
}
|
||||
|
||||
pub async fn install_minecraft_with_reporter(
|
||||
context: &InstanceLaunchContext,
|
||||
invocation_context: &crate::InvocationContext,
|
||||
launch_context: &InstanceLaunchContext,
|
||||
repairing: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
) -> 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 +318,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(
|
||||
invocation_context,
|
||||
&content_set.game_version,
|
||||
&state,
|
||||
)
|
||||
.await?;
|
||||
let version = &minecraft.versions[version_index];
|
||||
let minecraft_updated = version_index
|
||||
<= minecraft
|
||||
@@ -332,6 +345,7 @@ pub async fn install_minecraft_with_reporter(
|
||||
}
|
||||
|
||||
let mut loader_version = get_loader_version_from_profile(
|
||||
invocation_context,
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
content_set.loader_version.as_deref(),
|
||||
@@ -341,6 +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(
|
||||
invocation_context,
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
Some("stable"),
|
||||
@@ -362,6 +377,7 @@ pub async fn install_minecraft_with_reporter(
|
||||
|
||||
// Download version info (5)
|
||||
let mut version_info = download::download_version_info(
|
||||
invocation_context,
|
||||
&state,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
@@ -392,18 +408,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(
|
||||
invocation_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(
|
||||
invocation_context,
|
||||
key,
|
||||
true,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
(path, true)
|
||||
@@ -443,6 +466,7 @@ pub async fn install_minecraft_with_reporter(
|
||||
.await?;
|
||||
}
|
||||
download::download_minecraft(
|
||||
invocation_context,
|
||||
&state,
|
||||
&version_info,
|
||||
loading_bar.as_ref(),
|
||||
@@ -622,12 +646,13 @@ pub async fn install_minecraft_with_reporter(
|
||||
}
|
||||
|
||||
pub async fn install_minecraft_for_instance_id_with_reporter(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
instance_id: &str,
|
||||
repairing: bool,
|
||||
reporter: Option<InstallProgressReporter>,
|
||||
) -> 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 +664,13 @@ pub async fn install_minecraft_for_instance_id_with_reporter(
|
||||
))
|
||||
})?;
|
||||
|
||||
install_minecraft_with_reporter(&context, repairing, reporter).await
|
||||
install_minecraft_with_reporter(
|
||||
invocation_context,
|
||||
&launch_context,
|
||||
repairing,
|
||||
reporter,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_protocol_version_from_jar(
|
||||
@@ -698,6 +729,7 @@ fn link_project_and_version(
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn launch_minecraft(
|
||||
invocation_context: &crate::InvocationContext,
|
||||
java_args: &[String],
|
||||
env_args: &[(String, String)],
|
||||
mc_set_options: &[(String, String)],
|
||||
@@ -706,11 +738,11 @@ pub async fn launch_minecraft(
|
||||
resolution: &WindowSize,
|
||||
credentials: &Credentials,
|
||||
post_exit_hook: Option<String>,
|
||||
context: &InstanceLaunchContext,
|
||||
launch_context: &InstanceLaunchContext,
|
||||
mut quick_play_type: QuickPlayType,
|
||||
) -> crate::Result<ProcessMetadata> {
|
||||
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 +764,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(
|
||||
invocation_context,
|
||||
&content_set.game_version,
|
||||
&state,
|
||||
)
|
||||
.await?;
|
||||
let version = &minecraft.versions[version_index];
|
||||
let minecraft_updated = version_index
|
||||
<= minecraft
|
||||
@@ -743,6 +779,7 @@ pub async fn launch_minecraft(
|
||||
.unwrap_or(0);
|
||||
|
||||
let loader_version = get_loader_version_from_profile(
|
||||
invocation_context,
|
||||
&content_set.game_version,
|
||||
content_set.loader,
|
||||
content_set.loader_version.as_deref(),
|
||||
@@ -763,6 +800,7 @@ pub async fn launch_minecraft(
|
||||
});
|
||||
|
||||
let mut version_info = download::download_version_info(
|
||||
invocation_context,
|
||||
&state,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
@@ -780,6 +818,7 @@ pub async fn launch_minecraft(
|
||||
.unwrap_or(0);
|
||||
if requires_logging_info {
|
||||
version_info = download::download_version_info(
|
||||
invocation_context,
|
||||
&state,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
@@ -791,11 +830,18 @@ pub async fn launch_minecraft(
|
||||
}
|
||||
}
|
||||
|
||||
let _ =
|
||||
download_log_config(&state, &version_info, None, false, None).await?;
|
||||
let _ = download_log_config(
|
||||
invocation_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 +1094,7 @@ pub async fn launch_minecraft(
|
||||
state
|
||||
.process_manager
|
||||
.insert_new_process(
|
||||
invocation_context,
|
||||
&instance.id,
|
||||
&instance.path,
|
||||
&instance.name,
|
||||
@@ -1062,7 +1109,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)),
|
||||
|
||||
@@ -16,6 +16,7 @@ mod event;
|
||||
pub mod install;
|
||||
mod launcher;
|
||||
mod logger;
|
||||
mod operation;
|
||||
mod state;
|
||||
|
||||
pub use api::*;
|
||||
@@ -25,6 +26,7 @@ pub use event::{
|
||||
emit::init_loading,
|
||||
};
|
||||
pub use logger::start_logger;
|
||||
pub use operation::{InvocationContext, REQUEST_CONTEXT_HEADER};
|
||||
pub use state::State;
|
||||
pub use util::fetch::DownloadReason;
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
pub const REQUEST_CONTEXT_HEADER: &str = "Modrinth-App-Request-Context";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct InvocationContext {
|
||||
pub cause: String,
|
||||
}
|
||||
|
||||
impl InvocationContext {
|
||||
pub fn new(cause: impl Into<String>) -> Self {
|
||||
Self {
|
||||
cause: cause.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cause(&self) -> &str {
|
||||
&self.cause
|
||||
}
|
||||
|
||||
pub fn request_context_header(&self) -> &str {
|
||||
&self.cause
|
||||
}
|
||||
|
||||
pub fn referer(&self) -> String {
|
||||
format!("https://tauri.modrinth.app/_rc/{}", self.cause)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn accepts_frontend_defined_causes() {
|
||||
let context: InvocationContext =
|
||||
serde_json::from_str(r#"{"cause":"new/frontend/cause"}"#).unwrap();
|
||||
|
||||
assert_eq!(context.cause(), "new/frontend/cause");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invocation_context_contains_only_a_cause() {
|
||||
assert!(
|
||||
serde_json::from_str::<InvocationContext>(
|
||||
r#"{"cause":"navigation/home","id":"frontend-id"}"#
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn headers_use_the_frontend_cause_unchanged() {
|
||||
let context = InvocationContext::new("navigation/instance/content");
|
||||
|
||||
assert_eq!(
|
||||
context.referer(),
|
||||
"https://tauri.modrinth.app/_rc/navigation/instance/content"
|
||||
);
|
||||
assert_eq!(
|
||||
context.request_context_header(),
|
||||
"navigation/instance/content"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
use crate::state::ProjectType;
|
||||
use crate::util::fetch::{FetchSemaphore, fetch_json, sha1_async};
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -804,17 +805,19 @@ macro_rules! impl_cache_methods {
|
||||
paste::paste! {
|
||||
#[tracing::instrument(skip(pool, fetch_semaphore))]
|
||||
pub async fn [<get_ $variant:snake>](
|
||||
context: &InvocationContext,
|
||||
id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<$type>>
|
||||
{
|
||||
Ok(Self::[<get_ $variant:snake _many>](&[id], cache_behaviour, pool, fetch_semaphore).await?.into_iter().next())
|
||||
Ok(Self::[<get_ $variant:snake _many>](context, &[id], cache_behaviour, pool, fetch_semaphore).await?.into_iter().next())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(pool, fetch_semaphore))]
|
||||
pub async fn [<get_ $variant:snake _many>](
|
||||
context: &InvocationContext,
|
||||
ids: &[&str],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
@@ -822,7 +825,7 @@ macro_rules! impl_cache_methods {
|
||||
) -> crate::Result<Vec<$type>>
|
||||
{
|
||||
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 [<get_ $variant:snake>] (
|
||||
context: &InvocationContext,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<$type>>
|
||||
{
|
||||
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: &InvocationContext,
|
||||
type_: CacheValueType,
|
||||
key: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -897,6 +902,7 @@ impl CachedEntry {
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<Self>> {
|
||||
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: &InvocationContext,
|
||||
type_: CacheValueType,
|
||||
keys: &[&str],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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.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?;
|
||||
|
||||
let values = Self::fetch_many(
|
||||
&context,
|
||||
type_,
|
||||
expired_keys,
|
||||
&state.api_semaphore,
|
||||
@@ -1084,12 +1094,14 @@ impl CachedEntry {
|
||||
}
|
||||
|
||||
async fn fetch_many(
|
||||
context: &InvocationContext,
|
||||
type_: CacheValueType,
|
||||
keys: DashSet<impl Display + Eq + Hash + Serialize>,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<Vec<(Self, bool)>> {
|
||||
async fn fetch_many_batched<T: DeserializeOwned>(
|
||||
context: &InvocationContext,
|
||||
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::<Vec<_>>(
|
||||
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::<Vec<TeamMember>>(
|
||||
context,
|
||||
Method::GET,
|
||||
env!("MODRINTH_API_URL_V3"),
|
||||
"teams?ids=",
|
||||
@@ -1287,6 +1303,7 @@ impl CachedEntry {
|
||||
}
|
||||
CacheValueType::Organization => {
|
||||
let mut orgs = fetch_many_batched::<Organization>(
|
||||
context,
|
||||
Method::GET,
|
||||
env!("MODRINTH_API_URL_V3"),
|
||||
"organizations?ids=",
|
||||
@@ -1343,6 +1360,7 @@ impl CachedEntry {
|
||||
}
|
||||
CacheValueType::File => {
|
||||
let mut versions = fetch_json::<HashMap<String, Version>>(
|
||||
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<String, Vec<Version>>,
|
||||
>(
|
||||
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::<Vec<Version>>(
|
||||
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: &InvocationContext,
|
||||
version_id: &str,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<CachedModpackFiles>> {
|
||||
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: &InvocationContext,
|
||||
project_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<Vec<Version>>> {
|
||||
let entry = Self::get(
|
||||
context,
|
||||
CacheValueType::ProjectVersions,
|
||||
project_id,
|
||||
cache_behaviour,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::ErrorKind;
|
||||
use crate::InvocationContext;
|
||||
use crate::data::ModrinthCredentials;
|
||||
use crate::event::FriendPayload;
|
||||
use crate::event::emit::{emit_friend, emit_notification};
|
||||
@@ -68,12 +69,14 @@ impl FriendsSocket {
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn connect(
|
||||
&self,
|
||||
context: &InvocationContext,
|
||||
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 = InvocationContext::new("background/friends");
|
||||
|
||||
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: &InvocationContext,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Vec<UserFriend>> {
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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 InvocationContext,
|
||||
state: &'a State,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
}
|
||||
@@ -57,6 +59,7 @@ impl ContentMetadataProvider for CachedEntryContentProvider<'_> {
|
||||
) -> Result<Option<modrinth_content_management::Version>, 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<Vec<modrinth_content_management::Version>, 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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
project_path: &str,
|
||||
version_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content: &ResolvedContent,
|
||||
reason: DownloadReason,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
version_id: &str,
|
||||
reason: DownloadReason,
|
||||
@@ -350,6 +368,7 @@ pub(crate) async fn add_project_from_version(
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
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),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
project_path: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
project_path: &str,
|
||||
update: &ContentUpdate,
|
||||
state: &State,
|
||||
) -> crate::Result<String> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<HashMap<String, String>> {
|
||||
@@ -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: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<BulkUpdatePlan> {
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<HashSet<String>> {
|
||||
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: &InvocationContext,
|
||||
root_versions: Vec<Version>,
|
||||
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: &InvocationContext,
|
||||
dependency: &Dependency,
|
||||
content_set: &ContentSet,
|
||||
state: &State,
|
||||
@@ -543,15 +567,19 @@ async fn resolve_dependency_version(
|
||||
project_versions_cache: &mut HashMap<String, Option<Vec<Version>>>,
|
||||
) -> crate::Result<Option<Version>> {
|
||||
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: &InvocationContext,
|
||||
version_id: &str,
|
||||
version_cache: &mut HashMap<String, Option<Version>>,
|
||||
state: &State,
|
||||
) -> crate::Result<Option<Version>> {
|
||||
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: &InvocationContext,
|
||||
project_id: &str,
|
||||
project_versions_cache: &mut HashMap<String, Option<Vec<Version>>>,
|
||||
state: &State,
|
||||
) -> crate::Result<Option<Vec<Version>>> {
|
||||
if !project_versions_cache.contains_key(project_id) {
|
||||
let versions = CachedEntry::get_project_versions(
|
||||
context,
|
||||
project_id,
|
||||
Some(CacheBehaviour::MustRevalidate),
|
||||
&state.pool,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
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::<HashMap<_, _>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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: &InvocationContext,
|
||||
candidates: &[UpdateCandidate],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
state: &State,
|
||||
@@ -168,6 +174,7 @@ async fn installed_update_channels(
|
||||
.map(|candidate| candidate.current_version_id.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let versions = CachedEntry::get_version_many(
|
||||
context,
|
||||
&version_ids,
|
||||
cache_behaviour,
|
||||
&state.pool,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
input: CreateInstance,
|
||||
state: &State,
|
||||
) -> crate::Result<Instance> {
|
||||
@@ -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: &InvocationContext,
|
||||
icon_path: Option<&str>,
|
||||
state: &State,
|
||||
) -> crate::Result<Option<String>> {
|
||||
@@ -178,6 +183,7 @@ async fn resolve_icon_path(
|
||||
|| icon.starts_with("http://")
|
||||
{
|
||||
let fetched = fetch::fetch(
|
||||
context,
|
||||
icon,
|
||||
None,
|
||||
None,
|
||||
|
||||
@@ -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::{InvocationContext, 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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content_set_id: Option<&str>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content_set_id: Option<&str>,
|
||||
state: &State,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content_set_id: Option<&str>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content_set_id: Option<&str>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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::<Vec<_>>();
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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: &InvocationContext,
|
||||
instance_id: &str,
|
||||
content_set_id: Option<&str>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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: &InvocationContext,
|
||||
dependencies: &[Dependency],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
@@ -464,6 +491,7 @@ pub(crate) async fn dependencies_to_content_items(
|
||||
.filter_map(|dependency| dependency.version_id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
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: &InvocationContext,
|
||||
resolved: &ResolvedContentScope,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
state: &State,
|
||||
filter: ContentFilter<'_>,
|
||||
) -> crate::Result<DashMap<String, ContentFile>> {
|
||||
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::<Vec<_>>();
|
||||
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::<HashMap<_, _>>();
|
||||
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::<Vec<_>>();
|
||||
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: &InvocationContext,
|
||||
file_info_by_hash: &HashMap<String, CachedFile>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
pool: &SqlitePool,
|
||||
@@ -760,6 +794,7 @@ async fn get_installed_update_channels(
|
||||
}
|
||||
let version_id_refs = version_ids.iter().copied().collect::<Vec<_>>();
|
||||
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: &InvocationContext,
|
||||
instance: &Instance,
|
||||
files: &[(String, ContentFile)],
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -831,6 +867,7 @@ async fn content_files_to_content_items(
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
let meta = resolve_metadata(
|
||||
context,
|
||||
&project_ids,
|
||||
&version_ids,
|
||||
cache_behaviour,
|
||||
@@ -916,6 +953,7 @@ struct ResolvedMetadata {
|
||||
}
|
||||
|
||||
async fn resolve_metadata(
|
||||
context: &InvocationContext,
|
||||
project_ids: &HashSet<String>,
|
||||
version_ids: &HashSet<String>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
@@ -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: &InvocationContext,
|
||||
version_id: &str,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<ModpackIdentifiers>> {
|
||||
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: &InvocationContext,
|
||||
version_id: &str,
|
||||
content_set: &ContentSet,
|
||||
pool: &SqlitePool,
|
||||
fetch_semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<ModpackIdentifiers> {
|
||||
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::<Vec<_>>();
|
||||
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),
|
||||
|
||||
@@ -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::{InvocationContext, State};
|
||||
use chrono::Utc;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub(crate) async fn sync_content_files(
|
||||
context: &InvocationContext,
|
||||
instance_id: &str,
|
||||
state: &State,
|
||||
) -> crate::Result<Vec<InstanceFile>> {
|
||||
@@ -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: &InvocationContext,
|
||||
instance: &Instance,
|
||||
state: &State,
|
||||
) -> crate::Result<Vec<InstanceFile>> {
|
||||
@@ -33,6 +35,7 @@ pub(crate) async fn sync_instance_content_files(
|
||||
.map(|file| file.hash_cache_key.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
let hashes = CachedEntry::get_file_hash_many(
|
||||
context,
|
||||
&cache_keys,
|
||||
None,
|
||||
&state.pool,
|
||||
|
||||
@@ -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::{InvocationContext, State};
|
||||
use notify::{RecommendedWatcher, RecursiveMode};
|
||||
use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
@@ -34,6 +34,12 @@ pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
||||
tracing::info!(parent: &span, "Initing watcher");
|
||||
while let Some(res) = rx.recv().await {
|
||||
let _span = span.enter();
|
||||
let context =
|
||||
InvocationContext::new("instance/refresh/filesystem_watch");
|
||||
tracing::debug!(
|
||||
invocation_cause = %context.cause(),
|
||||
"Processing filesystem watcher refresh"
|
||||
);
|
||||
|
||||
match res {
|
||||
Ok(events) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Theseus state management system
|
||||
use crate::InvocationContext;
|
||||
use crate::util::fetch::{FetchSemaphore, IoSemaphore};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
@@ -114,20 +115,23 @@ impl State {
|
||||
)
|
||||
.await;
|
||||
|
||||
let auth_context = InvocationContext::new("auth/session_refresh");
|
||||
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 {
|
||||
tracing::error!("Error running discord RPC: {e}");
|
||||
}
|
||||
|
||||
let friends_context = InvocationContext::new("background/friends");
|
||||
let _ = state
|
||||
.friends_socket
|
||||
.connect(
|
||||
&friends_context,
|
||||
&state.pool,
|
||||
&state.api_semaphore,
|
||||
&state.process_manager,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::InvocationContext;
|
||||
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: &InvocationContext,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
||||
semaphore: &FetchSemaphore,
|
||||
) -> crate::Result<Option<Self>> {
|
||||
@@ -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: &InvocationContext,
|
||||
) -> 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::<Vec<_>>();
|
||||
|
||||
CachedEntry::get_user_many(
|
||||
context,
|
||||
&user_ids.iter().map(|x| &**x).collect::<Vec<_>>(),
|
||||
Some(CacheBehaviour::Bypass),
|
||||
&state.pool,
|
||||
@@ -196,6 +202,7 @@ pub const fn get_login_url() -> &'static str {
|
||||
}
|
||||
|
||||
pub async fn finish_login_flow(
|
||||
context: &InvocationContext,
|
||||
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: &InvocationContext,
|
||||
token: &str,
|
||||
semaphore: &FetchSemaphore,
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<crate::state::cache::User> {
|
||||
let result = fetch_advanced(
|
||||
context,
|
||||
Method::GET,
|
||||
concat!(env!("MODRINTH_API_URL"), "user"),
|
||||
None,
|
||||
|
||||
@@ -102,6 +102,7 @@ impl ProcessManager {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn insert_new_process(
|
||||
&self,
|
||||
invocation_context: &crate::InvocationContext,
|
||||
instance_id: &str,
|
||||
instance_path: &str,
|
||||
instance_name: &str,
|
||||
@@ -214,6 +215,7 @@ impl ProcessManager {
|
||||
}
|
||||
|
||||
tokio::spawn(Process::sequential_process_manager(
|
||||
invocation_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(
|
||||
invocation_context: crate::InvocationContext,
|
||||
instance_id: String,
|
||||
instance_path: String,
|
||||
post_exit_command: Option<String>,
|
||||
@@ -833,6 +836,7 @@ impl Process {
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) =
|
||||
crate::api::instance::try_update_playtime_by_instance_id(
|
||||
&invocation_context,
|
||||
&playtime_instance_id,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
use super::io::{self, IOError};
|
||||
use crate::event::LoadingBarId;
|
||||
use crate::event::emit::emit_loading;
|
||||
use crate::operation::{InvocationContext, REQUEST_CONTEXT_HEADER};
|
||||
use crate::{ErrorKind, LabrinthError};
|
||||
use bytes::Bytes;
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use eyre::{Context, eyre};
|
||||
use governor::clock::{Clock, DefaultClock};
|
||||
use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
|
||||
use parking_lot::Mutex;
|
||||
use rand::Rng;
|
||||
use reqwest::Method;
|
||||
@@ -14,12 +17,14 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::ffi::OsStr;
|
||||
use std::future::Future;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::pin::Pin;
|
||||
use std::sync::LazyLock;
|
||||
use std::time::{self};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::time::{self, Duration, Instant, SystemTime};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::{fs::File, io::AsyncReadExt, io::AsyncWriteExt};
|
||||
use tracing::{debug, info};
|
||||
|
||||
pub const DOWNLOAD_META_HEADER: &str = "modrinth-download-meta";
|
||||
|
||||
@@ -188,8 +193,148 @@ static GLOBAL_FETCH_FENCE: LazyLock<FetchFence> =
|
||||
inner: Mutex::new(HashMap::new()),
|
||||
});
|
||||
|
||||
const API_RETRY_AFTER_FALLBACK: Duration = Duration::from_secs(60);
|
||||
|
||||
// This means the unit recovery time will be:
|
||||
// replenish one unit time in seconds = (60 / (units recovered per minute))
|
||||
// smooth recovery time = replenish one unit time in seconds * API_RATE_LIMIT_RECOVERY_SIZE.
|
||||
//
|
||||
// At 20 it means (60 / 120) * 20 = 10 seconds.
|
||||
const API_RATE_LIMIT_RECOVERY_SIZE: u32 = 20;
|
||||
|
||||
/// Implements request-rate-limit handling as well as a local rate limiter of 120 RPM + 50 burst.
|
||||
struct ApiRateLimit {
|
||||
block_until: Mutex<Option<Instant>>,
|
||||
check_lock: Mutex<()>,
|
||||
local: Arc<DefaultDirectRateLimiter>,
|
||||
recovery_padding: Duration,
|
||||
}
|
||||
|
||||
impl ApiRateLimit {
|
||||
fn new() -> Self {
|
||||
let quota = Quota::per_minute(NonZeroU32::new(120).unwrap())
|
||||
.allow_burst(NonZeroU32::new(50).unwrap());
|
||||
let recovery_size =
|
||||
API_RATE_LIMIT_RECOVERY_SIZE.min(quota.burst_size().get());
|
||||
|
||||
Self {
|
||||
block_until: Mutex::new(None),
|
||||
check_lock: Mutex::new(()),
|
||||
local: Arc::new(RateLimiter::direct(quota)),
|
||||
recovery_padding: quota
|
||||
.replenish_interval()
|
||||
.saturating_mul(recovery_size.saturating_sub(1)),
|
||||
}
|
||||
}
|
||||
|
||||
fn check(&self) -> crate::Result<()> {
|
||||
let _check_guard = self.check_lock.lock();
|
||||
self.ensure_not_blocked()?;
|
||||
|
||||
if let Err(not_until) = self.local.check() {
|
||||
// Adds hysteresis to the rate limiting system, ensuring recovery happens
|
||||
// for longer but for more units, avoiding the "flapping" effect when running
|
||||
// out of units.
|
||||
|
||||
let retry_after = not_until
|
||||
.wait_time_from(DefaultClock::default().now())
|
||||
.saturating_add(self.recovery_padding);
|
||||
info!(
|
||||
?retry_after,
|
||||
"Hit builtin rate limiter; waiting for recovery"
|
||||
);
|
||||
self.block_for(retry_after);
|
||||
|
||||
let retry_in_seconds = self
|
||||
.retry_in_seconds()
|
||||
.unwrap_or_else(|| duration_seconds_ceil(retry_after));
|
||||
|
||||
return Err(ErrorKind::Ratelimited { retry_in_seconds }.into());
|
||||
}
|
||||
|
||||
self.ensure_not_blocked()
|
||||
}
|
||||
|
||||
fn handle_response(
|
||||
&self,
|
||||
response: &reqwest::Response,
|
||||
) -> Option<ErrorKind> {
|
||||
if response.status() != reqwest::StatusCode::TOO_MANY_REQUESTS {
|
||||
return None;
|
||||
}
|
||||
|
||||
debug!("Received 429 response; blocking");
|
||||
|
||||
let retry_after = response
|
||||
.headers()
|
||||
.get(reqwest::header::RETRY_AFTER)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| parse_retry_after(value, SystemTime::now()))
|
||||
.unwrap_or(API_RETRY_AFTER_FALLBACK);
|
||||
|
||||
self.block_for(retry_after);
|
||||
|
||||
Some(ErrorKind::Ratelimited {
|
||||
retry_in_seconds: self.retry_in_seconds().unwrap_or(0),
|
||||
})
|
||||
}
|
||||
|
||||
fn ensure_not_blocked(&self) -> crate::Result<()> {
|
||||
if let Some(retry_in_seconds) = self.retry_in_seconds() {
|
||||
debug!("Hit builtin rate limiter; blocking");
|
||||
return Err(ErrorKind::Ratelimited { retry_in_seconds }.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn block_for(&self, duration: Duration) {
|
||||
let Some(block_until) = Instant::now().checked_add(duration) else {
|
||||
return;
|
||||
};
|
||||
let mut current_block = self.block_until.lock();
|
||||
|
||||
if current_block.is_none_or(|current| current < block_until) {
|
||||
*current_block = Some(block_until);
|
||||
}
|
||||
}
|
||||
|
||||
fn retry_in_seconds(&self) -> Option<u64> {
|
||||
let mut block_until = self.block_until.lock();
|
||||
let remaining = (*block_until)?.checked_duration_since(Instant::now());
|
||||
|
||||
if let Some(remaining) = remaining
|
||||
&& !remaining.is_zero()
|
||||
{
|
||||
return Some(duration_seconds_ceil(remaining));
|
||||
}
|
||||
|
||||
*block_until = None;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
static GLOBAL_API_RATE_LIMIT: LazyLock<ApiRateLimit> =
|
||||
LazyLock::new(ApiRateLimit::new);
|
||||
|
||||
fn parse_retry_after(value: &str, now: SystemTime) -> Option<Duration> {
|
||||
if let Ok(seconds) = value.parse::<u64>() {
|
||||
return Some(Duration::from_secs(seconds));
|
||||
}
|
||||
|
||||
let retry_at = httpdate::parse_http_date(value).ok()?;
|
||||
Some(retry_at.duration_since(now).unwrap_or(Duration::ZERO))
|
||||
}
|
||||
|
||||
fn duration_seconds_ceil(duration: Duration) -> u64 {
|
||||
duration
|
||||
.as_secs()
|
||||
.saturating_add(u64::from(duration.subsec_nanos() > 0))
|
||||
}
|
||||
|
||||
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)))
|
||||
@@ -210,6 +355,41 @@ pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = 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_invocation_headers(
|
||||
request: reqwest::RequestBuilder,
|
||||
url: &str,
|
||||
context: &InvocationContext,
|
||||
) -> reqwest::RequestBuilder {
|
||||
if !is_approved_modrinth_service_url(url) {
|
||||
return request;
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) && context.cause() == "unattributed" {
|
||||
tracing::warn!(
|
||||
"Unattributed invocation 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(
|
||||
@@ -221,6 +401,7 @@ pub type FetchProgressFn<'a> = dyn FnMut(
|
||||
|
||||
#[tracing::instrument(skip(semaphore))]
|
||||
pub async fn fetch(
|
||||
context: &InvocationContext,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
download_meta: Option<&DownloadMeta>,
|
||||
@@ -229,6 +410,7 @@ pub async fn fetch(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced(
|
||||
context,
|
||||
Method::GET,
|
||||
url,
|
||||
sha1,
|
||||
@@ -245,6 +427,7 @@ pub async fn fetch(
|
||||
|
||||
#[tracing::instrument(skip(semaphore))]
|
||||
pub async fn fetch_with_client(
|
||||
context: &InvocationContext,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
download_meta: Option<&DownloadMeta>,
|
||||
@@ -254,6 +437,7 @@ pub async fn fetch_with_client(
|
||||
client: &reqwest::Client,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced_with_client(
|
||||
context,
|
||||
Method::GET,
|
||||
url,
|
||||
sha1,
|
||||
@@ -271,6 +455,7 @@ pub async fn fetch_with_client(
|
||||
|
||||
#[tracing::instrument(skip(semaphore, progress))]
|
||||
pub async fn fetch_with_client_progress(
|
||||
context: &InvocationContext,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
download_meta: Option<&DownloadMeta>,
|
||||
@@ -281,6 +466,7 @@ pub async fn fetch_with_client_progress(
|
||||
progress: Option<&mut FetchProgressFn<'_>>,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced_with_client_and_progress(
|
||||
context,
|
||||
Method::GET,
|
||||
url,
|
||||
sha1,
|
||||
@@ -299,6 +485,7 @@ pub async fn fetch_with_client_progress(
|
||||
|
||||
#[tracing::instrument(skip(json_body, semaphore))]
|
||||
pub async fn fetch_json<T>(
|
||||
context: &InvocationContext,
|
||||
method: Method,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
@@ -311,8 +498,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)?;
|
||||
@@ -324,6 +511,7 @@ where
|
||||
#[tracing::instrument(skip(json_body, semaphore))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn fetch_advanced(
|
||||
context: &InvocationContext,
|
||||
method: Method,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
@@ -336,6 +524,7 @@ pub async fn fetch_advanced(
|
||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced_with_client(
|
||||
context,
|
||||
method,
|
||||
url,
|
||||
sha1,
|
||||
@@ -354,6 +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: &InvocationContext,
|
||||
method: Method,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
@@ -367,6 +557,7 @@ pub async fn fetch_advanced_with_progress(
|
||||
progress: Option<&mut FetchProgressFn<'_>>,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced_with_client_and_progress(
|
||||
context,
|
||||
method,
|
||||
url,
|
||||
sha1,
|
||||
@@ -387,6 +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: &InvocationContext,
|
||||
method: Method,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
@@ -400,6 +592,7 @@ pub async fn fetch_advanced_with_client(
|
||||
client: &reqwest::Client,
|
||||
) -> crate::Result<Bytes> {
|
||||
fetch_advanced_with_client_and_progress(
|
||||
context,
|
||||
method,
|
||||
url,
|
||||
sha1,
|
||||
@@ -419,6 +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: &InvocationContext,
|
||||
method: Method,
|
||||
url: &str,
|
||||
sha1: Option<&str>,
|
||||
@@ -452,6 +646,10 @@ async fn fetch_advanced_with_client_and_progress(
|
||||
.map(|m| (DOWNLOAD_META_HEADER.to_string(), m.to_header_value()));
|
||||
|
||||
for attempt in 1..=(FETCH_ATTEMPTS + 1) {
|
||||
if is_api_url {
|
||||
GLOBAL_API_RATE_LIMIT.check()?;
|
||||
}
|
||||
|
||||
if let Some(fence_key) = fence_key
|
||||
&& GLOBAL_FETCH_FENCE.is_blocked(fence_key)
|
||||
{
|
||||
@@ -461,7 +659,11 @@ async fn fetch_advanced_with_client_and_progress(
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut req = client.request(method.clone(), url);
|
||||
let mut req = attach_invocation_headers(
|
||||
client.request(method.clone(), url),
|
||||
url,
|
||||
context,
|
||||
);
|
||||
|
||||
if let Some(body) = json_body.clone() {
|
||||
req = req.json(&body);
|
||||
@@ -483,6 +685,13 @@ async fn fetch_advanced_with_client_and_progress(
|
||||
let result = req.send().await;
|
||||
match result {
|
||||
Ok(resp) => {
|
||||
if is_api_url
|
||||
&& let Some(error) =
|
||||
GLOBAL_API_RATE_LIMIT.handle_response(&resp)
|
||||
{
|
||||
return Err(error.into());
|
||||
}
|
||||
|
||||
if resp.status().is_server_error() {
|
||||
if let Some(fence_key) = fence_key {
|
||||
GLOBAL_FETCH_FENCE.record_fail(fence_key);
|
||||
@@ -601,6 +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: &InvocationContext,
|
||||
mirrors: &[&str],
|
||||
sha1: Option<&str>,
|
||||
download_meta: Option<&DownloadMeta>,
|
||||
@@ -616,6 +826,7 @@ pub async fn fetch_mirrors(
|
||||
|
||||
for (index, mirror) in mirrors.iter().enumerate() {
|
||||
let result = fetch_with_client(
|
||||
context,
|
||||
mirror,
|
||||
sha1,
|
||||
download_meta,
|
||||
@@ -636,6 +847,7 @@ pub async fn fetch_mirrors(
|
||||
|
||||
#[tracing::instrument(skip(semaphore, progress))]
|
||||
pub async fn fetch_mirrors_with_progress(
|
||||
context: &InvocationContext,
|
||||
mirrors: &[&str],
|
||||
sha1: Option<&str>,
|
||||
download_meta: Option<&DownloadMeta>,
|
||||
@@ -652,6 +864,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,
|
||||
@@ -674,6 +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: &InvocationContext,
|
||||
url: &str,
|
||||
json_body: serde_json::Value,
|
||||
semaphore: &FetchSemaphore,
|
||||
@@ -681,7 +895,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_invocation_headers(
|
||||
INSECURE_REQWEST_CLIENT.post(url),
|
||||
url,
|
||||
context,
|
||||
)
|
||||
.json(&json_body);
|
||||
|
||||
if let Some(creds) =
|
||||
crate::state::ModrinthCredentials::get_active(exec).await?
|
||||
@@ -784,6 +1003,116 @@ pub async fn sha1_async(bytes: Bytes) -> crate::Result<String> {
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod invocation_context_tests {
|
||||
use super::*;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
fn built_request(
|
||||
url: &str,
|
||||
context: &InvocationContext,
|
||||
) -> reqwest::Request {
|
||||
attach_invocation_headers(
|
||||
INSECURE_REQWEST_CLIENT.get(url),
|
||||
url,
|
||||
context,
|
||||
)
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
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,
|
||||
);
|
||||
|
||||
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_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));
|
||||
assert!(!request.headers().contains_key(REQUEST_CONTEXT_HEADER));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn similarly_prefixed_hosts_are_not_approved() {
|
||||
let context = InvocationContext::new("navigation/browse");
|
||||
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 = InvocationContext::new("new/frontend/cause");
|
||||
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<Path>,
|
||||
) -> crate::Result<(u64, String)> {
|
||||
|
||||
Reference in New Issue
Block a user