From d137ad0aeb30d0a3b22fb710020f3d351a200332 Mon Sep 17 00:00:00 2001 From: Buckminsterfullerene02 Date: Sun, 25 Feb 2024 00:17:31 +0000 Subject: [PATCH 1/2] configurable config,cache,data dirs Remove config/data dir config as its not necessary fmt idk --- src/gui/mod.rs | 112 ++++++++++++++++++++++++++++++++--------------- src/lib.rs | 60 ++++++++++++++++++++++++- src/state/mod.rs | 45 ++++++++++++++++--- 3 files changed, 175 insertions(+), 42 deletions(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index ad59fd83..0d4cab0b 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -10,11 +10,12 @@ use std::collections::{BTreeMap, BTreeSet}; use std::time::{Duration, SystemTime}; use std::{ collections::{HashMap, HashSet}, + fs, ops::DerefMut, path::PathBuf, }; -use anyhow::{anyhow, Context, Result}; +use anyhow::{anyhow, Result}; use eframe::egui::{Button, CollapsingHeader, RichText, Visuals}; use eframe::epaint::{Pos2, Vec2}; use eframe::{ @@ -32,8 +33,9 @@ use tracing::{debug, trace}; use crate::mod_lints::{LintId, LintReport, SplitAssetPair}; use crate::Dirs; use crate::{ + clear_directory, integrate::uninstall, - is_drg_pak, + is_drg_pak, is_valid_directory, providers::{ ApprovalStatus, FetchProgress, ModInfo, ModSpecification, ModStore, ModioTags, ProviderFactory, RequiredStatus, @@ -928,26 +930,26 @@ impl App { }); ui.end_row(); - let config_dir = &self.state.dirs.config_dir; - ui.label("Config directory:"); - if ui.link(config_dir.display().to_string()).clicked() { - opener::open(config_dir).ok(); - } - ui.end_row(); + let edit_directory_field = |ui: &mut egui::Ui, label: &str, path: &mut String, err: &mut Option| { + ui.horizontal(|ui| { + ui.label(label); + ui.text_edit_singleline(path); - let cache_dir = &self.state.dirs.cache_dir; - ui.label("Cache directory:"); - if ui.link(cache_dir.display().to_string()).clicked() { - opener::open(cache_dir).ok(); - } - ui.end_row(); + if ui.button("Browse").clicked() { + if let Some(selected_path) = rfd::FileDialog::new().pick_folder() { + *path = selected_path.to_string_lossy().to_string(); + *err = None; + } + } - let data_dir = &self.state.dirs.data_dir; - ui.label("Data directory:"); - if ui.link(data_dir.display().to_string()).clicked() { - opener::open(data_dir).ok(); - } - ui.end_row(); + if let Some(err_msg) = err { + ui.label(&*err_msg); + } + ui.end_row(); + }); + }; + + edit_directory_field(ui, "Cache Directory:", &mut window.cache_dir, &mut window.cache_dir_err); ui.label("GUI theme:"); ui.horizontal(|ui| { @@ -980,24 +982,56 @@ impl App { } }); - ui.with_layout(egui::Layout::right_to_left(Align::TOP), |ui| { - if ui.add_enabled(window.drg_pak_path_err.is_none(), egui::Button::new("save")).clicked() { + ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| { + let can_save = window.drg_pak_path_err.is_none() + && window.cache_dir_err.is_none(); + + if ui.add_enabled(can_save, egui::Button::new("save")).clicked() { try_save = true; } + if let Some(error) = &window.drg_pak_path_err { ui.colored_label(ui.visuals().error_fg_color, error); } + if let Some(error) = &window.cache_dir_err { + ui.colored_label(ui.visuals().error_fg_color, error); + } }); }); if try_save { - if let Err(e) = is_drg_pak(&window.drg_pak_path).context("Is not valid DRG pak") { + let mut has_error = false; + + if let Err(e) = is_drg_pak(&window.drg_pak_path) { window.drg_pak_path_err = Some(e.to_string()); - } else { - self.state.config.drg_pak_path = Some(PathBuf::from( - self.settings_window.take().unwrap().drg_pak_path, - )); - self.state.config.save().unwrap(); + has_error = true; + } + if let Err(e) = is_valid_directory(&window.cache_dir) { + window.cache_dir_err = Some(e); + has_error = true; + } + + if !has_error { + if let Some(old_cache_dir) = &self.state.config.cache_dir { + if old_cache_dir.to_string_lossy() != window.cache_dir { + if let Err(e) = fs::create_dir_all(&window.cache_dir) { + window.cache_dir_err = + Some(format!("Failed to create new cache directory: {}", e)); + has_error = true; + } else if let Err(e) = clear_directory(old_cache_dir) { + window.cache_dir_err = + Some(format!("Failed to clear cache directory: {}", e)); + has_error = true; + } + } + } + + if !has_error { + self.state.config.drg_pak_path = Some(PathBuf::from(&window.drg_pak_path)); + self.state.config.cache_dir = Some(PathBuf::from(&window.cache_dir)); + + self.state.config.save().unwrap(); + } } } else if !open { self.settings_window = None; @@ -1483,19 +1517,27 @@ impl WindowProviderParameters { struct WindowSettings { drg_pak_path: String, drg_pak_path_err: Option, + cache_dir: String, + cache_dir_err: Option, } impl WindowSettings { fn new(state: &State) -> Self { - let path = state - .config - .drg_pak_path - .as_ref() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_default(); Self { - drg_pak_path: path, + drg_pak_path: state + .config + .drg_pak_path + .as_ref() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(), drg_pak_path_err: None, + cache_dir: state + .config + .cache_dir + .as_ref() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_default(), + cache_dir_err: None, } } } diff --git a/src/lib.rs b/src/lib.rs index 3b7307d1..a951cf88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,8 @@ pub mod mod_lints; pub mod providers; pub mod state; -use std::io::{Cursor, Read}; +use std::fs::{self, copy, create_dir_all}; +use std::io::{self, Cursor, Read}; use std::str::FromStr; use std::{ collections::HashSet, @@ -87,6 +88,63 @@ pub fn write_file, C: AsRef<[u8]>>(path: P, data: C) -> Result<() .with_context(|| format!("Could not write to file {}", path.as_ref().display())) } +pub fn is_valid_directory(path: &str) -> Result<(), String> { + let path = Path::new(path); + + if !path.exists() { + return Err("Path does not exist.".to_string()); + } + if !path.is_dir() { + return Err("Path is not a directory.".to_string()); + } + + match fs::metadata(path) { + Ok(metadata) => { + if !metadata.permissions().readonly() { + Ok(()) + } else { + Err("Directory is not writable.".to_string()) + } + } + Err(_) => Err("Unable to access directory metadata.".to_string()), + } +} + +pub fn copy_directory_contents(src: &Path, dest: &Path) -> io::Result<()> { + if src.is_dir() { + create_dir_all(dest)?; + + for entry in fs::read_dir(src)? { + let entry = entry?; + let path = entry.path(); + let dest_path = dest.join(entry.file_name()); + + if path.is_dir() { + copy_directory_contents(&path, &dest_path)?; + } else { + copy(&path, &dest_path)?; + } + } + } + Ok(()) +} + +pub fn clear_directory(path: &Path) -> io::Result<()> { + if path.is_dir() { + for entry in fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + } + } + Ok(()) +} + pub fn is_drg_pak>(path: P) -> Result<()> { let mut reader = std::io::BufReader::new(open_file(path)?); let pak = repak::PakBuilder::new().reader(&mut reader)?; diff --git a/src/state/mod.rs b/src/state/mod.rs index e6a641b5..d37bd672 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -326,11 +326,21 @@ impl ModData!["0.1.0"] { #[obake::versioned] #[obake(version("0.0.0"))] +#[obake(version("0.1.0"))] #[derive(Debug, Serialize, Deserialize)] pub struct Config { + #[obake(cfg("0.0.0"))] + #[obake(cfg("0.1.0"))] pub provider_parameters: HashMap>, + #[obake(cfg("0.0.0"))] + #[obake(cfg("0.1.0"))] pub drg_pak_path: Option, + #[obake(cfg("0.0.0"))] + #[obake(cfg("0.1.0"))] pub gui_theme: Option, + + #[obake(cfg("0.1.0"))] + pub cache_dir: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -338,6 +348,8 @@ pub struct Config { pub enum VersionAnnotatedConfig { #[serde(rename = "0.0.0")] V0_0_0(Config!["0.0.0"]), + #[serde(rename = "0.1.0")] + V0_1_0(Config!["0.1.0"]), #[serde(other)] Unsupported, } @@ -357,17 +369,20 @@ impl Default for MaybeVersionedConfig { impl Default for VersionAnnotatedConfig { fn default() -> Self { - VersionAnnotatedConfig::V0_0_0(Default::default()) + VersionAnnotatedConfig::V0_1_0(Default::default()) } } impl Deref for VersionAnnotatedConfig { - type Target = Config!["0.0.0"]; + type Target = Config_v0_1_0; fn deref(&self) -> &Self::Target { match self { - VersionAnnotatedConfig::V0_0_0(cfg) => cfg, - VersionAnnotatedConfig::Unsupported => unreachable!(), + VersionAnnotatedConfig::V0_0_0(_) => unreachable!("Attempted to deref a legacy config"), + VersionAnnotatedConfig::V0_1_0(ref cfg) => cfg, + VersionAnnotatedConfig::Unsupported => { + unreachable!("Attempted to deref an unsupported config") + } } } } @@ -375,20 +390,37 @@ impl Deref for VersionAnnotatedConfig { impl DerefMut for VersionAnnotatedConfig { fn deref_mut(&mut self) -> &mut Self::Target { match self { - VersionAnnotatedConfig::V0_0_0(cfg) => cfg, + VersionAnnotatedConfig::V0_0_0(_) => unreachable!(), + VersionAnnotatedConfig::V0_1_0(cfg) => cfg, VersionAnnotatedConfig::Unsupported => unreachable!(), } } } -impl Default for Config!["0.0.0"] { +impl Default for Config!["0.1.0"] { fn default() -> Self { + let default_dirs = Dirs::default_xdg().expect("Failed to get default directories"); + Self { provider_parameters: Default::default(), drg_pak_path: DRGInstallation::find() .as_ref() .map(DRGInstallation::main_pak), gui_theme: None, + cache_dir: Some(default_dirs.cache_dir), + } + } +} + +impl From for Config!["0.1.0"] { + fn from(legacy: Config!("0.0.0")) -> Self { + let default_dirs = Dirs::default_xdg().expect("Failed to get default directories"); + + Self { + provider_parameters: legacy.provider_parameters, + drg_pak_path: legacy.drg_pak_path, + gui_theme: legacy.gui_theme, + cache_dir: Some(default_dirs.cache_dir), } } } @@ -432,6 +464,7 @@ fn read_config_or_default(config_path: &PathBuf) -> Result match v { + VersionAnnotatedConfig::V0_1_0(v) => VersionAnnotatedConfig::V0_1_0(v), VersionAnnotatedConfig::V0_0_0(v) => VersionAnnotatedConfig::V0_0_0(v), VersionAnnotatedConfig::Unsupported => bail!("unsupported config version"), }, From c3dfeea2339c62a45b95f9303c0bb6bbbbf7a4e9 Mon Sep 17 00:00:00 2001 From: Buckminsterfullerene02 Date: Sun, 25 Feb 2024 00:55:55 +0000 Subject: [PATCH 2/2] fix versioning and incorrect gui layout --- src/gui/mod.rs | 2 +- src/lib.rs | 3 ++- src/state/mod.rs | 39 ++++++--------------------------------- 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 1f495a28..b3aef5fa 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -932,8 +932,8 @@ impl App { ui.end_row(); let edit_directory_field = |ui: &mut egui::Ui, label: &str, path: &mut String, err: &mut Option| { + ui.label(label); ui.horizontal(|ui| { - ui.label(label); ui.text_edit_singleline(path); if ui.button("Browse").clicked() { diff --git a/src/lib.rs b/src/lib.rs index b18bbe22..74a4ed4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,12 +7,13 @@ pub mod mod_lints; pub mod providers; pub mod state; +use std::fs::{self, copy, create_dir_all}; use std::io::{Cursor, Read}; use std::ops::Deref; -use std::fs::{self, copy, create_dir_all}; use std::str::FromStr; use std::{ collections::HashSet, + io, path::{Path, PathBuf}, }; diff --git a/src/state/mod.rs b/src/state/mod.rs index f6ffe492..c393d88c 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -330,17 +330,10 @@ fn is_false(value: &bool) -> bool { #[obake::versioned] #[obake(version("0.0.0"))] -#[obake(version("0.1.0"))] #[derive(Debug, Serialize, Deserialize)] pub struct Config { - #[obake(cfg("0.0.0"))] - #[obake(cfg("0.1.0"))] pub provider_parameters: HashMap>, - #[obake(cfg("0.0.0"))] - #[obake(cfg("0.1.0"))] pub drg_pak_path: Option, - #[obake(cfg("0.0.0"))] - #[obake(cfg("0.1.0"))] pub gui_theme: Option, #[serde(default, skip_serializing_if = "is_false")] pub disable_fix_exploding_gas: bool, @@ -352,8 +345,6 @@ pub struct Config { pub enum VersionAnnotatedConfig { #[serde(rename = "0.0.0")] V0_0_0(Config!["0.0.0"]), - #[serde(rename = "0.1.0")] - V0_1_0(Config!["0.1.0"]), #[serde(other)] Unsupported, } @@ -373,20 +364,17 @@ impl Default for MaybeVersionedConfig { impl Default for VersionAnnotatedConfig { fn default() -> Self { - VersionAnnotatedConfig::V0_1_0(Default::default()) + VersionAnnotatedConfig::V0_0_0(Default::default()) } } impl Deref for VersionAnnotatedConfig { - type Target = Config_v0_1_0; + type Target = Config!["0.0.0"]; fn deref(&self) -> &Self::Target { match self { - VersionAnnotatedConfig::V0_0_0(_) => unreachable!("Attempted to deref a legacy config"), - VersionAnnotatedConfig::V0_1_0(ref cfg) => cfg, - VersionAnnotatedConfig::Unsupported => { - unreachable!("Attempted to deref an unsupported config") - } + VersionAnnotatedConfig::V0_0_0(cfg) => cfg, + VersionAnnotatedConfig::Unsupported => unreachable!(), } } } @@ -394,14 +382,13 @@ impl Deref for VersionAnnotatedConfig { impl DerefMut for VersionAnnotatedConfig { fn deref_mut(&mut self) -> &mut Self::Target { match self { - VersionAnnotatedConfig::V0_0_0(_) => unreachable!(), - VersionAnnotatedConfig::V0_1_0(cfg) => cfg, + VersionAnnotatedConfig::V0_0_0(cfg) => cfg, VersionAnnotatedConfig::Unsupported => unreachable!(), } } } -impl Default for Config!["0.1.0"] { +impl Default for Config!["0.0.0"] { fn default() -> Self { let default_dirs = Dirs::default_xdg().expect("Failed to get default directories"); @@ -425,19 +412,6 @@ impl From<&VersionAnnotatedConfig> for MetaConfig { } } -impl From for Config!["0.1.0"] { - fn from(legacy: Config!("0.0.0")) -> Self { - let default_dirs = Dirs::default_xdg().expect("Failed to get default directories"); - - Self { - provider_parameters: legacy.provider_parameters, - drg_pak_path: legacy.drg_pak_path, - gui_theme: legacy.gui_theme, - cache_dir: Some(default_dirs.cache_dir), - } - } -} - pub struct State { pub dirs: Dirs, pub config: ConfigWrapper, @@ -477,7 +451,6 @@ fn read_config_or_default(config_path: &PathBuf) -> Result match v { - VersionAnnotatedConfig::V0_1_0(v) => VersionAnnotatedConfig::V0_1_0(v), VersionAnnotatedConfig::V0_0_0(v) => VersionAnnotatedConfig::V0_0_0(v), VersionAnnotatedConfig::Unsupported => bail!("unsupported config version"), },