mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 07:58:40 +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"
 | 
					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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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, ¤t_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)?;
 | 
				
			||||||
| 
						 | 
					@ -512,7 +519,7 @@ async fn main() -> anyhow::Result<()> {
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
                            println!("Profile name      : {name}");
 | 
					                            println!("Profile name      : {name}");
 | 
				
			||||||
                            println!("Instance folder   : {}", profile.instance_folder.display());
 | 
					                            println!("Instance folder   : {}", profile.instance_folder.display());
 | 
				
			||||||
                            println!("Modpack source: {}", profile.pack_source);
 | 
					                            println!("Modpack source    : {}", profile.pack_source);
 | 
				
			||||||
                            println!("Side              : {}", profile.side);
 | 
					                            println!("Side              : {}", profile.side);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue