mirror of
https://github.com/modrinth/code.git
synced 2026-08-28 18:45:15 +00:00
fix: bulk update updating linked content (#6517)
* fix: bulk update updating linked content * fix: disabled content sometimes desyncing * fix: bad filter * fix: fmt
This commit is contained in:
+1
@@ -97,6 +97,7 @@ type ReleaseChannel = 'release' | 'beta' | 'alpha'
|
|||||||
export type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
|
export type InstanceLoader = 'vanilla' | 'forge' | 'fabric' | 'quilt' | 'neoforge'
|
||||||
|
|
||||||
type ContentFile = {
|
type ContentFile = {
|
||||||
|
enabled: boolean
|
||||||
metadata?: {
|
metadata?: {
|
||||||
project_id: string
|
project_id: string
|
||||||
version_id: string
|
version_id: string
|
||||||
|
|||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n\t\tUPDATE instance_files\n\t\tSET\n\t\t\trelative_path = ?,\n\t\t\tfile_name = ?,\n\t\t\tenabled = ?,\n\t\t\tmissing = 0,\n\t\t\tmodified_at = ?\n\t\tWHERE instance_id = ? AND relative_path = ?\n\t\t",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 6
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "c5f78eaae528b6f930bcc9ade15384564c86cd98a1e407515ea718127a3b1777"
|
||||||
|
}
|
||||||
Generated
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n\t\tUPDATE instance_files\n\t\tSET\n\t\t\trelative_path = ?,\n\t\t\tfile_name = ?,\n\t\t\tenabled = ?,\n\t\t\tmodified_at = ?\n\t\tWHERE instance_id = ? AND relative_path = ?\n\t\t",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 6
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "f93f5fca4f004dc70cab05f879993eb92fa6199c7356bbc2c5ca36468f916380"
|
|
||||||
}
|
|
||||||
@@ -117,6 +117,7 @@ impl ModLoader {
|
|||||||
pub struct ContentFile {
|
pub struct ContentFile {
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
|
pub enabled: bool,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub metadata: Option<FileMetadata>,
|
pub metadata: Option<FileMetadata>,
|
||||||
pub update_version_id: Option<String>,
|
pub update_version_id: Option<String>,
|
||||||
|
|||||||
@@ -627,6 +627,7 @@ pub(crate) async fn rename_instance_file(
|
|||||||
relative_path = ?,
|
relative_path = ?,
|
||||||
file_name = ?,
|
file_name = ?,
|
||||||
enabled = ?,
|
enabled = ?,
|
||||||
|
missing = 0,
|
||||||
modified_at = ?
|
modified_at = ?
|
||||||
WHERE instance_id = ? AND relative_path = ?
|
WHERE instance_id = ? AND relative_path = ?
|
||||||
",
|
",
|
||||||
@@ -856,11 +857,11 @@ pub(crate) async fn set_content_entry_enabled_for_file(
|
|||||||
file_id: &str,
|
file_id: &str,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<bool> {
|
||||||
let enabled = i64::from(enabled);
|
let enabled = i64::from(enabled);
|
||||||
let modified_at = Utc::now().timestamp();
|
let modified_at = Utc::now().timestamp();
|
||||||
|
|
||||||
sqlx::query!(
|
let result = sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE instance_content_entries
|
UPDATE instance_content_entries
|
||||||
SET enabled = ?, modified_at = ?
|
SET enabled = ?, modified_at = ?
|
||||||
@@ -874,7 +875,7 @@ pub(crate) async fn set_content_entry_enabled_for_file(
|
|||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(result.rows_affected() > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn remove_content_entries_for_file(
|
pub(crate) async fn remove_content_entries_for_file(
|
||||||
|
|||||||
@@ -661,13 +661,31 @@ pub(crate) async fn toggle_disable_project(
|
|||||||
}
|
}
|
||||||
None => index_existing_file(&scope, &new_path, state).await?,
|
None => index_existing_file(&scope, &new_path, state).await?,
|
||||||
};
|
};
|
||||||
content_rows::set_content_entry_enabled_for_file(
|
let updated_entry = content_rows::set_content_entry_enabled_for_file(
|
||||||
&scope.content_set_id,
|
&scope.content_set_id,
|
||||||
&file.id,
|
&file.id,
|
||||||
enabled,
|
enabled,
|
||||||
&state.pool,
|
&state.pool,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
if !updated_entry {
|
||||||
|
let project_type = ProjectType::get_from_parent_folder(&new_path)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
crate::ErrorKind::InputError(format!(
|
||||||
|
"Unable to infer project type from {new_path}"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
upsert_entry_for_file(
|
||||||
|
&scope,
|
||||||
|
&file,
|
||||||
|
project_type,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
ContentSourceKind::Local,
|
||||||
|
&state.pool,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(new_path)
|
Ok(new_path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,12 +281,31 @@ async fn plan_bulk_update(
|
|||||||
instance_id: &str,
|
instance_id: &str,
|
||||||
state: &State,
|
state: &State,
|
||||||
) -> crate::Result<BulkUpdatePlan> {
|
) -> crate::Result<BulkUpdatePlan> {
|
||||||
|
let updateable_paths =
|
||||||
|
bulk_updateable_project_paths(instance_id, state).await?;
|
||||||
|
if updateable_paths.is_empty() {
|
||||||
|
return Ok(BulkUpdatePlan {
|
||||||
|
project_updates: Vec::new(),
|
||||||
|
dependency_additions: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let updates = check_content_updates(
|
let updates = check_content_updates(
|
||||||
instance_id,
|
instance_id,
|
||||||
Some(CacheBehaviour::MustRevalidate),
|
Some(CacheBehaviour::MustRevalidate),
|
||||||
state,
|
state,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|update| updateable_paths.contains(&update.relative_path))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if updates.is_empty() {
|
||||||
|
return Ok(BulkUpdatePlan {
|
||||||
|
project_updates: Vec::new(),
|
||||||
|
dependency_additions: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let content_set =
|
let content_set =
|
||||||
content_rows::get_applied_content_set(instance_id, &state.pool)
|
content_rows::get_applied_content_set(instance_id, &state.pool)
|
||||||
.await?
|
.await?
|
||||||
@@ -320,6 +339,7 @@ async fn plan_bulk_update(
|
|||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
let version_ids = installed
|
let version_ids = installed
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|project| updateable_paths.contains(&project.relative_path))
|
||||||
.filter_map(|project| project.version_id.clone())
|
.filter_map(|project| project.version_id.clone())
|
||||||
.chain(
|
.chain(
|
||||||
updates
|
updates
|
||||||
@@ -343,6 +363,7 @@ async fn plan_bulk_update(
|
|||||||
let planned_versions = installed
|
let planned_versions = installed
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|project| project.enabled)
|
.filter(|project| project.enabled)
|
||||||
|
.filter(|project| updateable_paths.contains(&project.relative_path))
|
||||||
.filter_map(|project| {
|
.filter_map(|project| {
|
||||||
let target_version_id = updates_by_path
|
let target_version_id = updates_by_path
|
||||||
.get(&project.relative_path)
|
.get(&project.relative_path)
|
||||||
@@ -379,6 +400,21 @@ async fn plan_bulk_update(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn bulk_updateable_project_paths(
|
||||||
|
instance_id: &str,
|
||||||
|
state: &State,
|
||||||
|
) -> crate::Result<HashSet<String>> {
|
||||||
|
let items = super::list_content::list_content(
|
||||||
|
instance_id,
|
||||||
|
None,
|
||||||
|
Some(CacheBehaviour::MustRevalidate),
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(items.into_iter().map(|item| item.file_path).collect())
|
||||||
|
}
|
||||||
|
|
||||||
async fn installed_projects(
|
async fn installed_projects(
|
||||||
instance_id: &str,
|
instance_id: &str,
|
||||||
content_set: &ContentSet,
|
content_set: &ContentSet,
|
||||||
|
|||||||
@@ -208,6 +208,8 @@ pub(crate) async fn list_content(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let imported_modpack_scope = is_imported_modpack_scope(&resolved, &link);
|
let imported_modpack_scope = is_imported_modpack_scope(&resolved, &link);
|
||||||
|
let linked_modpack_source_kind = linked_modpack_source_kind(&link);
|
||||||
|
let mut failed_modpack_identifier_lookup = false;
|
||||||
let modpack_ids = if imported_modpack_scope {
|
let modpack_ids = if imported_modpack_scope {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
@@ -226,6 +228,7 @@ pub(crate) async fn list_content(
|
|||||||
"Failed to fetch modpack identifiers: {}",
|
"Failed to fetch modpack identifiers: {}",
|
||||||
err
|
err
|
||||||
);
|
);
|
||||||
|
failed_modpack_identifier_lookup = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -240,6 +243,12 @@ pub(crate) async fn list_content(
|
|||||||
}
|
}
|
||||||
} else if let Some(ids) = modpack_ids.as_ref() {
|
} else if let Some(ids) = modpack_ids.as_ref() {
|
||||||
ContentFilter::ExcludeModpack(ids)
|
ContentFilter::ExcludeModpack(ids)
|
||||||
|
} else if failed_modpack_identifier_lookup {
|
||||||
|
ContentFilter::ExcludeSourceKind {
|
||||||
|
source_kind: linked_modpack_source_kind
|
||||||
|
.unwrap_or(ContentSourceKind::ModrinthModpack),
|
||||||
|
exclude_untracked: true,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ContentFilter::All
|
ContentFilter::All
|
||||||
};
|
};
|
||||||
@@ -735,6 +744,9 @@ async fn content_projects_for_scope(
|
|||||||
update_version_id,
|
update_version_id,
|
||||||
hash: file.sha1,
|
hash: file.sha1,
|
||||||
file_name: file.file_name,
|
file_name: file.file_name,
|
||||||
|
enabled: entry.map_or(file.enabled, |entry| {
|
||||||
|
entry.enabled && file.enabled
|
||||||
|
}),
|
||||||
size: file.size,
|
size: file.size,
|
||||||
metadata: file_metadata_from_entry_or_cache(entry, metadata),
|
metadata: file_metadata_from_entry_or_cache(entry, metadata),
|
||||||
project_type,
|
project_type,
|
||||||
@@ -882,7 +894,7 @@ async fn content_files_to_content_items(
|
|||||||
file_path: path.clone(),
|
file_path: path.clone(),
|
||||||
id: file.hash.clone(),
|
id: file.hash.clone(),
|
||||||
size: file.size,
|
size: file.size,
|
||||||
enabled: !file.file_name.ends_with(".disabled"),
|
enabled: file.enabled,
|
||||||
project_type: file.project_type,
|
project_type: file.project_type,
|
||||||
project: project.map(|project| ContentItemProject {
|
project: project.map(|project| ContentItemProject {
|
||||||
id: project.id.clone(),
|
id: project.id.clone(),
|
||||||
@@ -1097,6 +1109,20 @@ fn linked_modpack_ids(link: &InstanceLink) -> Option<(String, String)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn linked_modpack_source_kind(
|
||||||
|
link: &InstanceLink,
|
||||||
|
) -> Option<ContentSourceKind> {
|
||||||
|
match link {
|
||||||
|
InstanceLink::ModrinthModpack { .. } => {
|
||||||
|
Some(ContentSourceKind::ModrinthModpack)
|
||||||
|
}
|
||||||
|
InstanceLink::ServerProjectModpack { .. } => {
|
||||||
|
Some(ContentSourceKind::ServerProject)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn check_modpack_update(
|
fn check_modpack_update(
|
||||||
installed_version_id: &str,
|
installed_version_id: &str,
|
||||||
installed_version: &Version,
|
installed_version: &Version,
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ pub(crate) async fn sync_instance_content_files(
|
|||||||
&state.api_semaphore,
|
&state.api_semaphore,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let hashes_by_path = hashes
|
let hashes_by_key = hashes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|hash| (hash.path.clone(), hash))
|
.map(|hash| {
|
||||||
|
(
|
||||||
|
format!(
|
||||||
|
"{}-{}",
|
||||||
|
hash.size,
|
||||||
|
hash.path.trim_end_matches(".disabled")
|
||||||
|
),
|
||||||
|
hash,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
let existing_files =
|
let existing_files =
|
||||||
sqlite::content_rows::get_instance_files(&instance.id, &state.pool)
|
sqlite::content_rows::get_instance_files(&instance.id, &state.pool)
|
||||||
@@ -55,8 +64,8 @@ pub(crate) async fn sync_instance_content_files(
|
|||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
|
|
||||||
for file in scanned {
|
for file in scanned {
|
||||||
let cache_path = format!("{}/{}", instance.path, file.relative_path);
|
let hash_key = file.hash_cache_key.trim_end_matches(".disabled");
|
||||||
let Some(hash) = hashes_by_path.get(&cache_path) else {
|
let Some(hash) = hashes_by_key.get(hash_key) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let existing_file = existing_files_by_path.get(&file.relative_path);
|
let existing_file = existing_files_by_path.get(&file.relative_path);
|
||||||
|
|||||||
Reference in New Issue
Block a user