Compare commits

..

4 commits

7 changed files with 78 additions and 24 deletions

4
Cargo.lock generated
View file

@ -2064,7 +2064,7 @@ dependencies = [
[[package]]
name = "mcmpmgr"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"anyhow",
"clap",
@ -2170,7 +2170,7 @@ dependencies = [
[[package]]
name = "mmm"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"anyhow",
"iced",

View file

@ -3,7 +3,8 @@ resolver = "2"
members = ["mcmpmgr", "mmm"]
[workspace.package]
version = "0.1.0"
authors = ["Warren Hood <nullbyte001@gmail.com>"]
version = "0.2.0"
edition = "2021"
[workspace.metadata.crane]

View file

@ -7,7 +7,7 @@ This is a rather unorganised list of TODOs just so I can somewhat keep track of
### Important
- [X] Canonicalize relative path args with `profile` commands
- [ ] Add a "merge" apply policy that can merge contents of certain file types.
- [X] Add a "merge" apply policy that can merge contents of certain file types.
Eg. toml, json, and anything else that is reasonable (key-value type things... with nesting)
This should also be able to merge folders, while recursively applying "merge" logic to individual files.
@ -16,6 +16,7 @@ This is a rather unorganised list of TODOs just so I can somewhat keep track of
merge (with conflict overrides on a file content level) should result in an install dir with A and B, where a.json and b.json are in A, and a.json is the result of merging a.json into the installed a.json (overwriting any existing key's values with the modpack's values), and the original files in folder B untouched (x.json and y.json)
merge (retaining original/modified values) merge should result in an install dir with A and B, where a.json and b.json are in A, and a.json is the result of merging a.json into the installed a.json (retaining the existing values from the file in the install dir), and the original files in folder B untouched (x.json and y.json)
- [ ] Test the merge apply policies when I am not half asleep.
### Nice to haves

View file

@ -1,8 +1,8 @@
[package]
name = "mcmpmgr"
authors = ["Warren Hood <nullbyte001@gmail.com>"]
version = "0.1.0"
edition = "2021"
authors.workspace = true
version.workspace = true
edition.workspace = true
[dependencies]
anyhow = { version = "1.0.86", features = ["backtrace"] }

View file

@ -139,8 +139,8 @@ enum FileCommands {
/// Side to copy the file/folder to
#[arg(long, default_value_t = DownloadSide::Server)]
side: DownloadSide,
/// File apply policy - whether to always apply the file or just apply it once (if the file doesn't exist)
#[arg(long, default_value_t = FileApplyPolicy::Always)]
/// File apply policy - whether to always apply the file or just apply it once (if the file doesn't exist), or merge (mergeretain or mergeoverwrite)
#[arg(long, default_value_t = FileApplyPolicy::MergeOverwrite)]
apply_policy: FileApplyPolicy,
},
/// Show metadata about a file in the pack

View file

@ -1,13 +1,15 @@
use crate::{
file_merge,
file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta},
mod_meta::{ModMeta, ModProvider},
providers::DownloadSide,
};
use anyhow::Result;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, BTreeSet},
path::{Path, PathBuf},
str::FromStr,
};
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
/// 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(
&self,
pack_dir: &Path,
@ -225,36 +229,83 @@ impl ModpackMeta {
if source_path.is_dir() {
// Sync a folder
if target_path.exists() {
if file_meta.apply_policy == FileApplyPolicy::Always
|| file_meta.apply_policy == FileApplyPolicy::Once
{
println!(
"Syncing and overwriting existing directory {} -> {}",
source_path.display(),
target_path.display(),
);
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(())
}
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() {
std::fs::create_dir_all(dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
let src_path = entry.path();
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 {
let parent_dir = dst.parent();
if let Some(parent_dir) = 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());
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(())

View file

@ -1,10 +1,11 @@
[package]
name = "mmm"
version = "0.1.0"
edition = "2021"
authors.workspace = true
version.workspace = true
edition.workspace = true
[dependencies]
anyhow = { version = "1.0.86", features = ["backtrace"] }
iced = { version = "0.12.1", features = ["tokio"] }
mcmpmgr = { version = "0.1.0", path = "../mcmpmgr" }
mcmpmgr = { path = "../mcmpmgr" }
rfd = "0.14.1"