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>
This commit is contained in:
Josiah Glosson 2024-12-28 21:23:27 -06:00 committed by GitHub
parent 8b7547ae38
commit 01fe08f079
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 80 additions and 41 deletions

View File

@ -13,13 +13,13 @@ use crate::util::io;
use crate::{profile, State}; use crate::{profile, State};
use async_zip::base::read::seek::ZipFileReader; use async_zip::base::read::seek::ZipFileReader;
use std::io::Cursor;
use std::path::{Component, PathBuf};
use super::install_from::{ use super::install_from::{
generate_pack_from_file, generate_pack_from_version_id, CreatePack, generate_pack_from_file, generate_pack_from_version_id, CreatePack,
CreatePackLocation, PackFormat, CreatePackLocation, PackFormat,
}; };
use crate::data::ProjectType;
use std::io::Cursor;
use std::path::{Component, PathBuf};
/// Install a pack /// Install a pack
/// Wrapper around install_pack_files that generates a pack creation description, and /// Wrapper around install_pack_files that generates a pack creation description, and
@ -189,6 +189,7 @@ pub async fn install_zipped_mrpack_files(
.hashes .hashes
.get(&PackFileHash::Sha1) .get(&PackFileHash::Sha1)
.map(|x| &**x), .map(|x| &**x),
ProjectType::get_from_parent_folder(&path),
&state.pool, &state.pool,
) )
.await?; .await?;
@ -247,6 +248,7 @@ pub async fn install_zipped_mrpack_files(
&profile_path, &profile_path,
&new_path.to_string_lossy(), &new_path.to_string_lossy(),
None, None,
ProjectType::get_from_parent_folder(&new_path),
&state.pool, &state.pool,
) )
.await?; .await?;

View File

@ -1,4 +1,5 @@
use crate::config::{META_URL, MODRINTH_API_URL, MODRINTH_API_URL_V3}; 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 crate::util::fetch::{fetch_json, sha1_async, FetchSemaphore};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use dashmap::DashSet; use dashmap::DashSet;
@ -194,7 +195,7 @@ pub struct SearchEntry {
pub struct CachedFileUpdate { pub struct CachedFileUpdate {
pub hash: String, pub hash: String,
pub game_version: String, pub game_version: String,
pub loader: String, pub loaders: Vec<String>,
pub update_version_id: String, pub update_version_id: String,
} }
@ -203,6 +204,7 @@ pub struct CachedFileHash {
pub path: String, pub path: String,
pub size: u64, pub size: u64,
pub hash: String, pub hash: String,
pub project_type: Option<ProjectType>,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -481,7 +483,12 @@ impl CacheValue {
) )
} }
CacheValue::FileUpdate(hash) => { 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(), CacheValue::SearchResults(search) => search.search.clone(),
} }
@ -1240,6 +1247,9 @@ impl CachedEntry {
path: path.to_string(), path: path.to_string(),
size, size,
hash, hash,
project_type: ProjectType::get_from_parent_folder(
&full_path,
),
}) })
.get_entry(), .get_entry(),
true, true,
@ -1270,18 +1280,21 @@ impl CachedEntry {
if key.len() == 3 { if key.len() == 3 {
let hash = key[0]; let hash = key[0];
let loader = key[1]; let loaders_key = key[1];
let game_version = key[2]; let game_version = key[2];
if let Some(values) = if let Some(values) =
filtered_keys.iter_mut().find(|x| { 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()); values.1.push(hash.to_string());
} else { } else {
filtered_keys.push(( filtered_keys.push((
(loader.to_string(), game_version.to_string()), (
loaders_key.to_string(),
game_version.to_string(),
),
vec![hash.to_string()], vec![hash.to_string()],
)) ))
} }
@ -1297,7 +1310,7 @@ impl CachedEntry {
format!("{}version_files/update", MODRINTH_API_URL); format!("{}version_files/update", MODRINTH_API_URL);
let variations = let variations =
futures::future::try_join_all(filtered_keys.iter().map( futures::future::try_join_all(filtered_keys.iter().map(
|((loader, game_version), hashes)| { |((loaders_key, game_version), hashes)| {
fetch_json::<HashMap<String, Version>>( fetch_json::<HashMap<String, Version>>(
Method::POST, Method::POST,
&version_update_url, &version_update_url,
@ -1305,7 +1318,7 @@ impl CachedEntry {
Some(serde_json::json!({ Some(serde_json::json!({
"algorithm": "sha1", "algorithm": "sha1",
"hashes": hashes, "hashes": hashes,
"loaders": [loader], "loaders": loaders_key.split('+').collect::<Vec<_>>(),
"game_versions": [game_version] "game_versions": [game_version]
})), })),
fetch_semaphore, fetch_semaphore,
@ -1317,7 +1330,7 @@ impl CachedEntry {
for (index, mut variation) in variations.into_iter().enumerate() for (index, mut variation) in variations.into_iter().enumerate()
{ {
let ((loader, game_version), hashes) = let ((loaders_key, game_version), hashes) =
&filtered_keys[index]; &filtered_keys[index];
for hash in hashes { for hash in hashes {
@ -1334,7 +1347,10 @@ impl CachedEntry {
CacheValue::FileUpdate(CachedFileUpdate { CacheValue::FileUpdate(CachedFileUpdate {
hash: hash.clone(), hash: hash.clone(),
game_version: game_version.clone(), game_version: game_version.clone(),
loader: loader.clone(), loaders: loaders_key
.split('+')
.map(|x| x.to_string())
.collect(),
update_version_id: version_id, update_version_id: version_id,
}) })
.get_entry(), .get_entry(),
@ -1343,7 +1359,9 @@ impl CachedEntry {
} else { } else {
vals.push(( vals.push((
CacheValueType::FileUpdate.get_empty_entry( CacheValueType::FileUpdate.get_empty_entry(
format!("{hash}-{loader}-{game_version}"), format!(
"{hash}-{loaders_key}-{game_version}"
),
), ),
true, true,
)) ))
@ -1450,6 +1468,7 @@ pub async fn cache_file_hash(
profile_path: &str, profile_path: &str,
path: &str, path: &str,
known_hash: Option<&str>, known_hash: Option<&str>,
project_type: Option<ProjectType>,
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>, exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
) -> crate::Result<()> { ) -> crate::Result<()> {
let size = bytes.len(); let size = bytes.len();
@ -1465,6 +1484,7 @@ pub async fn cache_file_hash(
path: format!("{}/{}", profile_path, path), path: format!("{}/{}", profile_path, path),
size: size as u64, size: size as u64,
hash, hash,
project_type,
}) })
.get_entry()], .get_entry()],
exec, exec,

View File

@ -1,4 +1,4 @@
use crate::data::{Dependency, User, Version}; use crate::data::{Dependency, ProjectType, User, Version};
use crate::jre::check_jre; use crate::jre::check_jre;
use crate::prelude::ModLoader; use crate::prelude::ModLoader;
use crate::state; use crate::state;
@ -226,6 +226,7 @@ where
path: file_name, path: file_name,
size: metadata.len(), size: metadata.len(),
hash: sha1.clone(), hash: sha1.clone(),
project_type: ProjectType::get_from_parent_folder(&full_path),
}, },
)); ));
} }
@ -249,9 +250,9 @@ where
.metadata .metadata
.game_version .game_version
.clone(), .clone(),
loader: mod_loader loaders: vec![mod_loader
.as_str() .as_str()
.to_string(), .to_string()],
update_version_id: update_version_id:
update_version.id.clone(), update_version.id.clone(),
}, },

View File

@ -1,5 +1,7 @@
use super::settings::{Hooks, MemorySettings, WindowSize}; 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;
use crate::util::fetch::{write_cached_icon, FetchSemaphore, IoSemaphore}; use crate::util::fetch::{write_cached_icon, FetchSemaphore, IoSemaphore};
use crate::util::io::{self}; use crate::util::io::{self};
@ -9,7 +11,7 @@ use serde::{Deserialize, Serialize};
use sqlx::SqlitePool; use sqlx::SqlitePool;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::convert::TryInto; use std::convert::TryInto;
use std::path::{Path, PathBuf}; use std::path::Path;
// Represent a Minecraft instance. // Represent a Minecraft instance.
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -146,7 +148,7 @@ pub struct FileMetadata {
pub version_id: String, pub version_id: String,
} }
#[derive(Serialize, Deserialize, Clone, Debug, Copy)] #[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum ProjectType { pub enum ProjectType {
Mod, Mod,
@ -176,7 +178,7 @@ impl ProjectType {
} }
} }
pub fn get_from_parent_folder(path: PathBuf) -> Option<Self> { pub fn get_from_parent_folder(path: &Path) -> Option<Self> {
// Get parent folder // Get parent folder
let path = path.parent()?.file_name()?; let path = path.parent()?.file_name()?;
match path.to_str()? { 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<Item = ProjectType> { pub fn iterator() -> impl Iterator<Item = ProjectType> {
[ [
ProjectType::Mod, ProjectType::Mod,
@ -587,17 +598,10 @@ impl Profile {
let file_updates = file_hashes let file_updates = file_hashes
.iter() .iter()
.filter_map(|x| { .filter_map(|file| {
all.iter().find(|prof| x.path.contains(&prof.path)).map( all.iter()
|profile| { .find(|prof| file.path.contains(&prof.path))
format!( .map(|profile| Self::get_cache_key(file, profile))
"{}-{}-{}",
x.hash,
profile.loader.as_str(),
profile.game_version
)
},
)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -690,14 +694,7 @@ impl Profile {
let file_updates = file_hashes let file_updates = file_hashes
.iter() .iter()
.map(|x| { .map(|x| Self::get_cache_key(x, self))
format!(
"{}-{}-{}",
x.hash,
self.loader.as_str(),
self.game_version
)
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let file_hashes_ref = let file_hashes_ref =
@ -773,6 +770,18 @@ impl Profile {
Ok(files) 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))] #[tracing::instrument(skip(pool))]
pub async fn add_project_version( pub async fn add_project_version(
profile_path: &str, profile_path: &str,
@ -873,8 +882,15 @@ impl Profile {
let project_path = let project_path =
format!("{}/{}", project_type.get_folder(), file_name); format!("{}/{}", project_type.get_folder(), file_name);
cache_file_hash(bytes.clone(), profile_path, &project_path, hash, exec) cache_file_hash(
.await?; bytes.clone(),
profile_path,
&project_path,
hash,
Some(project_type),
exec,
)
.await?;
util::fetch::write(&path.join(&project_path), &bytes, io_semaphore) util::fetch::write(&path.join(&project_path), &bytes, io_semaphore)
.await?; .await?;