Clean up config.rs

Some refactoring to `config.rs` with the aim to:

* Increase type safety
* Reduce duplication
* Leverage `std::result::Result` & `std::path::PathBuf` APIs

To achieve these goals, the following was done:

* `get_config_file` propagates an `FF2MpvError` if the config file doesn't exist/is invalid
* Always type check `unix` & `windows` code by using `cfg!` expressions over
`#[cfg()]` attributes. This also removed some duplicated lines of code
* Move `#[serde(default)]` to `Config` struct instead of every struct
member: https://serde.rs/container-attrs.html#default
This commit is contained in:
Markus Pettersson 2023-10-29 19:06:52 +01:00
parent 4fcc2ac31f
commit ef7e431779

View file

@ -1,81 +1,67 @@
use serde::Deserialize;
use std::env; use std::env;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize;
use crate::error::FF2MpvError; use crate::error::FF2MpvError;
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(default)]
pub struct Config { pub struct Config {
#[serde(default = "default_player_command")]
pub player_command: String, pub player_command: String,
#[serde(default = "default_player_args")]
pub player_args: Vec<String>, pub player_args: Vec<String>,
} }
impl Default for Config { impl Default for Config {
fn default() -> Self { fn default() -> Self {
Self { Self {
player_command: default_player_command(), player_command: "mpv".to_owned(),
player_args: default_player_args(), player_args: vec![],
} }
} }
} }
impl Config { impl Config {
pub const FILENAME: &str = "ff2mpv-rust.json";
pub fn build() -> Self { pub fn build() -> Self {
if let Ok(config) = Config::parse_config_file() { Config::parse_config_file().unwrap_or_default()
config
} else {
Config::default()
}
} }
pub fn parse_config_file() -> Result<Self, FF2MpvError> { pub fn parse_config_file() -> Result<Self, FF2MpvError> {
let config_path = Config::get_config_location(); let config_path = Config::get_config_location()?;
if !config_path.exists() {
return Err(FF2MpvError::NoConfig);
}
let string = fs::read_to_string(config_path)?; let string = fs::read_to_string(config_path)?;
let config = serde_json::from_str(&string)?; let config = serde_json::from_str(&string)?;
Ok(config) Ok(config)
} }
#[cfg(target_family = "unix")] /// Returns a *valid* config path, i.e. it exists and is readable by the
fn get_config_location() -> PathBuf { /// current user. If the config path is not valid, an [`FF2MpvError`] is
let mut path = PathBuf::new(); /// returned.
fn get_config_location() -> Result<PathBuf, FF2MpvError> {
let config_dir = if cfg!(target_family = "unix") {
if let Ok(home) = env::var("XDG_CONFIG_HOME") { if let Ok(home) = env::var("XDG_CONFIG_HOME") {
path.push(home); PathBuf::from(home)
} else if let Ok(home) = env::var("HOME") { } else if let Ok(home) = env::var("HOME") {
path.push(home); PathBuf::from(home).join(".config")
path.push(".config");
} else { } else {
path.push("/etc"); PathBuf::from("/etc")
} }
} else if cfg!(target_family = "windows") {
env::var("APPDATA")
.map_err(|_| FF2MpvError::NoConfig)?
.into()
} else {
unimplemented!("This platform is not supported")
};
path.push("ff2mpv-rust.json"); let path = config_dir.join(Config::FILENAME);
path match path.try_exists() {
Ok(true) => Ok(path),
// Broken symbolic link to config file.
Ok(false) => Err(FF2MpvError::NoConfig),
Err(err) => Err(FF2MpvError::IOError(err)),
} }
#[cfg(target_family = "windows")]
fn get_config_location() -> PathBuf {
let mut path = PathBuf::new();
let appdata = env::var("APPDATA").unwrap();
path.push(appdata);
path.push("ff2mpv-rust.json");
path
} }
} }
fn default_player_command() -> String {
"mpv".to_owned()
}
fn default_player_args() -> Vec<String> {
vec![]
}