From ef7e4317791ab9b8c0a156865e426765da14809a Mon Sep 17 00:00:00 2001 From: Markus Pettersson Date: Sun, 29 Oct 2023 19:06:52 +0100 Subject: [PATCH] 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 --- src/config.rs | 80 +++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/src/config.rs b/src/config.rs index 35331ba..15821b4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,81 +1,67 @@ +use serde::Deserialize; use std::env; use std::fs; use std::path::PathBuf; -use serde::Deserialize; use crate::error::FF2MpvError; #[derive(Deserialize)] +#[serde(default)] pub struct Config { - #[serde(default = "default_player_command")] pub player_command: String, - - #[serde(default = "default_player_args")] pub player_args: Vec, } impl Default for Config { fn default() -> Self { Self { - player_command: default_player_command(), - player_args: default_player_args(), + player_command: "mpv".to_owned(), + player_args: vec![], } } } impl Config { + pub const FILENAME: &str = "ff2mpv-rust.json"; + pub fn build() -> Self { - if let Ok(config) = Config::parse_config_file() { - config - } else { - Config::default() - } + Config::parse_config_file().unwrap_or_default() } pub fn parse_config_file() -> Result { - let config_path = Config::get_config_location(); - if !config_path.exists() { - return Err(FF2MpvError::NoConfig); - } - + let config_path = Config::get_config_location()?; let string = fs::read_to_string(config_path)?; let config = serde_json::from_str(&string)?; Ok(config) } - #[cfg(target_family = "unix")] - fn get_config_location() -> PathBuf { - let mut path = PathBuf::new(); - - if let Ok(home) = env::var("XDG_CONFIG_HOME") { - path.push(home); - } else if let Ok(home) = env::var("HOME") { - path.push(home); - path.push(".config"); + /// Returns a *valid* config path, i.e. it exists and is readable by the + /// current user. If the config path is not valid, an [`FF2MpvError`] is + /// returned. + fn get_config_location() -> Result { + let config_dir = if cfg!(target_family = "unix") { + if let Ok(home) = env::var("XDG_CONFIG_HOME") { + PathBuf::from(home) + } else if let Ok(home) = env::var("HOME") { + PathBuf::from(home).join(".config") + } else { + PathBuf::from("/etc") + } + } else if cfg!(target_family = "windows") { + env::var("APPDATA") + .map_err(|_| FF2MpvError::NoConfig)? + .into() } else { - path.push("/etc"); + unimplemented!("This platform is not supported") + }; + + let path = config_dir.join(Config::FILENAME); + 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)), } - - path.push("ff2mpv-rust.json"); - path - } - - #[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 { - vec![] -}