mirror of
				https://github.com/WarrenHood/MCModpackManager.git
				synced 2025-11-04 13:18:41 +00:00 
			
		
		
		
	Compare commits
	
		
			2 commits
		
	
	
		
			042bfb8baa
			...
			d079f448ae
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
							
							
								
								 | 
						d079f448ae | ||
| 
							
							
								
								 | 
						db4b2bd694 | 
							
								
								
									
										7
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -2067,6 +2067,7 @@ dependencies = [
 | 
			
		|||
 "clap",
 | 
			
		||||
 "git2",
 | 
			
		||||
 "home",
 | 
			
		||||
 "pathdiff",
 | 
			
		||||
 "reqwest",
 | 
			
		||||
 "semver",
 | 
			
		||||
 "serde",
 | 
			
		||||
| 
						 | 
				
			
			@ -2618,6 +2619,12 @@ version = "1.0.15"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "pathdiff"
 | 
			
		||||
version = "0.2.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "percent-encoding"
 | 
			
		||||
version = "2.3.1"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ anyhow = "1.0.86"
 | 
			
		|||
clap = { version = "4.5.15", features = ["derive"] }
 | 
			
		||||
git2 = "0.19.0"
 | 
			
		||||
home = "0.5.9"
 | 
			
		||||
pathdiff = "0.2.1"
 | 
			
		||||
reqwest = { version = "0.12.5", features = ["json"] }
 | 
			
		||||
semver = { version = "1.0.23", features = ["serde"] }
 | 
			
		||||
serde = { version = "1.0.207", features = ["derive"] }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										64
									
								
								mcmpmgr/src/file_meta.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								mcmpmgr/src/file_meta.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
use crate::providers::DownloadSide;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, Hash)]
 | 
			
		||||
pub struct FileMeta {
 | 
			
		||||
    pub target_path: String,
 | 
			
		||||
    pub side: DownloadSide,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialEq for FileMeta {
 | 
			
		||||
    fn eq(&self, other: &Self) -> bool {
 | 
			
		||||
        self.target_path == other.target_path
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialOrd for FileMeta {
 | 
			
		||||
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
 | 
			
		||||
        self.target_path.partial_cmp(&other.target_path)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Ord for FileMeta {
 | 
			
		||||
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
 | 
			
		||||
        self.target_path.cmp(&other.target_path)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Eq for FileMeta {}
 | 
			
		||||
 | 
			
		||||
/// Get a normalized relative path string in a consistent way across platforms
 | 
			
		||||
/// TODO: Make a nice struct for this maybe
 | 
			
		||||
pub fn get_normalized_relative_path(
 | 
			
		||||
    path_to_normalize: &Path,
 | 
			
		||||
    base_path: &Path,
 | 
			
		||||
) -> anyhow::Result<String> {
 | 
			
		||||
    if path_to_normalize.is_absolute() {
 | 
			
		||||
        anyhow::bail!(
 | 
			
		||||
            "Absolute paths are not supported! Will not normalise {}",
 | 
			
		||||
            path_to_normalize.display()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    let base_path = base_path.canonicalize()?;
 | 
			
		||||
    let full_path = base_path.join(path_to_normalize).canonicalize()?;
 | 
			
		||||
    let relative_path = pathdiff::diff_paths(&full_path, &base_path).ok_or(anyhow::format_err!(
 | 
			
		||||
        "Cannot normalize path {} relative to {}",
 | 
			
		||||
        &path_to_normalize.display(),
 | 
			
		||||
        &base_path.display()
 | 
			
		||||
    ))?;
 | 
			
		||||
 | 
			
		||||
    let mut normalized_path = String::new();
 | 
			
		||||
    for (i, component) in relative_path.components().enumerate() {
 | 
			
		||||
        if i > 0 {
 | 
			
		||||
            normalized_path.push('/');
 | 
			
		||||
        }
 | 
			
		||||
        normalized_path.push_str(&component.as_os_str().to_string_lossy());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if !normalized_path.starts_with("./") && !normalized_path.starts_with("/") {
 | 
			
		||||
        normalized_path.insert_str(0, "./");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(normalized_path)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
pub mod mod_meta;
 | 
			
		||||
pub mod file_meta;
 | 
			
		||||
pub mod modpack;
 | 
			
		||||
pub mod profiles;
 | 
			
		||||
pub mod providers;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
mod file_meta;
 | 
			
		||||
mod mod_meta;
 | 
			
		||||
mod modpack;
 | 
			
		||||
mod profiles;
 | 
			
		||||
| 
						 | 
				
			
			@ -6,6 +7,7 @@ mod resolver;
 | 
			
		|||
 | 
			
		||||
use anyhow::{Error, Result};
 | 
			
		||||
use clap::{Args, Parser, Subcommand};
 | 
			
		||||
use file_meta::{get_normalized_relative_path, FileMeta};
 | 
			
		||||
use mod_meta::{ModMeta, ModProvider};
 | 
			
		||||
use modpack::ModpackMeta;
 | 
			
		||||
use profiles::{PackSource, Profile};
 | 
			
		||||
| 
						 | 
				
			
			@ -74,7 +76,7 @@ enum Commands {
 | 
			
		|||
        modloader: Option<modpack::ModLoader>,
 | 
			
		||||
        /// Side override
 | 
			
		||||
        #[arg(long, short)]
 | 
			
		||||
        side: Option<DownloadSide>
 | 
			
		||||
        side: Option<DownloadSide>,
 | 
			
		||||
    },
 | 
			
		||||
    /// Remove a mod from the modpack
 | 
			
		||||
    Remove {
 | 
			
		||||
| 
						 | 
				
			
			@ -109,10 +111,46 @@ enum Commands {
 | 
			
		|||
        #[arg(long, short, action)]
 | 
			
		||||
        locked: bool,
 | 
			
		||||
    },
 | 
			
		||||
    /// Manage local files in the modpack
 | 
			
		||||
    File(FileArgs),
 | 
			
		||||
    /// Manage mcmpmgr profiles
 | 
			
		||||
    Profile(ProfileArgs),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Args)]
 | 
			
		||||
#[command(args_conflicts_with_subcommands = true)]
 | 
			
		||||
struct FileArgs {
 | 
			
		||||
    #[command(subcommand)]
 | 
			
		||||
    command: Option<FileCommands>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Subcommand)]
 | 
			
		||||
enum FileCommands {
 | 
			
		||||
    /// List all files/folders in the pack
 | 
			
		||||
    List,
 | 
			
		||||
    /// Add new files/folder to the pack
 | 
			
		||||
    Add {
 | 
			
		||||
        /// Local path to file/folder to include in the pack (must be in the pack root)
 | 
			
		||||
        local_path: PathBuf,
 | 
			
		||||
        /// Target path to copy the file/folder to relative to the MC instance directory
 | 
			
		||||
        #[arg(short, long)]
 | 
			
		||||
        target_path: Option<PathBuf>,
 | 
			
		||||
        /// Side to copy the file/folder to
 | 
			
		||||
        #[arg(long, default_value_t = DownloadSide::Server)]
 | 
			
		||||
        side: DownloadSide,
 | 
			
		||||
    },
 | 
			
		||||
    /// Show metadata about a file in the pack
 | 
			
		||||
    Show {
 | 
			
		||||
        /// Local path of the file/folder to show
 | 
			
		||||
        local_path: String,
 | 
			
		||||
    },
 | 
			
		||||
    /// Remove a file/folder from the pack
 | 
			
		||||
    Remove {
 | 
			
		||||
        /// local path to file/folder to remove
 | 
			
		||||
        local_path: PathBuf,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Args)]
 | 
			
		||||
#[command(args_conflicts_with_subcommands = true)]
 | 
			
		||||
struct ProfileArgs {
 | 
			
		||||
| 
						 | 
				
			
			@ -134,9 +172,9 @@ enum ProfileCommands {
 | 
			
		|||
        /// A local file path to a modpack directory or a git repo url prefixed with 'git+'
 | 
			
		||||
        #[arg(long, short)]
 | 
			
		||||
        pack_source: PackSource,
 | 
			
		||||
        /// Mods directory
 | 
			
		||||
        /// Instance directory (containing a mods folder)
 | 
			
		||||
        #[arg(long, short)]
 | 
			
		||||
        mods_directory: PathBuf,
 | 
			
		||||
        instance_directory: PathBuf,
 | 
			
		||||
    },
 | 
			
		||||
    /// Install a profile
 | 
			
		||||
    Install {
 | 
			
		||||
| 
						 | 
				
			
			@ -228,7 +266,7 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                locked,
 | 
			
		||||
                mc_version,
 | 
			
		||||
                modloader,
 | 
			
		||||
                side
 | 
			
		||||
                side,
 | 
			
		||||
            } => {
 | 
			
		||||
                let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
 | 
			
		||||
                let old_modpack_meta = modpack_meta.clone();
 | 
			
		||||
| 
						 | 
				
			
			@ -251,15 +289,15 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                        DownloadSide::Both => {
 | 
			
		||||
                            mod_meta.server_side = Some(true);
 | 
			
		||||
                            mod_meta.client_side = Some(true);
 | 
			
		||||
                        },
 | 
			
		||||
                        }
 | 
			
		||||
                        DownloadSide::Server => {
 | 
			
		||||
                            mod_meta.server_side = Some(true);
 | 
			
		||||
                            mod_meta.client_side = Some(false);
 | 
			
		||||
                        },
 | 
			
		||||
                        }
 | 
			
		||||
                        DownloadSide::Client => {
 | 
			
		||||
                            mod_meta.server_side = Some(false);
 | 
			
		||||
                            mod_meta.client_side = Some(true);
 | 
			
		||||
                        },
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                for provider in providers.into_iter() {
 | 
			
		||||
| 
						 | 
				
			
			@ -391,6 +429,37 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                pack_lock.init(&modpack_meta, !locked).await?;
 | 
			
		||||
                pack_lock.save_current_dir_lock()?;
 | 
			
		||||
            }
 | 
			
		||||
            Commands::File(FileArgs { command }) => {
 | 
			
		||||
                if let Some(command) = command {
 | 
			
		||||
                    match command {
 | 
			
		||||
                        FileCommands::List => todo!(),
 | 
			
		||||
                        FileCommands::Add {
 | 
			
		||||
                            local_path,
 | 
			
		||||
                            target_path,
 | 
			
		||||
                            side,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
 | 
			
		||||
                            let current_dir = &std::env::current_dir()?;
 | 
			
		||||
                            let file_meta = FileMeta {
 | 
			
		||||
                                target_path: get_normalized_relative_path(
 | 
			
		||||
                                    &target_path.unwrap_or(local_path.clone()),
 | 
			
		||||
                                    current_dir,
 | 
			
		||||
                                )?,
 | 
			
		||||
                                side,
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
                            modpack_meta.add_file(&local_path, &file_meta, current_dir)?;
 | 
			
		||||
                            modpack_meta.save_current_dir_project()?;
 | 
			
		||||
                        }
 | 
			
		||||
                        FileCommands::Show { local_path } => todo!(),
 | 
			
		||||
                        FileCommands::Remove { local_path } => {
 | 
			
		||||
                            let mut modpack_meta = ModpackMeta::load_from_current_directory()?;
 | 
			
		||||
                            modpack_meta.remove_file(&local_path, &std::env::current_dir()?)?;
 | 
			
		||||
                            modpack_meta.save_current_dir_project()?;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Commands::Profile(ProfileArgs { command }) => {
 | 
			
		||||
                if let Some(command) = command {
 | 
			
		||||
                    match command {
 | 
			
		||||
| 
						 | 
				
			
			@ -405,10 +474,10 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                            name,
 | 
			
		||||
                            side,
 | 
			
		||||
                            pack_source,
 | 
			
		||||
                            mods_directory,
 | 
			
		||||
                            instance_directory,
 | 
			
		||||
                        } => {
 | 
			
		||||
                            let mut userdata = profiles::Data::load()?;
 | 
			
		||||
                            let profile = Profile::new(&mods_directory, pack_source, side);
 | 
			
		||||
                            let profile = Profile::new(&instance_directory, pack_source, side);
 | 
			
		||||
                            userdata.add_profile(&name, profile);
 | 
			
		||||
                            userdata.save()?;
 | 
			
		||||
                            println!("Saved profile '{name}'");
 | 
			
		||||
| 
						 | 
				
			
			@ -442,7 +511,7 @@ async fn main() -> anyhow::Result<()> {
 | 
			
		|||
                                anyhow::bail!("Profile '{name}' does not exist")
 | 
			
		||||
                            };
 | 
			
		||||
                            println!("Profile name  : {name}");
 | 
			
		||||
                            println!("Mods folder   : {}", profile.mods_folder.display());
 | 
			
		||||
                            println!("Instance folder   : {}", profile.instance_folder.display());
 | 
			
		||||
                            println!("Modpack source: {}", profile.pack_source);
 | 
			
		||||
                            println!("Side          : {}", profile.side);
 | 
			
		||||
                        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,7 @@
 | 
			
		|||
use crate::mod_meta::{ModMeta, ModProvider};
 | 
			
		||||
use crate::{
 | 
			
		||||
    file_meta::{get_normalized_relative_path, FileMeta},
 | 
			
		||||
    mod_meta::{ModMeta, ModProvider},
 | 
			
		||||
};
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::{
 | 
			
		||||
| 
						 | 
				
			
			@ -38,11 +41,19 @@ impl std::str::FromStr for ModLoader {
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
pub struct ModpackMeta {
 | 
			
		||||
    /// The name of the modpack
 | 
			
		||||
    pub pack_name: String,
 | 
			
		||||
    /// The intended minecraft version on which this pack should run
 | 
			
		||||
    pub mc_version: String,
 | 
			
		||||
    /// The default modloader for the modpack
 | 
			
		||||
    pub modloader: ModLoader,
 | 
			
		||||
    /// Map of mod name -> mod metadata
 | 
			
		||||
    pub mods: BTreeMap<String, ModMeta>,
 | 
			
		||||
    /// Mapping of relative paths to files to copy over from the modpack
 | 
			
		||||
    pub files: Option<BTreeMap<String, FileMeta>>,
 | 
			
		||||
    /// Default provider for newly added mods in the modpack
 | 
			
		||||
    pub default_providers: Vec<ModProvider>,
 | 
			
		||||
    /// A set of forbidden mods in the modpack
 | 
			
		||||
    pub forbidden_mods: BTreeSet<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +114,81 @@ impl ModpackMeta {
 | 
			
		|||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add local files or folders to the pack. These should be committed to version control
 | 
			
		||||
    pub fn add_file(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        file_path: &Path,
 | 
			
		||||
        file_meta: &FileMeta,
 | 
			
		||||
        pack_root: &Path,
 | 
			
		||||
    ) -> Result<&mut Self> {
 | 
			
		||||
        let relative_path = if file_path.is_relative() {
 | 
			
		||||
            file_path
 | 
			
		||||
        } else {
 | 
			
		||||
            &pathdiff::diff_paths(file_path, pack_root).ok_or(anyhow::format_err!(
 | 
			
		||||
                "Cannot get relative path of {} in {}",
 | 
			
		||||
                file_path.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);
 | 
			
		||||
 | 
			
		||||
        // Make sure this path is consistent across platforms
 | 
			
		||||
        let relative_path = get_normalized_relative_path(relative_path, &pack_root)?;
 | 
			
		||||
 | 
			
		||||
        if !full_path
 | 
			
		||||
            .canonicalize()?
 | 
			
		||||
            .starts_with(pack_root.canonicalize()?)
 | 
			
		||||
        {
 | 
			
		||||
            anyhow::bail!(
 | 
			
		||||
                "You cannot add local files to the modpack from outside the pack source directory. {} is not contained in {}",
 | 
			
		||||
                full_path.canonicalize()?.display(),
 | 
			
		||||
                pack_root.canonicalize()?.display()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        match &mut self.files {
 | 
			
		||||
            Some(files) => {
 | 
			
		||||
                files.insert(relative_path.clone(), file_meta.clone());
 | 
			
		||||
            }
 | 
			
		||||
            None => {
 | 
			
		||||
                self.files
 | 
			
		||||
                    .insert(BTreeMap::new())
 | 
			
		||||
                    .insert(relative_path.clone(), file_meta.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        println!(
 | 
			
		||||
            "Added file '{relative_path}' -> '{}' to modpack...",
 | 
			
		||||
            file_meta.target_path
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        Ok(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn remove_file(&mut self, file_path: &PathBuf, pack_root: &Path) -> Result<&mut Self> {
 | 
			
		||||
        let relative_path = get_normalized_relative_path(&file_path, pack_root)?;
 | 
			
		||||
        if let Some(files) = &mut self.files {
 | 
			
		||||
            let removed = files.remove(&relative_path);
 | 
			
		||||
            if let Some(removed) = removed {
 | 
			
		||||
                println!(
 | 
			
		||||
                    "Removed file '{relative_path}' -> '{}' from modpack...",
 | 
			
		||||
                    removed.target_path
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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() {
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +226,7 @@ impl std::default::Default for ModpackMeta {
 | 
			
		|||
            mc_version: "1.20.1".into(),
 | 
			
		||||
            modloader: ModLoader::Forge,
 | 
			
		||||
            mods: Default::default(),
 | 
			
		||||
            files: Default::default(),
 | 
			
		||||
            default_providers: vec![ModProvider::Modrinth],
 | 
			
		||||
            forbidden_mods: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,15 +43,15 @@ impl Display for PackSource {
 | 
			
		|||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Profile {
 | 
			
		||||
    pub mods_folder: PathBuf,
 | 
			
		||||
    pub instance_folder: PathBuf,
 | 
			
		||||
    pub pack_source: PackSource,
 | 
			
		||||
    pub side: DownloadSide,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Profile {
 | 
			
		||||
    pub fn new(mods_folder: &Path, pack_source: PackSource, side: DownloadSide) -> Self {
 | 
			
		||||
    pub fn new(instance_folder: &Path, pack_source: PackSource, side: DownloadSide) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mods_folder: mods_folder.into(),
 | 
			
		||||
            instance_folder: instance_folder.into(),
 | 
			
		||||
            pack_source,
 | 
			
		||||
            side,
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ impl Profile {
 | 
			
		|||
        };
 | 
			
		||||
 | 
			
		||||
        pack_lock
 | 
			
		||||
            .download_mods(&self.mods_folder, self.side)
 | 
			
		||||
            .download_mods(&self.instance_folder.join("mods"), self.side)
 | 
			
		||||
            .await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -118,8 +118,7 @@ impl Data {
 | 
			
		|||
 | 
			
		||||
        if let Some(home_dir) = home_dir {
 | 
			
		||||
            Ok(home_dir)
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
        } else {
 | 
			
		||||
            anyhow::bail!("Unable to locate home directory")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ pub enum FileSource {
 | 
			
		|||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
 | 
			
		||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
 | 
			
		||||
pub enum DownloadSide {
 | 
			
		||||
    Both,
 | 
			
		||||
    Server,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,7 +25,7 @@ struct ManagerGUI {
 | 
			
		|||
    previous_view: ManagerView,
 | 
			
		||||
    profile_edit_settings: ProfileSettings,
 | 
			
		||||
    profile_save_error: Option<String>,
 | 
			
		||||
    current_install_status: ProfileInstallStatus
 | 
			
		||||
    current_install_status: ProfileInstallStatus,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
| 
						 | 
				
			
			@ -41,7 +41,7 @@ enum ManagerView {
 | 
			
		|||
/// The current application view
 | 
			
		||||
struct ProfileSettings {
 | 
			
		||||
    name: String,
 | 
			
		||||
    mods_dir: Option<PathBuf>,
 | 
			
		||||
    instance_dir: Option<PathBuf>,
 | 
			
		||||
    pack_source: String,
 | 
			
		||||
    side: DownloadSide,
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +50,7 @@ impl Default for ProfileSettings {
 | 
			
		|||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            name: Default::default(),
 | 
			
		||||
            mods_dir: Default::default(),
 | 
			
		||||
            instance_dir: Default::default(),
 | 
			
		||||
            pack_source: Default::default(),
 | 
			
		||||
            side: DownloadSide::Client,
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -60,12 +60,15 @@ impl Default for ProfileSettings {
 | 
			
		|||
impl TryFrom<ProfileSettings> for profiles::Profile {
 | 
			
		||||
    type Error = String;
 | 
			
		||||
    fn try_from(value: ProfileSettings) -> Result<Self, Self::Error> {
 | 
			
		||||
        let mods_dir = value
 | 
			
		||||
            .mods_dir
 | 
			
		||||
            .ok_or(format!("A mods directory is required"))?;
 | 
			
		||||
        let instance_dir = value
 | 
			
		||||
            .instance_dir
 | 
			
		||||
            .ok_or(format!("An instance directory is required"))?;
 | 
			
		||||
        if !instance_dir.join("mods").exists() {
 | 
			
		||||
            return Err(format!("Instance folder {} does not seem to contain a mods directory. Are you sure this is a valid instance directory?", instance_dir.display()));
 | 
			
		||||
        }
 | 
			
		||||
        let pack_source = value.pack_source;
 | 
			
		||||
        Ok(profiles::Profile::new(
 | 
			
		||||
            &mods_dir,
 | 
			
		||||
            &instance_dir,
 | 
			
		||||
            profiles::PackSource::from_str(&pack_source)?,
 | 
			
		||||
            value.side,
 | 
			
		||||
        ))
 | 
			
		||||
| 
						 | 
				
			
			@ -81,13 +84,13 @@ impl Default for ManagerView {
 | 
			
		|||
#[derive(Debug, Clone)]
 | 
			
		||||
enum Message {
 | 
			
		||||
    SwitchView(ManagerView),
 | 
			
		||||
    BrowseModsDir,
 | 
			
		||||
    BrowseInstanceDir,
 | 
			
		||||
    EditProfileName(String),
 | 
			
		||||
    EditPackSource(String),
 | 
			
		||||
    SaveProfile,
 | 
			
		||||
    DeleteProfile(String),
 | 
			
		||||
    InstallProfile(String),
 | 
			
		||||
    ProfileInstalled(ProfileInstallStatus)
 | 
			
		||||
    ProfileInstalled(ProfileInstallStatus),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +98,7 @@ enum ProfileInstallStatus {
 | 
			
		|||
    NotStarted,
 | 
			
		||||
    Installing,
 | 
			
		||||
    Success,
 | 
			
		||||
    Error(String)
 | 
			
		||||
    Error(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for ProfileInstallStatus {
 | 
			
		||||
| 
						 | 
				
			
			@ -158,8 +161,8 @@ impl Application for ManagerGUI {
 | 
			
		|||
                        self.profile_edit_settings.name = profile.trim().into();
 | 
			
		||||
                        if let Some(loaded_profile) = loaded_profile {
 | 
			
		||||
                            self.profile_edit_settings.name = profile.into();
 | 
			
		||||
                            self.profile_edit_settings.mods_dir =
 | 
			
		||||
                                Some(loaded_profile.mods_folder.clone());
 | 
			
		||||
                            self.profile_edit_settings.instance_dir =
 | 
			
		||||
                                Some(loaded_profile.instance_folder.clone());
 | 
			
		||||
                            self.profile_edit_settings.pack_source =
 | 
			
		||||
                                loaded_profile.pack_source.to_string();
 | 
			
		||||
                            self.profile_edit_settings.side = loaded_profile.side;
 | 
			
		||||
| 
						 | 
				
			
			@ -172,9 +175,9 @@ impl Application for ManagerGUI {
 | 
			
		|||
                self.current_view = view;
 | 
			
		||||
                Command::none()
 | 
			
		||||
            }
 | 
			
		||||
            Message::BrowseModsDir => {
 | 
			
		||||
                self.profile_edit_settings.mods_dir = rfd::FileDialog::new()
 | 
			
		||||
                    .set_title("Select your mods folder")
 | 
			
		||||
            Message::BrowseInstanceDir => {
 | 
			
		||||
                self.profile_edit_settings.instance_dir = rfd::FileDialog::new()
 | 
			
		||||
                    .set_title("Select your instance folder")
 | 
			
		||||
                    .pick_folder();
 | 
			
		||||
                Command::none()
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -235,24 +238,21 @@ impl Application for ManagerGUI {
 | 
			
		|||
                            let result = profile.install().await;
 | 
			
		||||
                            if let Err(err) = result {
 | 
			
		||||
                                ProfileInstallStatus::Error(format!("{}", err))
 | 
			
		||||
                            }
 | 
			
		||||
                            else {
 | 
			
		||||
                            } else {
 | 
			
		||||
                                ProfileInstallStatus::Success
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                        } else {
 | 
			
		||||
                            ProfileInstallStatus::Error(format!("Profile '{}' doesn't exist", name))
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    Message::ProfileInstalled
 | 
			
		||||
                    Message::ProfileInstalled,
 | 
			
		||||
                )
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            Message::ProfileInstalled(result) => {
 | 
			
		||||
                self.current_install_status = result;
 | 
			
		||||
 | 
			
		||||
                Command::none()
 | 
			
		||||
            },
 | 
			
		||||
            
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -325,8 +325,11 @@ impl ManagerGUI {
 | 
			
		|||
                ]
 | 
			
		||||
                .spacing(5),
 | 
			
		||||
                row![
 | 
			
		||||
                    "Mods directory",
 | 
			
		||||
                    text_input("Mods directory", &profile.mods_folder.display().to_string()),
 | 
			
		||||
                    "Instance folder",
 | 
			
		||||
                    text_input(
 | 
			
		||||
                        "Instance folder",
 | 
			
		||||
                        &profile.instance_folder.display().to_string()
 | 
			
		||||
                    ),
 | 
			
		||||
                ]
 | 
			
		||||
                .spacing(20),
 | 
			
		||||
                row!["Mods to download", text(profile.side),].spacing(5),
 | 
			
		||||
| 
						 | 
				
			
			@ -354,16 +357,17 @@ impl ManagerGUI {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        match &self.current_install_status {
 | 
			
		||||
            ProfileInstallStatus::NotStarted => {},
 | 
			
		||||
            ProfileInstallStatus::NotStarted => {}
 | 
			
		||||
            ProfileInstallStatus::Installing => {
 | 
			
		||||
                profile_view = profile_view.push(text("Installing..."));
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            ProfileInstallStatus::Success => {
 | 
			
		||||
                profile_view = profile_view.push(text("Installed"));
 | 
			
		||||
            },
 | 
			
		||||
            }
 | 
			
		||||
            ProfileInstallStatus::Error(err) => {
 | 
			
		||||
                profile_view = profile_view.push(text(format!("Failed to install profile: {}", err)));
 | 
			
		||||
            },
 | 
			
		||||
                profile_view =
 | 
			
		||||
                    profile_view.push(text(format!("Failed to install profile: {}", err)));
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        profile_view
 | 
			
		||||
| 
						 | 
				
			
			@ -379,8 +383,8 @@ impl ManagerGUI {
 | 
			
		|||
        previous_view: ManagerView,
 | 
			
		||||
        can_edit_name: bool,
 | 
			
		||||
    ) -> Element<Message> {
 | 
			
		||||
        let current_mods_directory_display = match &self.profile_edit_settings.mods_dir {
 | 
			
		||||
            Some(mods_dir) => mods_dir.display().to_string(),
 | 
			
		||||
        let current_instance_directory_display = match &self.profile_edit_settings.instance_dir {
 | 
			
		||||
            Some(instance_dir) => instance_dir.display().to_string(),
 | 
			
		||||
            None => String::from(""),
 | 
			
		||||
        };
 | 
			
		||||
        let mut profile_editor = column![
 | 
			
		||||
| 
						 | 
				
			
			@ -405,12 +409,12 @@ impl ManagerGUI {
 | 
			
		|||
            ]
 | 
			
		||||
            .spacing(5),
 | 
			
		||||
            row![
 | 
			
		||||
                "Mods directory",
 | 
			
		||||
                "Instance directory",
 | 
			
		||||
                text_input(
 | 
			
		||||
                    "Browse for your MC instance's mods directory",
 | 
			
		||||
                    ¤t_mods_directory_display
 | 
			
		||||
                    "Browse for your MC instance directory (contains your mods folder)",
 | 
			
		||||
                    ¤t_instance_directory_display
 | 
			
		||||
                ),
 | 
			
		||||
                button("Browse").on_press(Message::BrowseModsDir)
 | 
			
		||||
                button("Browse").on_press(Message::BrowseInstanceDir)
 | 
			
		||||
            ]
 | 
			
		||||
            .spacing(5),
 | 
			
		||||
            row![
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue