mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 01:38:41 +00:00 
			
		
		
		
	Properly added support for local files in the modpack
This commit is contained in:
		
							parent
							
								
									d079f448ae
								
							
						
					
					
						commit
						9df380ad18
					
				
							
								
								
									
										3
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -148,6 +148,9 @@ name = "anyhow"
 | 
			
		|||
version = "1.0.86"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "backtrace",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "approx"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ version = "0.1.0"
 | 
			
		|||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow = "1.0.86"
 | 
			
		||||
anyhow = { version = "1.0.86", features = ["backtrace"] }
 | 
			
		||||
clap = { version = "4.5.15", features = ["derive"] }
 | 
			
		||||
git2 = "0.19.0"
 | 
			
		||||
home = "0.5.9"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,44 @@
 | 
			
		|||
use crate::providers::DownloadSide;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
use std::{fmt::Display, path::{Path, PathBuf}, str::FromStr};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
 | 
			
		||||
pub struct FileMeta {
 | 
			
		||||
    /// Relative path of file in the instance folder
 | 
			
		||||
    pub target_path: String,
 | 
			
		||||
    /// Which side the files should be applied on
 | 
			
		||||
    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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ mod resolver;
 | 
			
		|||
 | 
			
		||||
use anyhow::{Error, Result};
 | 
			
		||||
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 modpack::ModpackMeta;
 | 
			
		||||
use profiles::{PackSource, Profile};
 | 
			
		||||
| 
						 | 
				
			
			@ -134,10 +134,13 @@ enum FileCommands {
 | 
			
		|||
        local_path: PathBuf,
 | 
			
		||||
        /// Target path to copy the file/folder to relative to the MC instance directory
 | 
			
		||||
        #[arg(short, long)]
 | 
			
		||||
        target_path: Option<PathBuf>,
 | 
			
		||||
        target_path: Option<String>,
 | 
			
		||||
        /// 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)]
 | 
			
		||||
        apply_policy: FileApplyPolicy,
 | 
			
		||||
    },
 | 
			
		||||
    /// Show metadata about a file in the pack
 | 
			
		||||
    Show {
 | 
			
		||||
| 
						 | 
				
			
			@ -437,15 +440,19 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                            local_path,
 | 
			
		||||
                            target_path,
 | 
			
		||||
                            side,
 | 
			
		||||
                            apply_policy,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
 | 
			
		||||
                            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, ¤t_dir)?
 | 
			
		||||
                            };
 | 
			
		||||
                            let file_meta = FileMeta {
 | 
			
		||||
                                target_path: get_normalized_relative_path(
 | 
			
		||||
                                    &target_path.unwrap_or(local_path.clone()),
 | 
			
		||||
                                    current_dir,
 | 
			
		||||
                                )?,
 | 
			
		||||
                                target_path,
 | 
			
		||||
                                side,
 | 
			
		||||
                                apply_policy,
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            modpack_meta.add_file(&local_path, &file_meta, current_dir)?;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
use crate::{
 | 
			
		||||
    file_meta::{get_normalized_relative_path, FileMeta},
 | 
			
		||||
    file_meta::{get_normalized_relative_path, FileApplyPolicy, FileMeta},
 | 
			
		||||
    mod_meta::{ModMeta, ModProvider},
 | 
			
		||||
    providers::DownloadSide,
 | 
			
		||||
};
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
| 
						 | 
				
			
			@ -130,16 +131,6 @@ impl ModpackMeta {
 | 
			
		|||
                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);
 | 
			
		||||
 | 
			
		||||
        // Make sure this path is consistent across platforms
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +180,86 @@ impl ModpackMeta {
 | 
			
		|||
        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<()> {
 | 
			
		||||
        let modpack_meta_file_path = directory.join(PathBuf::from(MODPACK_FILENAME));
 | 
			
		||||
        if modpack_meta_file_path.exists() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@ use std::{
 | 
			
		|||
    str::FromStr,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{providers::DownloadSide, resolver::PinnedPackMeta};
 | 
			
		||||
use crate::{modpack::ModpackMeta, providers::DownloadSide, resolver::PinnedPackMeta};
 | 
			
		||||
 | 
			
		||||
const CONFIG_DIR_NAME: &str = "mcmpmgr";
 | 
			
		||||
const DATA_FILENAME: &str = "data.toml";
 | 
			
		||||
| 
						 | 
				
			
			@ -58,16 +58,20 @@ impl Profile {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    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 } => {
 | 
			
		||||
                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 } => (
 | 
			
		||||
                PinnedPackMeta::load_from_directory(&path, true).await?,
 | 
			
		||||
                path.to_path_buf(),
 | 
			
		||||
                None,
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
        let modpack_meta = ModpackMeta::load_from_directory(&pack_directory)?;
 | 
			
		||||
        modpack_meta.install_files(&pack_directory, &self.instance_folder, self.side)?;
 | 
			
		||||
 | 
			
		||||
        pack_lock
 | 
			
		||||
            .download_mods(&self.instance_folder.join("mods"), self.side)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,12 @@ pub enum DownloadSide {
 | 
			
		|||
    Client,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl DownloadSide {
 | 
			
		||||
    pub fn contains(self, side: Self) -> bool {
 | 
			
		||||
        self == Self::Both || side == Self::Both || self == side
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromStr for DownloadSide {
 | 
			
		||||
    type Err = anyhow::Error;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ version = "0.1.0"
 | 
			
		|||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow = "1.0.86"
 | 
			
		||||
anyhow = { version = "1.0.86", features = ["backtrace"] }
 | 
			
		||||
iced = { version = "0.12.1", features = ["tokio"] }
 | 
			
		||||
mcmpmgr = { version = "0.1.0", path = "../mcmpmgr" }
 | 
			
		||||
rfd = "0.14.1"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue