Skip to content

Commit 609b044

Browse files
configurable config,cache,data dirs
1 parent d91b633 commit 609b044

3 files changed

Lines changed: 227 additions & 46 deletions

File tree

src/gui/mod.rs

Lines changed: 121 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,8 @@ mod toggle_switch;
88

99
use std::collections::{BTreeMap, BTreeSet};
1010
use std::time::{Duration, SystemTime};
11-
use std::{
12-
collections::{HashMap, HashSet},
13-
ops::DerefMut,
14-
path::PathBuf,
15-
};
11+
use std::{collections::{HashMap, HashSet}, fs, ops::DerefMut, path::PathBuf};
12+
use std::path::Path;
1613

1714
use anyhow::{anyhow, Context, Result};
1815
use eframe::egui::{Button, CollapsingHeader, RichText, Visuals};
@@ -34,6 +31,9 @@ use crate::Dirs;
3431
use crate::{
3532
integrate::uninstall,
3633
is_drg_pak,
34+
is_valid_directory,
35+
copy_directory_contents,
36+
clear_directory,
3737
providers::{
3838
ApprovalStatus, FetchProgress, ModInfo, ModSpecification, ModStore, ModioTags,
3939
ProviderFactory, RequiredStatus,
@@ -928,26 +928,28 @@ impl App {
928928
});
929929
ui.end_row();
930930

931-
let config_dir = &self.state.dirs.config_dir;
932-
ui.label("Config directory:");
933-
if ui.link(config_dir.display().to_string()).clicked() {
934-
opener::open(config_dir).ok();
935-
}
936-
ui.end_row();
931+
let edit_directory_field = |ui: &mut egui::Ui, label: &str, path: &mut String, err: &mut Option<String>| {
932+
ui.horizontal(|ui| {
933+
ui.label(label);
934+
ui.text_edit_singleline(path);
937935

938-
let cache_dir = &self.state.dirs.cache_dir;
939-
ui.label("Cache directory:");
940-
if ui.link(cache_dir.display().to_string()).clicked() {
941-
opener::open(cache_dir).ok();
942-
}
943-
ui.end_row();
936+
if ui.button("Browse").clicked() {
937+
if let Some(selected_path) = rfd::FileDialog::new().pick_folder() {
938+
*path = selected_path.to_string_lossy().to_string();
939+
*err = None;
940+
}
941+
}
944942

945-
let data_dir = &self.state.dirs.data_dir;
946-
ui.label("Data directory:");
947-
if ui.link(data_dir.display().to_string()).clicked() {
948-
opener::open(data_dir).ok();
949-
}
950-
ui.end_row();
943+
if let Some(err_msg) = err {
944+
ui.label(&*err_msg);
945+
}
946+
ui.end_row();
947+
});
948+
};
949+
950+
edit_directory_field(ui, "Config Directory:", &mut window.config_dir, &mut window.config_dir_err);
951+
edit_directory_field(ui, "Cache Directory:", &mut window.cache_dir, &mut window.cache_dir_err);
952+
edit_directory_field(ui, "Data Directory:", &mut window.data_dir, &mut window.data_dir_err);
951953

952954
ui.label("GUI theme:");
953955
ui.horizontal(|ui| {
@@ -980,24 +982,91 @@ impl App {
980982
}
981983
});
982984

983-
ui.with_layout(egui::Layout::right_to_left(Align::TOP), |ui| {
984-
if ui.add_enabled(window.drg_pak_path_err.is_none(), egui::Button::new("save")).clicked() {
985+
ui.with_layout(egui::Layout::right_to_left(egui::Align::TOP), |ui| {
986+
let can_save = window.drg_pak_path_err.is_none()
987+
&& window.config_dir_err.is_none()
988+
&& window.cache_dir_err.is_none()
989+
&& window.data_dir_err.is_none();
990+
991+
if ui.add_enabled(can_save, egui::Button::new("save")).clicked() {
985992
try_save = true;
986993
}
994+
987995
if let Some(error) = &window.drg_pak_path_err {
988996
ui.colored_label(ui.visuals().error_fg_color, error);
989997
}
998+
if let Some(error) = &window.config_dir_err {
999+
ui.colored_label(ui.visuals().error_fg_color, error);
1000+
}
1001+
if let Some(error) = &window.cache_dir_err {
1002+
ui.colored_label(ui.visuals().error_fg_color, error);
1003+
}
1004+
if let Some(error) = &window.data_dir_err {
1005+
ui.colored_label(ui.visuals().error_fg_color, error);
1006+
}
9901007
});
9911008

9921009
});
9931010
if try_save {
994-
if let Err(e) = is_drg_pak(&window.drg_pak_path).context("Is not valid DRG pak") {
1011+
let mut has_error = false;
1012+
1013+
if let Err(e) = is_drg_pak(&window.drg_pak_path) {
9951014
window.drg_pak_path_err = Some(e.to_string());
996-
} else {
997-
self.state.config.drg_pak_path = Some(PathBuf::from(
998-
self.settings_window.take().unwrap().drg_pak_path,
999-
));
1000-
self.state.config.save().unwrap();
1015+
has_error = true;
1016+
}
1017+
if let Err(e) = is_valid_directory(&window.config_dir) {
1018+
window.config_dir_err = Some(e);
1019+
has_error = true;
1020+
}
1021+
if let Err(e) = is_valid_directory(&window.cache_dir) {
1022+
window.cache_dir_err = Some(e);
1023+
has_error = true;
1024+
}
1025+
if let Err(e) = is_valid_directory(&window.data_dir) {
1026+
window.data_dir_err = Some(e);
1027+
has_error = true;
1028+
}
1029+
1030+
if !has_error {
1031+
if let Some(old_config_dir) = &self.state.config.config_dir {
1032+
if old_config_dir.to_string_lossy() != window.config_dir {
1033+
if let Err(e) = copy_directory_contents(old_config_dir, Path::new(&window.config_dir)) {
1034+
window.config_dir_err = Some(format!("Failed to copy config directory: {}", e));
1035+
has_error = true;
1036+
}
1037+
}
1038+
}
1039+
1040+
if let Some(old_data_dir) = &self.state.config.data_dir {
1041+
if old_data_dir.to_string_lossy() != window.data_dir {
1042+
if let Err(e) = copy_directory_contents(old_data_dir, Path::new(&window.data_dir)) {
1043+
window.data_dir_err = Some(format!("Failed to copy data directory: {}", e));
1044+
has_error = true;
1045+
}
1046+
}
1047+
}
1048+
1049+
if let Some(old_cache_dir) = &self.state.config.cache_dir {
1050+
if old_cache_dir.to_string_lossy() != window.cache_dir {
1051+
if let Err(e) = fs::create_dir_all(&window.cache_dir) {
1052+
window.cache_dir_err = Some(format!("Failed to create new cache directory: {}", e));
1053+
has_error = true;
1054+
}
1055+
else if let Err(e) = clear_directory(old_cache_dir) {
1056+
window.cache_dir_err = Some(format!("Failed to clear cache directory: {}", e));
1057+
has_error = true;
1058+
}
1059+
}
1060+
}
1061+
1062+
if !has_error {
1063+
self.state.config.drg_pak_path = Some(PathBuf::from(&window.drg_pak_path));
1064+
self.state.config.config_dir = Some(PathBuf::from(&window.config_dir));
1065+
self.state.config.cache_dir = Some(PathBuf::from(&window.cache_dir));
1066+
self.state.config.data_dir = Some(PathBuf::from(&window.data_dir));
1067+
1068+
self.state.config.save().unwrap();
1069+
}
10011070
}
10021071
} else if !open {
10031072
self.settings_window = None;
@@ -1483,19 +1552,33 @@ impl WindowProviderParameters {
14831552
struct WindowSettings {
14841553
drg_pak_path: String,
14851554
drg_pak_path_err: Option<String>,
1555+
config_dir: String,
1556+
config_dir_err: Option<String>,
1557+
cache_dir: String,
1558+
cache_dir_err: Option<String>,
1559+
data_dir: String,
1560+
data_dir_err: Option<String>,
14861561
}
14871562

14881563
impl WindowSettings {
14891564
fn new(state: &State) -> Self {
1490-
let path = state
1491-
.config
1492-
.drg_pak_path
1493-
.as_ref()
1494-
.map(|p| p.to_string_lossy().to_string())
1495-
.unwrap_or_default();
14961565
Self {
1497-
drg_pak_path: path,
1566+
drg_pak_path: state.config.drg_pak_path.as_ref()
1567+
.map(|p| p.to_string_lossy().to_string())
1568+
.unwrap_or_default(),
14981569
drg_pak_path_err: None,
1570+
config_dir: state.config.config_dir.as_ref()
1571+
.map(|p| p.to_string_lossy().to_string())
1572+
.unwrap_or_default(),
1573+
config_dir_err: None,
1574+
cache_dir: state.config.cache_dir.as_ref()
1575+
.map(|p| p.to_string_lossy().to_string())
1576+
.unwrap_or_default(),
1577+
cache_dir_err: None,
1578+
data_dir: state.config.data_dir.as_ref()
1579+
.map(|p| p.to_string_lossy().to_string())
1580+
.unwrap_or_default(),
1581+
data_dir_err: None,
14991582
}
15001583
}
15011584
}

src/lib.rs

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ pub mod mod_lints;
77
pub mod providers;
88
pub mod state;
99

10-
use std::io::{Cursor, Read};
10+
use std::io::{self, Cursor, Read};
1111
use std::str::FromStr;
12-
use std::{
13-
collections::HashSet,
14-
path::{Path, PathBuf},
12+
use std::{collections::HashSet, path::{Path, PathBuf}};
13+
use std::fs::{
14+
self,
15+
copy,
16+
create_dir_all,
1517
};
1618

1719
use anyhow::{Context, Result};
@@ -87,6 +89,63 @@ pub fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, data: C) -> Result<()
8789
.with_context(|| format!("Could not write to file {}", path.as_ref().display()))
8890
}
8991

92+
pub fn is_valid_directory(path: &str) -> Result<(), String> {
93+
let path = Path::new(path);
94+
95+
if !path.exists() {
96+
return Err("Path does not exist.".to_string());
97+
}
98+
if !path.is_dir() {
99+
return Err("Path is not a directory.".to_string());
100+
}
101+
102+
match fs::metadata(path) {
103+
Ok(metadata) => {
104+
if !metadata.permissions().readonly() {
105+
Ok(())
106+
} else {
107+
Err("Directory is not writable.".to_string())
108+
}
109+
}
110+
Err(_) => Err("Unable to access directory metadata.".to_string()),
111+
}
112+
}
113+
114+
pub fn copy_directory_contents(src: &Path, dest: &Path) -> io::Result<()> {
115+
if src.is_dir() {
116+
create_dir_all(dest)?;
117+
118+
for entry in fs::read_dir(src)? {
119+
let entry = entry?;
120+
let path = entry.path();
121+
let dest_path = dest.join(entry.file_name());
122+
123+
if path.is_dir() {
124+
copy_directory_contents(&path, &dest_path)?;
125+
} else {
126+
copy(&path, &dest_path)?;
127+
}
128+
}
129+
}
130+
Ok(())
131+
}
132+
133+
pub fn clear_directory(path: &Path) -> io::Result<()> {
134+
if path.is_dir() {
135+
for entry in fs::read_dir(path)? {
136+
let entry = entry?;
137+
let path = entry.path();
138+
139+
if path.is_dir() {
140+
fs::remove_dir_all(&path)?;
141+
} else {
142+
fs::remove_file(&path)?;
143+
}
144+
}
145+
}
146+
Ok(())
147+
}
148+
90149
pub fn is_drg_pak<P: AsRef<Path>>(path: P) -> Result<()> {
91150
let mut reader = std::io::BufReader::new(open_file(path)?);
92151
let pak = repak::PakBuilder::new().reader(&mut reader)?;

0 commit comments

Comments
 (0)