Skip to content

Commit

Permalink
refactor(config): restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
micielski committed Jun 24, 2024
1 parent 07f3414 commit c19cd77
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 146 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ fuzzy-matcher = "0.3.7"
clap = { version = "4.5.6", features = ["derive"] }
base64 = "0.22"
xdg = "2.5"
url = "2.5"
url = { version = "2.5", features = ["serde"] }
toml = "0.8"

# Async
Expand Down
19 changes: 13 additions & 6 deletions rm-config/src/keymap.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
use std::{collections::HashMap, marker::PhantomData};
use std::{collections::HashMap, marker::PhantomData, path::PathBuf, sync::OnceLock};

use anyhow::Result;
use crossterm::event::{KeyCode, KeyModifiers};
use serde::{
de::{self, SeqAccess, Visitor},
de::{self, Visitor},
Deserialize, Serialize,
};
use toml::Table;

use crate::{utils, KEYMAP_CONFIG_FILENAME};
use crate::utils;
use rm_shared::action::Action;

#[derive(Serialize, Deserialize)]
pub struct Keymap {
pub(crate) struct KeymapConfig {
general: General<GeneralAction>,
torrents_tab: TorrentsTab<TorrentsAction>,
}
Expand Down Expand Up @@ -241,11 +241,13 @@ impl Default for KeyModifier {
}
}

impl Keymap {
impl KeymapConfig {
pub const FILENAME: &'static str = "keymap.toml";

pub fn init() -> Result<Self> {
let table = {
// TODO: handle errors or there will be hell to pay
if let Ok(table) = utils::fetch_config_table(KEYMAP_CONFIG_FILENAME) {
if let Ok(table) = utils::fetch_config(Self::FILENAME) {
table
} else {
todo!();
Expand Down Expand Up @@ -273,4 +275,9 @@ impl Keymap {
let config = toml::from_str(&config_string)?;
Ok(config)
}

pub fn path() -> &'static PathBuf {
static PATH: OnceLock<PathBuf> = OnceLock::new();
PATH.get_or_init(|| utils::get_config_path(Self::FILENAME))
}
}
154 changes: 24 additions & 130 deletions rm-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,149 +1,43 @@
mod keymap;
mod main_config;
mod utils;

use std::{collections::HashMap, path::PathBuf, sync::OnceLock};
use std::{collections::HashMap, path::PathBuf};

use anyhow::{bail, Context, Result};
use anyhow::Result;
use crossterm::event::{KeyCode, KeyModifiers};
use ratatui::style::Color;
use rm_shared::action::Action;
use serde::{Deserialize, Serialize};
use toml::Table;
use xdg::BaseDirectories;
use keymap::KeymapConfig;
use main_config::MainConfig;

use crate::utils::put_config;
use keymap::Keymap;
use rm_shared::action::Action;

#[derive(Serialize, Deserialize)]
pub struct Config {
pub connection: Connection,
pub general: General,
#[serde(skip)]
pub keymap: Option<HashMap<(KeyCode, KeyModifiers), Action>>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct General {
#[serde(default)]
pub auto_hide: bool,
#[serde(default = "default_accent_color")]
pub accent_color: Color,
#[serde(default = "default_beginner_mode")]
pub beginner_mode: bool,
}

fn default_accent_color() -> Color {
Color::LightMagenta
}

fn default_beginner_mode() -> bool {
true
pub general: main_config::General,
pub connection: main_config::Connection,
pub keymap: HashMap<(KeyCode, KeyModifiers), Action>,
pub directories: Directories,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Connection {
pub username: Option<String>,
pub password: Option<String>,
pub url: String,
}

const DEFAULT_CONFIG: &str = include_str!("../defaults/config.toml");
static XDG_DIRS: OnceLock<BaseDirectories> = OnceLock::new();
static CONFIG_PATH: OnceLock<PathBuf> = OnceLock::new();
pub const MAIN_CONFIG_FILENAME: &str = "config.toml";
pub const KEYMAP_CONFIG_FILENAME: &str = "keymap.toml";

pub fn xdg_dirs() -> &'static BaseDirectories {
XDG_DIRS.get_or_init(|| xdg::BaseDirectories::with_prefix("rustmission").unwrap())
}

pub fn get_config_path(filename: &str) -> &'static PathBuf {
CONFIG_PATH.get_or_init(|| xdg_dirs().place_config_file(filename).unwrap())
pub struct Directories {
pub main_path: &'static PathBuf,
pub keymap_path: &'static PathBuf,
}

impl Config {
pub fn init() -> Result<Self> {
let Ok(table) = utils::fetch_config_table(MAIN_CONFIG_FILENAME) else {
put_config(DEFAULT_CONFIG, MAIN_CONFIG_FILENAME)?;
// TODO: check if the user really changed the config.
println!(
"Update {:?} and start rustmission again",
get_config_path(MAIN_CONFIG_FILENAME)
);
std::process::exit(0);
};
let main_config = MainConfig::init()?;
let keybindings = KeymapConfig::init()?;

Self::table_config_verify(&table)?;

let mut config = Self::table_to_config(&table)?;
config.keymap = Some(Keymap::init().unwrap().to_hashmap());
Ok(config)
}

fn table_to_config(table: &Table) -> Result<Self> {
let config_string = table.to_string();
let config: Self = toml::from_str(&config_string)?;
Ok(config)
}

fn table_config_verify(table: &Table) -> Result<()> {
let Some(connection_table) = table.get("connection").unwrap().as_table() else {
bail!("expected connection table")
let directories = Directories {
main_path: MainConfig::path(),
keymap_path: KeymapConfig::path(),
};

let url = connection_table
.get("url")
.and_then(|url| url.as_str())
.with_context(|| {
format!(
"no url given in: {}",
get_config_path(MAIN_CONFIG_FILENAME).to_str().unwrap()
)
})?;

url::Url::parse(url).with_context(|| {
format!(
"invalid url '{url}' in {}",
get_config_path(MAIN_CONFIG_FILENAME).to_str().unwrap()
)
})?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

fn invalid_config() -> Table {
toml::toml! {
[connection]
username = "username"
password = "password"
auto_hide = "dfgoij"
url = "bad_url"
}
}

fn valid_config() -> Table {
toml::toml! {
[connection]
username = "username"
password = "password"
url = "http://192.168.1.1/transmission/rpc"
}
}

#[test]
fn validates_properly() {
let valid_config = valid_config();
assert!(Config::table_config_verify(&valid_config).is_ok());
}

#[test]
fn invalidates_properly() {
let invalid_config = invalid_config();
assert!(Config::table_config_verify(&invalid_config).is_err());
Ok(Self {
general: main_config.general,
connection: main_config.connection,
keymap: keybindings.to_hashmap(),
directories,
})
}
}
60 changes: 60 additions & 0 deletions rm-config/src/main_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::{path::PathBuf, sync::OnceLock};

use anyhow::Result;
use ratatui::style::Color;
use serde::{Deserialize, Serialize};
use url::Url;

use crate::utils::{self, put_config};

#[derive(Serialize, Deserialize)]
pub struct MainConfig {
pub general: General,
pub connection: Connection,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct General {
#[serde(default)]
pub auto_hide: bool,
#[serde(default = "default_accent_color")]
pub accent_color: Color,
#[serde(default = "default_beginner_mode")]
pub beginner_mode: bool,
}

fn default_accent_color() -> Color {
Color::LightMagenta
}

fn default_beginner_mode() -> bool {
true
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Connection {
pub username: Option<String>,
pub password: Option<String>,
pub url: Url,
}

impl MainConfig {
pub(crate) const FILENAME: &'static str = "config.toml";
const DEFAULT_CONFIG: &'static str = include_str!("../defaults/config.toml");

pub(crate) fn init() -> Result<Self> {
let Ok(config) = utils::fetch_config(Self::FILENAME) else {
put_config(Self::DEFAULT_CONFIG, Self::FILENAME)?;
// TODO: check if the user really changed the config.
println!("Update {:?} and start rustmission again", Self::path());
std::process::exit(0);
};

Ok(config)
}

pub(crate) fn path() -> &'static PathBuf {
static PATH: OnceLock<PathBuf> = OnceLock::new();
PATH.get_or_init(|| utils::get_config_path(Self::FILENAME))
}
}
20 changes: 15 additions & 5 deletions rm-config/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
sync::OnceLock,
};

use anyhow::Result;
use toml::Table;
use serde::de::DeserializeOwned;
use xdg::BaseDirectories;

use crate::get_config_path;
pub fn xdg_dirs() -> &'static BaseDirectories {
static XDG_DIRS: OnceLock<BaseDirectories> = OnceLock::new();
XDG_DIRS.get_or_init(|| xdg::BaseDirectories::with_prefix("rustmission").unwrap())
}

pub fn get_config_path(filename: &str) -> PathBuf {
xdg_dirs().place_config_file(filename).unwrap()
}

pub fn fetch_config_table(config_name: &str) -> Result<Table> {
let config_path = crate::xdg_dirs()
pub fn fetch_config<T: DeserializeOwned>(config_name: &str) -> Result<T> {
let config_path = xdg_dirs()
.find_config_file(config_name)
.ok_or_else(|| anyhow::anyhow!("{} not found", config_name))?;

Expand All @@ -20,7 +30,7 @@ pub fn fetch_config_table(config_name: &str) -> Result<Table> {
Ok(toml::from_str(&config_buf)?)
}

pub fn put_config(content: &str, filename: &str) -> Result<Table> {
pub fn put_config<T: DeserializeOwned>(content: &str, filename: &str) -> Result<T> {
let config_path = get_config_path(filename);
let mut config_file = File::create(config_path)?;
config_file.write_all(content.as_bytes())?;
Expand Down
4 changes: 2 additions & 2 deletions rm-main/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Ctx {
});
}
Err(e) => {
let config_path = rm_config::get_config_path(rm_config::MAIN_CONFIG_FILENAME);
let config_path = config.directories.main_path;
return Err(Error::msg(format!(
"{e}\nIs the connection info in {:?} correct?",
config_path
Expand Down Expand Up @@ -111,7 +111,7 @@ impl App {

tokio::select! {
event = tui_event => {
if let Some(action) = event_to_action(self.mode, event.unwrap(), self.ctx.config.keymap.as_ref().unwrap()) {
if let Some(action) = event_to_action(self.mode, event.unwrap(), &self.ctx.config.keymap) {
if let Some(action) = self.update(action).await {
self.ctx.action_tx.send(action).unwrap();
}
Expand Down
3 changes: 1 addition & 2 deletions rm-main/src/transmission/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ pub fn client_from_config(config: &Config) -> TransClient {
.as_ref()
.unwrap_or(&"".to_string())
.clone();
let url = config.connection.url.parse().unwrap();

let auth = BasicAuth { user, password };

TransClient::with_auth(url, auth)
TransClient::with_auth(config.connection.url.clone(), auth)
}

0 comments on commit c19cd77

Please sign in to comment.