Properly added support for local files in the modpack

This commit is contained in:
Warren Hood 2024-09-01 21:57:20 +02:00
parent d079f448ae
commit 9df380ad18
8 changed files with 150 additions and 26 deletions

3
Cargo.lock generated
View file

@ -148,6 +148,9 @@ name = "anyhow"
version = "1.0.86" version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
dependencies = [
"backtrace",
]
[[package]] [[package]]
name = "approx" name = "approx"

View file

@ -5,7 +5,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.86" anyhow = { version = "1.0.86", features = ["backtrace"] }
clap = { version = "4.5.15", features = ["derive"] } clap = { version = "4.5.15", features = ["derive"] }
git2 = "0.19.0" git2 = "0.19.0"
home = "0.5.9" home = "0.5.9"

View file

@ -1,11 +1,44 @@
use crate::providers::DownloadSide; use crate::providers::DownloadSide;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf}; use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
#[derive(Debug, Clone, Serialize, Deserialize, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, Hash)]
pub struct FileMeta { pub struct FileMeta {
/// Relative path of file in the instance folder
pub target_path: String, pub target_path: String,
/// Which side the files should be applied on
pub side: DownloadSide, pub side: DownloadSide,
/// When to apply the files to the instance
pub apply_policy: FileApplyPolicy
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub enum FileApplyPolicy {
/// Always ensure the file or folder exactly matches that defined in the pack
Always,
/// Only apply the file or folder if it doesn't already exist in the pack
Once
}
impl FromStr for FileApplyPolicy {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"always" => Ok(Self::Always),
"once" => Ok(Self::Once),
_ => anyhow::bail!("Invalid apply policy {}. Expected one of: always, once", s),
}
}
}
impl Display for FileApplyPolicy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Always => write!(f, "Always"),
Self::Once => write!(f, "Once"),
}
}
} }
impl PartialEq for FileMeta { impl PartialEq for FileMeta {

View file

@ -7,7 +7,7 @@ mod resolver;
use anyhow::{Error, Result}; use anyhow::{Error, Result};
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use file_meta::{get_normalized_relative_path, FileMeta}; use file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta};
use mod_meta::{ModMeta, ModProvider}; use mod_meta::{ModMeta, ModProvider};
use modpack::ModpackMeta; use modpack::ModpackMeta;
use profiles::{PackSource, Profile}; use profiles::{PackSource, Profile};
@ -134,10 +134,13 @@ enum FileCommands {
local_path: PathBuf, local_path: PathBuf,
/// Target path to copy the file/folder to relative to the MC instance directory /// Target path to copy the file/folder to relative to the MC instance directory
#[arg(short, long)] #[arg(short, long)]
target_path: Option<PathBuf>, target_path: Option<String>,
/// Side to copy the file/folder to /// Side to copy the file/folder to
#[arg(long, default_value_t = DownloadSide::Server)] #[arg(long, default_value_t = DownloadSide::Server)]
side: DownloadSide, 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)]
apply_policy: FileApplyPolicy,
}, },
/// Show metadata about a file in the pack /// Show metadata about a file in the pack
Show { Show {
@ -437,15 +440,19 @@ async fn main() -> anyhow::Result<()> {
local_path, local_path,
target_path, target_path,
side, side,
apply_policy,
} => { } => {
let mut modpack_meta = ModpackMeta::load_from_current_directory()?; let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
let current_dir = &std::env::current_dir()?; let current_dir = &std::env::current_dir()?;
let target_path = if let Some(target_path) = target_path {
target_path
} else {
get_normalized_relative_path(&local_path, &current_dir)?
};
let file_meta = FileMeta { let file_meta = FileMeta {
target_path: get_normalized_relative_path( target_path,
&target_path.unwrap_or(local_path.clone()),
current_dir,
)?,
side, side,
apply_policy,
}; };
modpack_meta.add_file(&local_path, &file_meta, current_dir)?; modpack_meta.add_file(&local_path, &file_meta, current_dir)?;

View file

@ -1,6 +1,7 @@
use crate::{ use crate::{
file_meta::{get_normalized_relative_path, FileMeta}, file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta},
mod_meta::{ModMeta, ModProvider}, mod_meta::{ModMeta, ModProvider},
providers::DownloadSide,
}; };
use anyhow::Result; use anyhow::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -130,16 +131,6 @@ impl ModpackMeta {
pack_root.display() pack_root.display()
))? ))?
}; };
let target_path = PathBuf::from(&file_meta.target_path);
if !target_path.is_relative() {
anyhow::bail!(
"Target path {} for file {} is not relative!",
file_meta.target_path,
file_path.display()
);
}
let full_path = pack_root.join(relative_path); let full_path = pack_root.join(relative_path);
// Make sure this path is consistent across platforms // Make sure this path is consistent across platforms
@ -189,6 +180,86 @@ impl ModpackMeta {
Ok(self) Ok(self)
} }
/// Installs all the manual files from the pack into the specified directory
///
/// Files/Folders are added if they don't exist if the policy is set to `FileApplyPolicy::Once`.
/// Otherwise, files/folders are always overwritten.
///
/// 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
pub fn install_files(
&self,
pack_dir: &Path,
instance_dir: &Path,
side: DownloadSide,
) -> Result<()> {
println!(
"Applying modpack files: {} -> {}...",
pack_dir.display(),
instance_dir.display()
);
if let Some(files) = &self.files {
for (rel_path, file_meta) in files {
let source_path = pack_dir.join(rel_path);
let target_path = instance_dir.join(&file_meta.target_path);
if !side.contains(file_meta.side) {
println!(
"Skipping apply of {} -> {}. (Applies for side={}, current side={})",
source_path.display(),
target_path.display(),
file_meta.side.to_string(),
side.to_string()
);
continue;
}
if target_path.exists() && file_meta.apply_policy == FileApplyPolicy::Once {
println!(
"Skipping apply of {} -> {}. (Already applied once)",
source_path.display(),
target_path.display(),
);
continue;
}
// Otherwise, this file/folder needs to be applied
if source_path.is_dir() {
// Sync a folder
if target_path.exists() {
println!(
"Syncing and overwriting existing directory {} -> {}",
source_path.display(),
target_path.display(),
);
std::fs::remove_dir_all(&target_path)?;
}
}
self.copy_files(&source_path, &target_path)?;
}
}
Ok(())
}
fn copy_files(&self, src: &Path, dst: &Path) -> 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)?;
}
} else {
let parent_dir = dst.parent();
if let Some(parent_dir) = parent_dir {
std::fs::create_dir_all(parent_dir)?;
}
println!("Syncing file {} -> {}", src.display(), dst.display());
std::fs::copy(src, dst)?;
}
Ok(())
}
pub fn init_project(&self, directory: &Path) -> Result<()> { pub fn init_project(&self, directory: &Path) -> Result<()> {
let modpack_meta_file_path = directory.join(PathBuf::from(MODPACK_FILENAME)); let modpack_meta_file_path = directory.join(PathBuf::from(MODPACK_FILENAME));
if modpack_meta_file_path.exists() { if modpack_meta_file_path.exists() {

View file

@ -7,7 +7,7 @@ use std::{
str::FromStr, str::FromStr,
}; };
use crate::{providers::DownloadSide, resolver::PinnedPackMeta}; use crate::{modpack::ModpackMeta, providers::DownloadSide, resolver::PinnedPackMeta};
const CONFIG_DIR_NAME: &str = "mcmpmgr"; const CONFIG_DIR_NAME: &str = "mcmpmgr";
const DATA_FILENAME: &str = "data.toml"; const DATA_FILENAME: &str = "data.toml";
@ -58,16 +58,20 @@ impl Profile {
} }
pub async fn install(&self) -> Result<()> { pub async fn install(&self) -> Result<()> {
let (pack_lock, temp_dir) = match &self.pack_source { let (pack_lock, pack_directory, _temp_dir) = match &self.pack_source {
PackSource::Git { url } => { PackSource::Git { url } => {
let (pack_lock, packdir) = PinnedPackMeta::load_from_git_repo(&url, true).await?; let (pack_lock, packdir) = PinnedPackMeta::load_from_git_repo(&url, true).await?;
(pack_lock, Some(packdir)) let pack_path = packdir.path().to_path_buf();
(pack_lock, pack_path, Some(packdir))
} }
PackSource::Local { path } => ( PackSource::Local { path } => (
PinnedPackMeta::load_from_directory(&path, true).await?, PinnedPackMeta::load_from_directory(&path, true).await?,
path.to_path_buf(),
None, None,
), ),
}; };
let modpack_meta = ModpackMeta::load_from_directory(&pack_directory)?;
modpack_meta.install_files(&pack_directory, &self.instance_folder, self.side)?;
pack_lock pack_lock
.download_mods(&self.instance_folder.join("mods"), self.side) .download_mods(&self.instance_folder.join("mods"), self.side)

View file

@ -28,6 +28,12 @@ pub enum DownloadSide {
Client, Client,
} }
impl DownloadSide {
pub fn contains(self, side: Self) -> bool {
self == Self::Both || side == Self::Both || self == side
}
}
impl FromStr for DownloadSide { impl FromStr for DownloadSide {
type Err = anyhow::Error; type Err = anyhow::Error;

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.86" anyhow = { version = "1.0.86", features = ["backtrace"] }
iced = { version = "0.12.1", features = ["tokio"] } iced = { version = "0.12.1", features = ["tokio"] }
mcmpmgr = { version = "0.1.0", path = "../mcmpmgr" } mcmpmgr = { version = "0.1.0", path = "../mcmpmgr" }
rfd = "0.14.1" rfd = "0.14.1"