feat(dtmm): Add config file

This commit is contained in:
Lucas Schwiderski 2023-02-22 15:35:32 +01:00
parent 0ac3f84dba
commit 09a6a969a6
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
3 changed files with 113 additions and 16 deletions

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
bitflags = "1.3.2"
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "unicode"] }
clap = { version = "4.0.15", features = ["color", "derive", "std", "cargo", "string", "unicode"] }
color-eyre = "0.6.2"
confy = "0.5.1"
druid = { git = "https://github.com/linebender/druid.git", features = ["im"] }

View file

@ -1,10 +1,16 @@
#![recursion_limit = "256"]
#![feature(let_chains)]
use std::fs;
use std::io::ErrorKind;
use std::path::PathBuf;
use std::sync::Arc;
use clap::command;
use clap::parser::ValueSource;
use clap::value_parser;
use clap::Arg;
use color_eyre::eyre::Context;
use color_eyre::Report;
use color_eyre::Result;
use druid::AppLauncher;
@ -12,6 +18,8 @@ use druid::ExtEventSink;
use druid::SingleUse;
use druid::Target;
use engine::import_mod;
use serde::Deserialize;
use serde::Serialize;
use state::ACTION_FINISH_ADD_MOD;
use tokio::runtime::Runtime;
use tokio::sync::mpsc::UnboundedReceiver;
@ -30,6 +38,12 @@ mod state;
mod theme;
mod widget;
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Config {
data_dir: Option<PathBuf>,
game_dir: Option<PathBuf>,
}
fn work_thread(
event_sink: Arc<RwLock<ExtEventSink>>,
action_queue: Arc<RwLock<UnboundedReceiver<AsyncAction>>>,
@ -76,11 +90,52 @@ fn work_thread(
Ok(())
}
#[cfg(not(arget_os = "windows"))]
fn get_default_config_path() -> String {
let config_dir = std::env::var("XDG_CONFIG_DIR").unwrap_or_else(|_| {
let home = std::env::var("HOME").unwrap_or_else(|_| {
let user = std::env::var("USER").expect("user env variable not set");
format!("/home/{user}")
});
format!("{home}/.config")
});
format!("{config_dir}/dtmm/dtmm.cfg")
}
#[cfg(target_os = "windows")]
fn get_default_config_path() -> String {
let config_dir = std::env::var("APPDATA").expect("appdata env var not set");
format!("{config_dir}\\dtmm\\dtmm.cfg")
}
#[cfg(not(arget_os = "windows"))]
fn get_default_data_dir() -> String {
let data_dir = std::env::var("XDG_DATA_DIR").unwrap_or_else(|_| {
let home = std::env::var("HOME").unwrap_or_else(|_| {
let user = std::env::var("USER").expect("user env variable not set");
format!("/home/{user}")
});
format!("{home}/.local/share")
});
format!("{data_dir}/dtmm")
}
#[cfg(target_os = "windows")]
fn get_default_data_dir() -> String {
let data_dir = std::env::var("APPDATA").expect("appdata env var not set");
format!("{data_dir}\\dtmm")
}
#[tracing::instrument]
#[tokio::main]
async fn main() -> Result<()> {
fn main() -> Result<()> {
color_eyre::install()?;
let default_config_path = get_default_config_path();
tracing::trace!(default_config_path);
let matches = command!()
.arg(Arg::new("oodle").long("oodle").help(
"The oodle library to load. This may either be:\n\
@ -88,6 +143,14 @@ async fn main() -> Result<()> {
- A file path relative to the current working directory.\n\
- An absolute file path.",
))
.arg(
Arg::new("config")
.long("config")
.short('c')
.help("Path to the config file")
.value_parser(value_parser!(PathBuf))
.default_value(&default_config_path),
)
.get_matches();
{
@ -121,7 +184,48 @@ async fn main() -> Result<()> {
oodle_sys::init(matches.get_one::<String>("oodle"));
}
let initial_state = State::new();
let config: Config = {
let path = matches
.get_one::<PathBuf>("config")
.expect("argument missing despite default");
match fs::read(path) {
Ok(data) => {
let data = String::from_utf8(data).wrap_err_with(|| {
format!("config file {} contains invalid UTF-8", path.display())
})?;
serde_sjson::from_str(&data)
.wrap_err_with(|| format!("invalid config file {}", path.display()))?
}
Err(err) if err.kind() == ErrorKind::NotFound => {
if matches.value_source("config") != Some(ValueSource::DefaultValue) {
return Err(err).wrap_err_with(|| {
format!("failed to read config file {}", path.display())
})?;
}
let config = Config {
data_dir: Some(PathBuf::from(get_default_data_dir())),
game_dir: None,
};
{
let data = serde_sjson::to_string(&config)
.wrap_err("failed to serialize default config value")?;
fs::write(&default_config_path, data).wrap_err_with(|| {
format!("failed to write default config to {default_config_path}")
})?;
}
config
}
Err(err) => {
return Err(err)
.wrap_err_with(|| format!("failed to read config file {}", path.display()))?;
}
}
};
let initial_state = State::new(config);
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel();
let delegate = Delegate::new(sender);

View file

@ -9,6 +9,8 @@ use druid::{
};
use tokio::sync::mpsc::UnboundedSender;
use crate::Config;
pub(crate) const ACTION_SELECT_MOD: Selector<usize> = Selector::new("dtmm.action.select-mod");
pub(crate) const ACTION_SELECTED_MOD_UP: Selector = Selector::new("dtmm.action.selected-mod-up");
pub(crate) const ACTION_SELECTED_MOD_DOWN: Selector =
@ -105,26 +107,17 @@ impl State {
#[allow(non_upper_case_globals)]
pub const selected_mod: SelectedModLens = SelectedModLens;
pub fn new() -> Self {
pub fn new(config: Config) -> Self {
let ctx = sdk::Context::new();
let (game_dir, data_dir) = if cfg!(debug_assertions) {
(
std::env::current_dir().expect("PWD is borked").join("data"),
PathBuf::from("/tmp/dtmm"),
)
} else {
(PathBuf::new(), PathBuf::new())
};
Self {
ctx: Arc::new(ctx),
current_view: View::default(),
mods: Vector::new(),
selected_mod_index: None,
is_deployment_in_progress: false,
game_dir: Arc::new(game_dir),
data_dir: Arc::new(data_dir),
game_dir: Arc::new(config.game_dir.unwrap_or_default()),
data_dir: Arc::new(config.data_dir.unwrap_or_default()),
}
}