From 01fe08f079cfa429b1cd43cfda1fc3f601829495 Mon Sep 17 00:00:00 2001 From: Josiah Glosson Date: Sat, 28 Dec 2024 21:23:27 -0600 Subject: [PATCH] Make the update checker work for non-mods (#3088) * Fix https://github.com/modrinth/code/issues/1057 * Make sure mods use the installed loader * Switch &PathBuf to &Path * Clippy fix * Deduplicate some code --------- Co-authored-by: Jai Agrawal <18202329+Geometrically@users.noreply.github.com> --- .../app-lib/src/api/pack/install_mrpack.rs | 8 ++- packages/app-lib/src/state/cache.rs | 40 ++++++++--- .../app-lib/src/state/legacy_converter.rs | 7 +- packages/app-lib/src/state/profiles.rs | 66 ++++++++++++------- 4 files changed, 80 insertions(+), 41 deletions(-) diff --git a/packages/app-lib/src/api/pack/install_mrpack.rs b/packages/app-lib/src/api/pack/install_mrpack.rs index 4f5939b2a..f6c6ead77 100644 --- a/packages/app-lib/src/api/pack/install_mrpack.rs +++ b/packages/app-lib/src/api/pack/install_mrpack.rs @@ -13,13 +13,13 @@ use crate::util::io; use crate::{profile, State}; use async_zip::base::read::seek::ZipFileReader; -use std::io::Cursor; -use std::path::{Component, PathBuf}; - use super::install_from::{ generate_pack_from_file, generate_pack_from_version_id, CreatePack, CreatePackLocation, PackFormat, }; +use crate::data::ProjectType; +use std::io::Cursor; +use std::path::{Component, PathBuf}; /// Install a pack /// Wrapper around install_pack_files that generates a pack creation description, and @@ -189,6 +189,7 @@ pub async fn install_zipped_mrpack_files( .hashes .get(&PackFileHash::Sha1) .map(|x| &**x), + ProjectType::get_from_parent_folder(&path), &state.pool, ) .await?; @@ -247,6 +248,7 @@ pub async fn install_zipped_mrpack_files( &profile_path, &new_path.to_string_lossy(), None, + ProjectType::get_from_parent_folder(&new_path), &state.pool, ) .await?; diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index b33ab09bb..000ff4eea 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -1,4 +1,5 @@ use crate::config::{META_URL, MODRINTH_API_URL, MODRINTH_API_URL_V3}; +use crate::state::ProjectType; use crate::util::fetch::{fetch_json, sha1_async, FetchSemaphore}; use chrono::{DateTime, Utc}; use dashmap::DashSet; @@ -194,7 +195,7 @@ pub struct SearchEntry { pub struct CachedFileUpdate { pub hash: String, pub game_version: String, - pub loader: String, + pub loaders: Vec, pub update_version_id: String, } @@ -203,6 +204,7 @@ pub struct CachedFileHash { pub path: String, pub size: u64, pub hash: String, + pub project_type: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -481,7 +483,12 @@ impl CacheValue { ) } CacheValue::FileUpdate(hash) => { - format!("{}-{}-{}", hash.hash, hash.loader, hash.game_version) + format!( + "{}-{}-{}", + hash.hash, + hash.loaders.join("+"), + hash.game_version + ) } CacheValue::SearchResults(search) => search.search.clone(), } @@ -1240,6 +1247,9 @@ impl CachedEntry { path: path.to_string(), size, hash, + project_type: ProjectType::get_from_parent_folder( + &full_path, + ), }) .get_entry(), true, @@ -1270,18 +1280,21 @@ impl CachedEntry { if key.len() == 3 { let hash = key[0]; - let loader = key[1]; + let loaders_key = key[1]; let game_version = key[2]; if let Some(values) = filtered_keys.iter_mut().find(|x| { - x.0 .0 == loader && x.0 .1 == game_version + x.0 .0 == loaders_key && x.0 .1 == game_version }) { values.1.push(hash.to_string()); } else { filtered_keys.push(( - (loader.to_string(), game_version.to_string()), + ( + loaders_key.to_string(), + game_version.to_string(), + ), vec![hash.to_string()], )) } @@ -1297,7 +1310,7 @@ impl CachedEntry { format!("{}version_files/update", MODRINTH_API_URL); let variations = futures::future::try_join_all(filtered_keys.iter().map( - |((loader, game_version), hashes)| { + |((loaders_key, game_version), hashes)| { fetch_json::>( Method::POST, &version_update_url, @@ -1305,7 +1318,7 @@ impl CachedEntry { Some(serde_json::json!({ "algorithm": "sha1", "hashes": hashes, - "loaders": [loader], + "loaders": loaders_key.split('+').collect::>(), "game_versions": [game_version] })), fetch_semaphore, @@ -1317,7 +1330,7 @@ impl CachedEntry { for (index, mut variation) in variations.into_iter().enumerate() { - let ((loader, game_version), hashes) = + let ((loaders_key, game_version), hashes) = &filtered_keys[index]; for hash in hashes { @@ -1334,7 +1347,10 @@ impl CachedEntry { CacheValue::FileUpdate(CachedFileUpdate { hash: hash.clone(), game_version: game_version.clone(), - loader: loader.clone(), + loaders: loaders_key + .split('+') + .map(|x| x.to_string()) + .collect(), update_version_id: version_id, }) .get_entry(), @@ -1343,7 +1359,9 @@ impl CachedEntry { } else { vals.push(( CacheValueType::FileUpdate.get_empty_entry( - format!("{hash}-{loader}-{game_version}"), + format!( + "{hash}-{loaders_key}-{game_version}" + ), ), true, )) @@ -1450,6 +1468,7 @@ pub async fn cache_file_hash( profile_path: &str, path: &str, known_hash: Option<&str>, + project_type: Option, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, ) -> crate::Result<()> { let size = bytes.len(); @@ -1465,6 +1484,7 @@ pub async fn cache_file_hash( path: format!("{}/{}", profile_path, path), size: size as u64, hash, + project_type, }) .get_entry()], exec, diff --git a/packages/app-lib/src/state/legacy_converter.rs b/packages/app-lib/src/state/legacy_converter.rs index 866a57cbc..2f6c8bc25 100644 --- a/packages/app-lib/src/state/legacy_converter.rs +++ b/packages/app-lib/src/state/legacy_converter.rs @@ -1,4 +1,4 @@ -use crate::data::{Dependency, User, Version}; +use crate::data::{Dependency, ProjectType, User, Version}; use crate::jre::check_jre; use crate::prelude::ModLoader; use crate::state; @@ -226,6 +226,7 @@ where path: file_name, size: metadata.len(), hash: sha1.clone(), + project_type: ProjectType::get_from_parent_folder(&full_path), }, )); } @@ -249,9 +250,9 @@ where .metadata .game_version .clone(), - loader: mod_loader + loaders: vec![mod_loader .as_str() - .to_string(), + .to_string()], update_version_id: update_version.id.clone(), }, diff --git a/packages/app-lib/src/state/profiles.rs b/packages/app-lib/src/state/profiles.rs index 572df175a..23b5729a4 100644 --- a/packages/app-lib/src/state/profiles.rs +++ b/packages/app-lib/src/state/profiles.rs @@ -1,5 +1,7 @@ use super::settings::{Hooks, MemorySettings, WindowSize}; -use crate::state::{cache_file_hash, CacheBehaviour, CachedEntry}; +use crate::state::{ + cache_file_hash, CacheBehaviour, CachedEntry, CachedFileHash, +}; use crate::util; use crate::util::fetch::{write_cached_icon, FetchSemaphore, IoSemaphore}; use crate::util::io::{self}; @@ -9,7 +11,7 @@ use serde::{Deserialize, Serialize}; use sqlx::SqlitePool; use std::convert::TryFrom; use std::convert::TryInto; -use std::path::{Path, PathBuf}; +use std::path::Path; // Represent a Minecraft instance. #[derive(Serialize, Deserialize, Clone, Debug)] @@ -146,7 +148,7 @@ pub struct FileMetadata { pub version_id: String, } -#[derive(Serialize, Deserialize, Clone, Debug, Copy)] +#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ProjectType { Mod, @@ -176,7 +178,7 @@ impl ProjectType { } } - pub fn get_from_parent_folder(path: PathBuf) -> Option { + pub fn get_from_parent_folder(path: &Path) -> Option { // Get parent folder let path = path.parent()?.file_name()?; match path.to_str()? { @@ -206,6 +208,15 @@ impl ProjectType { } } + pub fn get_loaders(&self) -> &'static [&'static str] { + match self { + ProjectType::Mod => &["fabric", "forge", "quilt", "neoforge"], + ProjectType::DataPack => &["datapack"], + ProjectType::ResourcePack => &["vanilla", "canvas", "minecraft"], + ProjectType::ShaderPack => &["iris", "optifine"], + } + } + pub fn iterator() -> impl Iterator { [ ProjectType::Mod, @@ -587,17 +598,10 @@ impl Profile { let file_updates = file_hashes .iter() - .filter_map(|x| { - all.iter().find(|prof| x.path.contains(&prof.path)).map( - |profile| { - format!( - "{}-{}-{}", - x.hash, - profile.loader.as_str(), - profile.game_version - ) - }, - ) + .filter_map(|file| { + all.iter() + .find(|prof| file.path.contains(&prof.path)) + .map(|profile| Self::get_cache_key(file, profile)) }) .collect::>(); @@ -690,14 +694,7 @@ impl Profile { let file_updates = file_hashes .iter() - .map(|x| { - format!( - "{}-{}-{}", - x.hash, - self.loader.as_str(), - self.game_version - ) - }) + .map(|x| Self::get_cache_key(x, self)) .collect::>(); let file_hashes_ref = @@ -773,6 +770,18 @@ impl Profile { Ok(files) } + fn get_cache_key(file: &CachedFileHash, profile: &Profile) -> String { + format!( + "{}-{}-{}", + file.hash, + file.project_type + .filter(|x| *x != ProjectType::Mod) + .map(|x| x.get_loaders().join("+")) + .unwrap_or_else(|| profile.loader.as_str().to_string()), + profile.game_version + ) + } + #[tracing::instrument(skip(pool))] pub async fn add_project_version( profile_path: &str, @@ -873,8 +882,15 @@ impl Profile { let project_path = format!("{}/{}", project_type.get_folder(), file_name); - cache_file_hash(bytes.clone(), profile_path, &project_path, hash, exec) - .await?; + cache_file_hash( + bytes.clone(), + profile_path, + &project_path, + hash, + Some(project_type), + exec, + ) + .await?; util::fetch::write(&path.join(&project_path), &bytes, io_semaphore) .await?;