Skip to content

Commit

Permalink
start implementing config
Browse files Browse the repository at this point in the history
  • Loading branch information
micielski committed Jun 23, 2024
1 parent 939a2f6 commit 2ecc514
Show file tree
Hide file tree
Showing 32 changed files with 327 additions and 100 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ license = "GPL-3.0-or-later"

[workspace.dependencies]
rm-config = { version = "0.3", path = "rm-config" }
rm-shared = { version = "0.3", path = "rm-shared" }

magnetease = "0.1"
anyhow = "1"
Expand All @@ -33,7 +34,7 @@ tokio-util = "0.7"
futures = "0.3"

# TUI
crossterm = { version = "0.27", features = ["event-stream"] }
crossterm = { version = "0.27", features = ["event-stream", "serde"] }
ratatui = { version = "0.26", features = ["serde"] }
tui-input = "0.8"
tui-tree-widget = "0.20"
Expand Down
2 changes: 2 additions & 0 deletions rm-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ homepage.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rm-shared.workspace = true
xdg.workspace = true
toml.workspace = true
serde.workspace = true
anyhow.workspace = true
url.workspace = true
ratatui.workspace = true
crossterm.workspace = true


155 changes: 155 additions & 0 deletions rm-config/src/keymap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use std::collections::HashMap;

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

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

#[derive(Serialize, Deserialize)]
pub struct Keymap {
general: General<GeneralAction>,
torrents_tab: TorrentsTab<TorrentsAction>,
}

#[derive(Serialize, Deserialize)]
struct General<T: Into<Action>> {
keybindings: Vec<Keybinding<T>>,
}

#[derive(Serialize, Deserialize, Debug)]
enum GeneralAction {
ShowHelp,
Quit,
SoftQuit,
SwitchToTorrents,
SwitchToSearch,
Left,
Right,
Down,
Up,
Search,
SwitchFocus,
Confirm,
PageDown,
PageUp,
Home,
End,
}

impl From<GeneralAction> for Action {
fn from(value: GeneralAction) -> Self {
match value {
GeneralAction::ShowHelp => Action::ShowHelp,
GeneralAction::Quit => Action::Quit,
GeneralAction::SoftQuit => Action::SoftQuit,
GeneralAction::SwitchToTorrents => Action::ChangeTab(1),
GeneralAction::SwitchToSearch => Action::ChangeTab(2),
GeneralAction::Left => Action::Left,
GeneralAction::Right => Action::Right,
GeneralAction::Down => Action::Down,
GeneralAction::Up => Action::Up,
GeneralAction::Search => Action::Search,
GeneralAction::SwitchFocus => Action::ChangeFocus,
GeneralAction::Confirm => Action::Confirm,
GeneralAction::PageDown => Action::ScrollDownPage,
GeneralAction::PageUp => Action::ScrollUpPage,
GeneralAction::Home => Action::Home,
GeneralAction::End => Action::End,
}
}
}

#[derive(Serialize, Deserialize)]
struct TorrentsTab<T: Into<Action>> {
keybindings: Vec<Keybinding<T>>,
}

#[derive(Serialize, Deserialize, Debug)]
enum TorrentsAction {
AddMagnet,
Pause,
DeleteWithFiles,
DeleteWithoutFiles,
ShowFiles,
ShowStats,
}

impl From<TorrentsAction> for Action {
fn from(value: TorrentsAction) -> Self {
match value {
TorrentsAction::AddMagnet => Action::AddMagnet,
TorrentsAction::Pause => Action::Pause,
TorrentsAction::DeleteWithFiles => Action::DeleteWithFiles,
TorrentsAction::DeleteWithoutFiles => Action::DeleteWithoutFiles,
TorrentsAction::ShowFiles => Action::ShowFiles,
TorrentsAction::ShowStats => Action::ShowStats,
}
}
}

#[derive(Serialize, Deserialize)]
struct Keybinding<T: Into<Action>> {
on: KeyCode,
#[serde(default)]
modifier: KeyModifier,
action: T,
}

#[derive(Serialize, Deserialize, Hash)]
enum KeyModifier {
None,
Ctrl,
Shift,
}

impl From<KeyModifier> for KeyModifiers {
fn from(value: KeyModifier) -> Self {
match value {
KeyModifier::None => KeyModifiers::NONE,
KeyModifier::Ctrl => KeyModifiers::CONTROL,
KeyModifier::Shift => KeyModifiers::SHIFT,
}
}
}

impl Default for KeyModifier {
fn default() -> Self {
Self::None
}
}

impl Keymap {
pub fn init() -> Result<Self> {
let table = {
if let Ok(table) = utils::fetch_config_table(KEYMAP_CONFIG_FILENAME) {
table
} else {
todo!();
}
};

Self::table_to_keymap(&table)
}

pub fn to_hashmap(self) -> HashMap<(KeyCode, KeyModifiers), Action> {
let mut hashmap = HashMap::new();
for keybinding in self.general.keybindings {
let hash_value = (keybinding.on, keybinding.modifier.into());
hashmap.insert(hash_value, keybinding.action.into());
}
for keybinding in self.torrents_tab.keybindings {
let hash_value = (keybinding.on, keybinding.modifier.into());
hashmap.insert(hash_value, keybinding.action.into());
}
hashmap
}

fn table_to_keymap(table: &Table) -> Result<Self> {
let config_string = table.to_string();
let config = toml::from_str(&config_string)?;
Ok(config)
}
}
74 changes: 30 additions & 44 deletions rm-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
use std::{
fs::File,
io::{Read, Write},
path::PathBuf,
sync::OnceLock,
};
mod keymap;
mod utils;

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

use anyhow::{bail, Context, 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;

#[derive(Debug, Serialize, Deserialize)]
use crate::utils::put_config;
use keymap::Keymap;

#[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)]
Expand Down Expand Up @@ -45,41 +50,34 @@ pub struct Connection {
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())
}

impl Config {
pub fn init() -> Result<Self> {
let Ok(table) = Self::table_from_home() else {
Self::put_default_conf_in_home()?;
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",
Self::get_config_path()
get_config_path(MAIN_CONFIG_FILENAME)
);
std::process::exit(0);
};

Self::table_config_verify(&table)?;

Self::table_to_config(&table)
}

fn table_from_home() -> Result<Table> {
let xdg_dirs = xdg::BaseDirectories::with_prefix("rustmission")?;
let config_path = xdg_dirs
.find_config_file("config.toml")
.ok_or_else(|| anyhow::anyhow!("config.toml not found"))?;

let mut config_buf = String::new();
let mut config_file = File::open(config_path).unwrap();
config_file.read_to_string(&mut config_buf).unwrap();
Ok(toml::from_str(&config_buf)?)
}

fn put_default_conf_in_home() -> Result<Table> {
let config_path = Self::get_config_path();
let mut config_file = File::create(config_path)?;
config_file.write_all(DEFAULT_CONFIG.as_bytes())?;
Ok(toml::from_str(DEFAULT_CONFIG)?)
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> {
Expand All @@ -99,31 +97,19 @@ impl Config {
.with_context(|| {
format!(
"no url given in: {}",
Self::get_config_path().to_str().unwrap()
get_config_path(MAIN_CONFIG_FILENAME).to_str().unwrap()
)
})?;

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

Ok(())
}

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

pub fn get_config_path() -> &'static PathBuf {
CONFIG_PATH.get_or_init(|| {
Self::get_xdg_dirs()
.place_config_file("config.toml")
.unwrap()
})
}
}

#[cfg(test)]
Expand Down
28 changes: 28 additions & 0 deletions rm-config/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::{
fs::File,
io::{Read, Write},
};

use anyhow::Result;
use toml::Table;

use crate::get_config_path;

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

let mut config_buf = String::new();
let mut config_file = File::open(config_path).unwrap();
config_file.read_to_string(&mut config_buf).unwrap();

Ok(toml::from_str(&config_buf)?)
}

pub fn put_config(content: &str, filename: &str) -> Result<Table> {
let config_path = get_config_path(filename);
let mut config_file = File::create(config_path)?;
config_file.write_all(content.as_bytes())?;
Ok(toml::from_str(content)?)
}
1 change: 1 addition & 0 deletions rm-main/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ path = "src/main.rs"

[dependencies]
rm-config.workspace = true
rm-shared.workspace = true
magnetease.workspace = true
anyhow.workspace = true
serde.workspace = true
Expand Down
Loading

0 comments on commit 2ecc514

Please sign in to comment.