feat: content management changes (#6104)

* feat: change modpack updating flow

* fix: pending install state loss

* fix: mods.vue perf problems

* chore: todo doc

* draft: try preload/fix suspense

* fix: lint
This commit is contained in:
Calum H.
2026-05-20 17:07:35 +00:00
committed by GitHub
parent 079a10bba9
commit c3fe7b4232
19 changed files with 1111 additions and 277 deletions
+35 -58
View File
@@ -3,13 +3,12 @@
//! ## Data Flow
//!
//! 1. Frontend calls `get_content_items(profile_path)`
//! 2. Backend fetches all installed files via `Profile::get_projects()`
//! 3. If profile is linked to a modpack:
//! 2. If profile is linked to a modpack:
//! - Fetch modpack file hashes from cache (populated during installation)
//! - Fallback: re-download .mrpack if cache miss (cleared/expired)
//! - Filter out files that belong to the modpack
//! 4. For remaining files, fetch project/version/owner metadata in parallel
//! 5. Return sorted `ContentItem` list
//! - Filter out files that belong to the modpack before update lookup
//! 3. For remaining files, fetch project/version/owner metadata in parallel
//! 4. Return sorted `ContentItem` list
//!
//! ## Caching
//!
@@ -226,12 +225,8 @@ pub async fn get_linked_modpack_info(
};
// Check for updates
let (has_update, update_version_id, update_version) = check_modpack_update(
profile,
&linked_data.version_id,
&version,
all_versions,
);
let (has_update, update_version_id, update_version) =
check_modpack_update(&linked_data.version_id, &version, all_versions);
Ok(Some(LinkedModpackInfo {
project,
@@ -243,10 +238,9 @@ pub async fn get_linked_modpack_info(
}))
}
/// Check if a newer compatible version exists for the linked modpack.
/// Check if a newer version exists for the linked modpack.
/// Returns (has_update, update_version_id, update_version).
fn check_modpack_update(
profile: &Profile,
installed_version_id: &str,
installed_version: &Version,
all_versions: Option<Vec<Version>>,
@@ -255,44 +249,19 @@ fn check_modpack_update(
return (false, None, None);
};
// Get the loader as a string for comparison
let loader_str = profile.loader.as_str().to_lowercase();
let game_version = &profile.game_version;
// Filter to compatible versions
let mut compatible_versions: Vec<&Version> = versions
let mut newer_versions: Vec<&Version> = versions
.iter()
.filter(|v| {
// Must support the profile's game version
let supports_game = v.game_versions.contains(game_version);
// Must support the profile's loader
// The v2 API replaces "mrpack" with actual loaders from mrpack_loaders,
// but if mrpack_loaders is missing, loaders may be just ["mrpack"].
// In that case we can't filter by loader, so accept the version.
let real_loaders: Vec<_> = v
.loaders
.iter()
.filter(|l| l.to_lowercase() != "mrpack")
.collect();
let supports_loader = real_loaders.is_empty()
|| real_loaders.iter().any(|l| l.to_lowercase() == loader_str);
supports_game && supports_loader
v.id != installed_version_id
&& v.date_published > installed_version.date_published
})
.collect();
// Sort by date_published descending (newest first)
compatible_versions.sort_by_key(|b| std::cmp::Reverse(b.date_published));
newer_versions.sort_by_key(|b| std::cmp::Reverse(b.date_published));
// Find the newest compatible version
if let Some(newest) = compatible_versions.first() {
// Check if the newest version is different and newer than installed
if newest.id != installed_version_id
&& newest.date_published > installed_version.date_published
{
return (true, Some(newest.id.clone()), Some((*newest).clone()));
}
if let Some(newest) = newer_versions.first() {
return (true, Some(newest.id.clone()), Some((*newest).clone()));
}
(false, None, None)
@@ -306,10 +275,6 @@ pub async fn get_content_items(
pool: &SqlitePool,
fetch_semaphore: &FetchSemaphore,
) -> crate::Result<Vec<ContentItem>> {
let all_files = profile
.get_projects(cache_behaviour, pool, fetch_semaphore)
.await?;
let modpack_ids = if let Some(ref linked_data) = profile.linked_data {
if linked_data.version_id.is_empty() {
None
@@ -350,23 +315,35 @@ pub async fn get_content_items(
None
};
let user_files: Vec<(String, ProfileFile)> = all_files
.into_iter()
.filter(|(_, file)| {
modpack_ids
.as_ref()
.is_none_or(|ids| !ids.is_modpack_file(file))
})
.collect();
let user_files: Vec<(String, ProfileFile)> = if let Some(ids) = &modpack_ids
{
let filtered_files = profile
.get_projects_excluding_modpack_files(
&ids.hashes,
&ids.project_ids,
cache_behaviour,
pool,
fetch_semaphore,
)
.await?;
filtered_files.into_iter().collect()
} else {
let all_files = profile
.get_projects(cache_behaviour, pool, fetch_semaphore)
.await?;
all_files.into_iter().collect()
};
profile_files_to_content_items(
let content_items = profile_files_to_content_items(
&profile.path,
&user_files,
cache_behaviour,
pool,
fetch_semaphore,
)
.await
.await?;
Ok(content_items)
}
/// Pre-fetched metadata for projects, versions, teams, and organizations.