Actually consider file/folder merge apply policies when installing modpacks

This commit is contained in:
Warren Hood 2024-10-09 01:16:55 +02:00
parent 4d7f6ed14e
commit d1bce34c60

View file

@ -1,13 +1,15 @@
use crate::{ use crate::{
file_merge,
file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta}, file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta},
mod_meta::{ModMeta, ModProvider}, mod_meta::{ModMeta, ModProvider},
providers::DownloadSide, providers::DownloadSide,
}; };
use anyhow::Result; use anyhow::{Context, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr,
}; };
const MODPACK_FILENAME: &str = "modpack.toml"; const MODPACK_FILENAME: &str = "modpack.toml";
@ -187,6 +189,8 @@ impl ModpackMeta {
/// ///
/// Files/Folders, when applied, will ensure that the exact contents of that file or folder match in the instance folder /// Files/Folders, when applied, will ensure that the exact contents of that file or folder match in the instance folder
/// Ie. If a folder is being applied, any files in that folder not in the modpack will be removed /// Ie. If a folder is being applied, any files in that folder not in the modpack will be removed
///
/// Both merge policies will recursively copy files/folders from the src into the destination, while performing merges instead of file copies.
pub fn install_files( pub fn install_files(
&self, &self,
pack_dir: &Path, pack_dir: &Path,
@ -225,36 +229,83 @@ impl ModpackMeta {
if source_path.is_dir() { if source_path.is_dir() {
// Sync a folder // Sync a folder
if target_path.exists() { if target_path.exists() {
if file_meta.apply_policy == FileApplyPolicy::Always
|| file_meta.apply_policy == FileApplyPolicy::Once
{
println!( println!(
"Syncing and overwriting existing directory {} -> {}", "Syncing and overwriting existing directory {} -> {}",
source_path.display(), source_path.display(),
target_path.display(), target_path.display(),
); );
std::fs::remove_dir_all(&target_path)?; std::fs::remove_dir_all(&target_path)?;
} else {
println!(
"Merging existing directory {} -> {} (policy={})",
source_path.display(),
target_path.display(),
file_meta.apply_policy
);
} }
} }
self.copy_files(&source_path, &target_path)?; }
self.copy_files(&source_path, &target_path, file_meta.apply_policy.clone())?;
} }
} }
Ok(()) Ok(())
} }
fn copy_files(&self, src: &Path, dst: &Path) -> Result<()> { fn copy_files(&self, src: &Path, dst: &Path, apply_policy: FileApplyPolicy) -> Result<()> {
if src.is_dir() { if src.is_dir() {
std::fs::create_dir_all(dst)?; std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? { for entry in std::fs::read_dir(src)? {
let entry = entry?; let entry = entry?;
let src_path = entry.path(); let src_path = entry.path();
let dst_path = dst.join(entry.file_name()); let dst_path = dst.join(entry.file_name());
self.copy_files(&src_path, &dst_path)?; self.copy_files(&src_path, &dst_path, apply_policy.clone())?;
} }
} else { } else {
let parent_dir = dst.parent(); let parent_dir = dst.parent();
if let Some(parent_dir) = parent_dir { if let Some(parent_dir) = parent_dir {
std::fs::create_dir_all(parent_dir)?; std::fs::create_dir_all(parent_dir)?;
} }
if apply_policy == FileApplyPolicy::Always || apply_policy == FileApplyPolicy::Once {
println!("Syncing file {} -> {}", src.display(), dst.display()); println!("Syncing file {} -> {}", src.display(), dst.display());
std::fs::copy(src, dst)?; std::fs::copy(src, dst)?;
} else {
// Merging files
let src_val = std::fs::read_to_string(src)?;
let dst_val = if dst.exists() {
Some(std::fs::read_to_string(dst)?)
} else {
None
};
if let Some(dst_val) = dst_val {
let file_ext = if let Some(ext) = dst.extension() {
ext
} else {
anyhow::bail!("Cannot merge file '{dst:#?}' with unknown file type")
}
.to_string_lossy();
let file_type = file_merge::FileType::from_str(&file_ext)
.with_context(|| format!("Couldn't merge file {src:?} -> {dst:?}"))?;
let merged_contents = file_merge::merge_files(
&src_val,
&dst_val,
apply_policy == FileApplyPolicy::MergeOverwrite,
file_type,
)
.with_context(|| format!("Failed to merge file {src:?} -> {dst:?}"))?;
std::fs::write(dst, merged_contents).with_context(|| {
format!("Failed to write merged contents of {src:?} -> {dst:?}")
})?;
} else {
println!("Syncing file {} -> {}", src.display(), dst.display());
std::fs::copy(src, dst)?;
}
}
} }
Ok(()) Ok(())