From 89986422cb238151a8916c2f0784878fd8cab016 Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Tue, 20 Aug 2024 12:48:57 +0200 Subject: [PATCH] categories --- rm-config/defaults/categories.toml | 6 ++ rm-config/defaults/config.toml | 2 +- rm-config/src/categories.rs | 61 +++++++++++++++++++ rm-config/src/lib.rs | 9 ++- rm-main/src/transmission/fetchers.rs | 1 + rm-main/src/tui/components/table.rs | 7 ++- .../tui/tabs/torrents/rustmission_torrent.rs | 14 +++++ rm-shared/src/header.rs | 5 +- 8 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 rm-config/defaults/categories.toml create mode 100644 rm-config/src/categories.rs diff --git a/rm-config/defaults/categories.toml b/rm-config/defaults/categories.toml new file mode 100644 index 0000000..241803a --- /dev/null +++ b/rm-config/defaults/categories.toml @@ -0,0 +1,6 @@ +# Example category: +# [[category]] +# name = "Classical Music" # required +# prefix = "[M]" # optional, default: "" +# default_dir = "/mnt/Music/Classical" # optional, default: transmission's default +# color = "Green" # optional, default: "White" diff --git a/rm-config/defaults/config.toml b/rm-config/defaults/config.toml index 0d8cca8..fe049d1 100644 --- a/rm-config/defaults/config.toml +++ b/rm-config/defaults/config.toml @@ -31,7 +31,7 @@ free_space_refresh = 10 # Available fields: # Id, Name, SizeWhenDone, Progress, Eta, DownloadRate, UploadRate, DownloadDir, # Padding, UploadRatio, UploadedEver, AddedDate, ActivityDate, PeersConnected -# SmallStatus +# SmallStatus, Category headers = ["Name", "SizeWhenDone", "Progress", "Eta", "DownloadRate", "UploadRate"] [search_tab] diff --git a/rm-config/src/categories.rs b/rm-config/src/categories.rs new file mode 100644 index 0000000..aa8bbce --- /dev/null +++ b/rm-config/src/categories.rs @@ -0,0 +1,61 @@ +use std::{collections::HashMap, io::ErrorKind, path::PathBuf, sync::OnceLock}; + +use anyhow::{Context, Result}; +use ratatui::style::Color; +use serde::Deserialize; + +use crate::utils::{self, ConfigFetchingError}; + +#[derive(Deserialize)] +pub struct CategoriesConfig { + categories: Vec, +} + +#[derive(Deserialize)] +pub struct Category { + pub name: String, + pub prefix: String, + pub color: Color, + pub default_dir: String, +} + +impl CategoriesConfig { + pub(crate) const FILENAME: &'static str = "categories.toml"; + pub const DEFAULT_CONFIG: &'static str = include_str!("../defaults/categories.toml"); + + pub(crate) fn init() -> Result { + match utils::fetch_config::(Self::FILENAME) { + Ok(config) => Ok(config), + Err(e) => match e { + ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => { + let categories_config = + utils::put_config::(Self::DEFAULT_CONFIG, Self::FILENAME)?; + Ok(categories_config) + } + ConfigFetchingError::Toml(e) => Err(e).with_context(|| { + format!( + "Failed to parse config located at {:?}", + utils::get_config_path(Self::FILENAME) + ) + }), + _ => anyhow::bail!(e), + }, + } + } + + pub(crate) fn path() -> &'static PathBuf { + static PATH: OnceLock = OnceLock::new(); + PATH.get_or_init(|| utils::get_config_path(Self::FILENAME)) + } +} + +impl CategoriesConfig { + pub fn to_hashmap(self) -> HashMap { + let mut hashmap = HashMap::new(); + for category in self.categories { + hashmap.insert(category.name.clone(), category); + } + + hashmap + } +} diff --git a/rm-config/src/lib.rs b/rm-config/src/lib.rs index 0506ac6..87b76b9 100644 --- a/rm-config/src/lib.rs +++ b/rm-config/src/lib.rs @@ -1,10 +1,12 @@ +pub mod categories; pub mod keymap; pub mod main_config; mod utils; -use std::{path::PathBuf, sync::LazyLock}; +use std::{collections::HashMap, path::PathBuf, sync::LazyLock}; use anyhow::Result; +use categories::{CategoriesConfig, Category}; use keymap::KeymapConfig; use main_config::MainConfig; @@ -22,22 +24,26 @@ pub struct Config { pub search_tab: main_config::SearchTab, pub icons: main_config::Icons, pub keybindings: KeymapConfig, + pub categories: HashMap, pub directories: Directories, } pub struct Directories { pub main_path: &'static PathBuf, pub keymap_path: &'static PathBuf, + pub categories_path: &'static PathBuf, } impl Config { fn init() -> Result { let main_config = MainConfig::init()?; let keybindings = KeymapConfig::init()?; + let categories = CategoriesConfig::init()?; let directories = Directories { main_path: MainConfig::path(), keymap_path: KeymapConfig::path(), + categories_path: CategoriesConfig::path(), }; Ok(Self { @@ -47,6 +53,7 @@ impl Config { search_tab: main_config.search_tab, icons: main_config.icons, keybindings: keybindings.clone(), + categories: categories.to_hashmap(), directories, }) } diff --git a/rm-main/src/transmission/fetchers.rs b/rm-main/src/transmission/fetchers.rs index 1d1ef06..90d187e 100644 --- a/rm-main/src/transmission/fetchers.rs +++ b/rm-main/src/transmission/fetchers.rs @@ -84,6 +84,7 @@ pub async fn torrents(ctx: app::Ctx) { TorrentGetField::PeersConnected, TorrentGetField::Error, TorrentGetField::ErrorString, + TorrentGetField::Labels, ]; let (torrents_tx, torrents_rx) = oneshot::channel(); ctx.send_torrent_action(TorrentAction::GetTorrents(fields, torrents_tx)); diff --git a/rm-main/src/tui/components/table.rs b/rm-main/src/tui/components/table.rs index 0e126ad..7917205 100644 --- a/rm-main/src/tui/components/table.rs +++ b/rm-main/src/tui/components/table.rs @@ -105,6 +105,11 @@ impl GenericTable { } pub fn select_last(&mut self) { - self.state.borrow_mut().select_last(); + if self.items.is_empty() { + return; + } + + let mut state = self.state.borrow_mut(); + state.select(Some(self.items.len() - 1)); } } diff --git a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs index 0cb9959..5acd6dc 100644 --- a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs @@ -28,6 +28,7 @@ pub struct RustmissionTorrent { pub activity_date: NaiveDateTime, pub added_date: NaiveDateTime, pub peers_connected: i64, + pub categories: Vec, pub error: Option, } @@ -176,6 +177,16 @@ impl RustmissionTorrent { } } } + Header::Category => match self.categories.get(0) { + Some(category) => { + if let Some(config_category) = CONFIG.categories.get(category) { + Cell::from(category.as_str()).fg(config_category.color) + } else { + Cell::from(category.as_str()) + } + } + None => Cell::default(), + }, } } @@ -271,6 +282,8 @@ impl From for RustmissionTorrent { } }; + let categories = t.labels.unwrap(); + Self { torrent_name, size_when_done, @@ -287,6 +300,7 @@ impl From for RustmissionTorrent { activity_date, added_date, peers_connected, + categories, error, } } diff --git a/rm-shared/src/header.rs b/rm-shared/src/header.rs index 141a29e..191cbeb 100644 --- a/rm-shared/src/header.rs +++ b/rm-shared/src/header.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, Copy)] pub enum Header { + Id, Name, SizeWhenDone, Progress, @@ -13,11 +14,11 @@ pub enum Header { Padding, UploadRatio, UploadedEver, - Id, ActivityDate, AddedDate, PeersConnected, SmallStatus, + Category, } impl Header { @@ -38,6 +39,7 @@ impl Header { Self::AddedDate => Constraint::Length(12), Self::PeersConnected => Constraint::Length(6), Self::SmallStatus => Constraint::Length(1), + Self::Category => Constraint::Max(15), } } @@ -58,6 +60,7 @@ impl Header { Self::AddedDate => "Added", Self::PeersConnected => "Peers", Self::SmallStatus => "", + Self::Category => "Category", } } }