From 83494777206e19c819bcb576ea9b584dca898ca7 Mon Sep 17 00:00:00 2001 From: Ryan Aidan Date: Wed, 26 Jun 2024 15:04:39 +0800 Subject: [PATCH 1/3] docs(readme): add brew install instructions (#41) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1880bb1..ee920eb 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ or with Nix: nix run . ``` +or with Brew: +```bash +brew install intuis/tap/rustmission +``` + ## Usage Launch Rustmission in your terminal to initialize the configuration and make adjustments as needed. Subsequently, run Rustmission again. For a list of keybindings, press '?'. From 96cc5ea9850f95c7769b50436fa44bdbb0135f91 Mon Sep 17 00:00:00 2001 From: Ryan Aidan Date: Wed, 26 Jun 2024 21:01:05 +0800 Subject: [PATCH 2/3] feat: animating spinner (#39) --- Cargo.lock | 11 +++++++++ Cargo.toml | 1 + rm-main/Cargo.toml | 2 ++ rm-main/src/action.rs | 1 + rm-main/src/app.rs | 6 +++++ rm-main/src/ui/components/mod.rs | 3 +++ rm-main/src/ui/tabs/search.rs | 41 ++++++++++++++++++++++++++------ 7 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61b9ac5..4bda9d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1350,6 +1350,7 @@ dependencies = [ "ratatui", "rm-config", "serde", + "throbber-widgets-tui", "tokio", "tokio-util", "transmission-rpc", @@ -1650,6 +1651,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "throbber-widgets-tui" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431b847a60fc7b1df94a5b1bcb3cdd7027bb0013b431b08038505e8891c577a2" +dependencies = [ + "rand", + "ratatui", +] + [[package]] name = "tinystr" version = "0.7.6" diff --git a/Cargo.toml b/Cargo.toml index e508dfa..761fba5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ crossterm = { version = "0.27", features = ["event-stream"] } ratatui = { version = "0.26", features = ["serde"] } tui-input = "0.8" tui-tree-widget = "0.20" +throbber-widgets-tui = "0.5.0" # Config for 'cargo dist' [workspace.metadata.dist] diff --git a/rm-main/Cargo.toml b/rm-main/Cargo.toml index bf320bb..e6311ea 100644 --- a/rm-main/Cargo.toml +++ b/rm-main/Cargo.toml @@ -29,3 +29,5 @@ crossterm.workspace = true ratatui.workspace = true tui-input.workspace = true tui-tree-widget.workspace = true +throbber-widgets-tui.workspace = true + diff --git a/rm-main/src/action.rs b/rm-main/src/action.rs index 07a5d05..d3a8a76 100644 --- a/rm-main/src/action.rs +++ b/rm-main/src/action.rs @@ -8,6 +8,7 @@ pub(crate) enum Action { Quit, SoftQuit, Render, + Tick, Up, Down, Left, diff --git a/rm-main/src/app.rs b/rm-main/src/app.rs index 89f8620..15f1c6f 100644 --- a/rm-main/src/app.rs +++ b/rm-main/src/app.rs @@ -102,11 +102,17 @@ impl App { } async fn main_loop(&mut self, tui: &mut Tui) -> Result<()> { + let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(250)); loop { let tui_event = tui.next(); let action = self.action_rx.recv(); + let tick_action = interval.tick(); tokio::select! { + _ = tick_action => { + self.ctx.action_tx.send(Action::Tick).unwrap(); + }, + event = tui_event => { if let Some(action) = event_to_action(self.mode, event.unwrap()) { if let Some(action) = self.update(action).await { diff --git a/rm-main/src/ui/components/mod.rs b/rm-main/src/ui/components/mod.rs index 39ee924..fb4eb91 100644 --- a/rm-main/src/ui/components/mod.rs +++ b/rm-main/src/ui/components/mod.rs @@ -12,4 +12,7 @@ pub trait Component { None } fn render(&mut self, _f: &mut Frame, _rect: Rect) {} + fn tick(&mut self) -> Option { + None + } } diff --git a/rm-main/src/ui/tabs/search.rs b/rm-main/src/ui/tabs/search.rs index b5a6f2b..96ff320 100644 --- a/rm-main/src/ui/tabs/search.rs +++ b/rm-main/src/ui/tabs/search.rs @@ -10,6 +10,7 @@ use ratatui::{ prelude::*, widgets::{Cell, Paragraph, Row, Table}, }; +use throbber_widgets_tui::ThrobberState; use tokio::sync::mpsc::{self, UnboundedSender}; use tui_input::Input; @@ -53,7 +54,10 @@ impl SearchTab { tokio::task::spawn(async move { let magnetease = Magnetease::new(); while let Some(search_phrase) = rx.recv().await { - search_result_info_clone.lock().unwrap().searching(); + search_result_info_clone + .lock() + .unwrap() + .searching(Arc::new(Mutex::new(ThrobberState::default()))); ctx_clone.send_action(Action::Render); let res = magnetease.search(&search_phrase).await; if res.is_empty() { @@ -193,11 +197,17 @@ impl Component for SearchTab { A::Home => self.scroll_to_home(), A::End => self.scroll_to_end(), A::Confirm => self.add_torrent(), + A::Tick => self.tick(), _ => None, } } + fn tick(&mut self) -> Option { + self.search_result_info.lock().unwrap().tick(); + Some(Action::Render) + } + fn render(&mut self, f: &mut Frame, rect: Rect) { let [top_line, rest, bottom_line] = Layout::vertical([ Constraint::Length(1), @@ -284,11 +294,11 @@ impl Component for SearchTab { } } -#[derive(Clone, Copy)] +#[derive(Clone)] enum SearchResultState { Nothing, NoResults, - Searching, + Searching(Arc>), Found(usize), } @@ -297,8 +307,8 @@ impl SearchResultState { Self::Nothing } - fn searching(&mut self) { - *self = Self::Searching; + fn searching(&mut self, state: Arc>) { + *self = Self::Searching(state); } fn not_found(&mut self) { @@ -314,8 +324,15 @@ impl Component for SearchResultState { fn render(&mut self, f: &mut Frame, rect: Rect) { match self { SearchResultState::Nothing => return, - SearchResultState::Searching => { - f.render_widget("󱗼 Searching...", rect); + SearchResultState::Searching(state) => { + let default_throbber = throbber_widgets_tui::Throbber::default() + .label("Searching...") + .style(ratatui::style::Style::default().fg(ratatui::style::Color::Yellow)); + f.render_stateful_widget( + default_throbber.clone(), + rect, + &mut state.lock().unwrap(), + ); } SearchResultState::NoResults => { let mut line = Line::default(); @@ -333,4 +350,14 @@ impl Component for SearchResultState { } } } + + fn tick(&mut self) -> Option { + match self { + SearchResultState::Searching(state) => { + state.lock().unwrap().calc_next(); + Some(Action::Render) + } + _ => None, + } + } } From 8cfd72f460f920de5254957a7cb3b3ba74fb885b Mon Sep 17 00:00:00 2001 From: Ryan Aidan Date: Fri, 28 Jun 2024 00:47:53 +0800 Subject: [PATCH 3/3] feat: configurable refresh intervals (#44) --- rm-config/defaults/config.toml | 5 ++++- rm-config/src/lib.rs | 6 ++++++ rm-main/src/transmission/fetchers.rs | 9 ++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/rm-config/defaults/config.toml b/rm-config/defaults/config.toml index d0f6256..5cce726 100644 --- a/rm-config/defaults/config.toml +++ b/rm-config/defaults/config.toml @@ -21,4 +21,7 @@ url = "http://CHANGE_ME:9091/transmission/rpc" # REQUIRED! # username = "CHANGE_ME" # password = "CHANGE_ME" - +# Refresh timings (in seconds) +torrents_refresh = 5 +stats_refresh = 10 +free_space_refresh = 10 diff --git a/rm-config/src/lib.rs b/rm-config/src/lib.rs index d6f0f11..6e8f85b 100644 --- a/rm-config/src/lib.rs +++ b/rm-config/src/lib.rs @@ -42,6 +42,12 @@ pub struct Connection { pub username: Option, pub password: Option, pub url: String, + #[serde(default)] + pub torrents_refresh: u64, + #[serde(default)] + pub stats_refresh: u64, + #[serde(default)] + pub free_space_refresh: u64, } const DEFAULT_CONFIG: &str = include_str!("../defaults/config.toml"); diff --git a/rm-main/src/transmission/fetchers.rs b/rm-main/src/transmission/fetchers.rs index db98d4e..4f65726 100644 --- a/rm-main/src/transmission/fetchers.rs +++ b/rm-main/src/transmission/fetchers.rs @@ -23,7 +23,7 @@ pub async fn stats(ctx: app::Ctx, stats: Arc>>) { .arguments; *stats.lock().unwrap() = Some(new_stats); ctx.send_action(Action::Render); - tokio::time::sleep(Duration::from_secs(5)).await; + tokio::time::sleep(Duration::from_secs(ctx.config.connection.stats_refresh)).await; } } @@ -50,7 +50,10 @@ pub async fn free_space(ctx: app::Ctx, free_space: Arc>> .arguments; *free_space.lock().unwrap() = Some(new_free_space); ctx.send_action(Action::Render); - tokio::time::sleep(Duration::from_secs(10)).await; + tokio::time::sleep(Duration::from_secs( + ctx.config.connection.free_space_refresh, + )) + .await; } } @@ -85,6 +88,6 @@ pub async fn torrents(ctx: app::Ctx, table_manager: Arc>) { .set_new_rows(new_torrents.iter().map(RustmissionTorrent::from).collect()); } ctx.send_action(Action::Render); - tokio::time::sleep(Duration::from_secs(5)).await; + tokio::time::sleep(Duration::from_secs(ctx.config.connection.torrents_refresh)).await; } }