From 7701ff94e2b7f48fc5289402eb0b5b6925394ddb Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Thu, 5 Sep 2024 08:23:59 +0200 Subject: [PATCH 1/9] feat: multiple selections --- rm-main/src/tui/tabs/torrents/mod.rs | 4 ++ .../tui/tabs/torrents/rustmission_torrent.rs | 20 +++++++--- .../src/tui/tabs/torrents/table_manager.rs | 38 ++++++++++++++++++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index dcbc6d5..f44e796 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -121,6 +121,10 @@ impl Component for TorrentsTab { A::ShowStats => self.show_statistics_popup(), A::ShowFiles => self.show_files_popup(), A::Confirm => self.show_details_popup(), + A::Select => { + self.table_manager.select_current_torrent(); + self.ctx.send_action(Action::Render); + } A::Pause => self.pause_current_torrent(), A::Delete => { if let Some(torrent) = self.table_manager.current_torrent() { diff --git a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs index 9656213..86fc504 100644 --- a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs @@ -22,7 +22,7 @@ pub struct RustmissionTorrent { pub uploaded_ever: String, pub upload_ratio: String, status: TorrentStatus, - pub style: Style, + style: Style, pub id: Id, pub download_dir: String, pub activity_date: NaiveDateTime, @@ -30,6 +30,7 @@ pub struct RustmissionTorrent { pub peers_connected: i64, pub category: Option, pub error: Option, + pub is_selected: bool, } #[derive(Clone)] @@ -53,7 +54,7 @@ impl RustmissionTorrent { .iter() .map(|header| self.header_to_cell(*header)) .collect::() - .style(self.style) + .style(self.style()) .height(if self.error.is_some() { 2 } else { 1 }) } @@ -99,7 +100,7 @@ impl RustmissionTorrent { let mut end = char_indices[end]; torrent_name_line.push_span(Span::styled( &self.torrent_name[last_end..start], - self.style, + self.style(), )); while !self.torrent_name.is_char_boundary(start) { @@ -154,7 +155,7 @@ impl RustmissionTorrent { flush_line(first, second); } - torrent_name_line.push_span(Span::styled(&self.torrent_name[last_end..], self.style)); + torrent_name_line.push_span(Span::styled(&self.torrent_name[last_end..], self.style())); let mut cells = vec![]; @@ -162,13 +163,21 @@ impl RustmissionTorrent { if *header == Header::Name { cells.push(std::mem::take(&mut torrent_name_line).into()) } else { - cells.push(self.header_to_cell(*header).style(self.style)) + cells.push(self.header_to_cell(*header).style(self.style())) } } Row::new(cells) } + fn style(&self) -> Style { + if self.is_selected { + self.style.reversed() + } else { + self.style + } + } + pub fn torrent_location(&self) -> String { format!("{}/{}", self.download_dir, self.torrent_name) } @@ -384,6 +393,7 @@ impl From for RustmissionTorrent { peers_connected, category, error, + is_selected: false, } } } diff --git a/rm-main/src/tui/tabs/torrents/table_manager.rs b/rm-main/src/tui/tabs/torrents/table_manager.rs index c32ea94..cca412e 100644 --- a/rm-main/src/tui/tabs/torrents/table_manager.rs +++ b/rm-main/src/tui/tabs/torrents/table_manager.rs @@ -3,6 +3,7 @@ use ratatui::{prelude::*, widgets::Row}; use rm_config::CONFIG; use rm_shared::header::Header; use std::{cmp::Ordering, collections::HashMap}; +use transmission_rpc::types::Id; use crate::tui::components::GenericTable; @@ -16,6 +17,7 @@ pub struct TableManager { pub sort_header: Option, pub sort_reverse: bool, pub sorting_is_being_selected: bool, + pub selected_torrents_ids: Vec, } pub struct Filter { @@ -37,6 +39,7 @@ impl TableManager { sort_header: None, sort_reverse: false, sorting_is_being_selected: false, + selected_torrents_ids: vec![], } } @@ -178,6 +181,29 @@ impl TableManager { } } + pub fn select_current_torrent(&mut self) { + let mut is_selected = true; + if let Some(t) = self.current_torrent() { + if let Id::Id(id) = t.id { + match self.selected_torrents_ids.iter().position(|&x| x == id) { + Some(idx) => { + self.selected_torrents_ids.remove(idx); + is_selected = false; + } + None => { + self.selected_torrents_ids.push(id); + } + } + } else { + unreachable!(); + } + } + + if let Some(t) = self.current_torrent() { + t.is_selected = is_selected; + } + } + pub fn rows(&self) -> Vec> { if let Some(filter) = &self.filter { let highlight_style = Style::default().fg(CONFIG.general.accent_color); @@ -223,7 +249,17 @@ impl TableManager { } } - pub fn set_new_rows(&mut self, rows: Vec) { + pub fn set_new_rows(&mut self, mut rows: Vec) { + if !self.selected_torrents_ids.is_empty() { + for row in &mut rows { + if let Id::Id(id) = row.id { + if self.selected_torrents_ids.contains(&id) { + row.is_selected = true; + } + } + } + } + self.table.set_items(rows); self.widths = self.header_widths(&self.table.items); self.update_rows_number(); From 527a542ff79c42da0d6a65a8a746a92eb5a7c5da Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Thu, 5 Sep 2024 12:32:52 +0200 Subject: [PATCH 2/9] make it look better when searching --- rm-main/src/tui/tabs/torrents/rustmission_torrent.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs index 86fc504..b72b659 100644 --- a/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/rustmission_torrent.rs @@ -100,7 +100,7 @@ impl RustmissionTorrent { let mut end = char_indices[end]; torrent_name_line.push_span(Span::styled( &self.torrent_name[last_end..start], - self.style(), + self.style, )); while !self.torrent_name.is_char_boundary(start) { @@ -155,7 +155,7 @@ impl RustmissionTorrent { flush_line(first, second); } - torrent_name_line.push_span(Span::styled(&self.torrent_name[last_end..], self.style())); + torrent_name_line.push_span(Span::styled(&self.torrent_name[last_end..], self.style)); let mut cells = vec![]; @@ -167,7 +167,11 @@ impl RustmissionTorrent { } } - Row::new(cells) + if self.is_selected { + Row::new(cells).reversed() + } else { + Row::new(cells) + } } fn style(&self) -> Style { From 78c5d7092c50e794179d3de6e45148d4036b05ab Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Thu, 5 Sep 2024 12:48:24 +0200 Subject: [PATCH 3/9] esc/quit to deselect --- rm-main/src/tui/tabs/torrents/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index f44e796..9bfc7ad 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -105,8 +105,22 @@ impl Component for TorrentsTab { return ComponentAction::Nothing; } + if !self.table_manager.selected_torrents_ids.is_empty() { + if action.is_soft_quit() { + self.table_manager + .table + .items + .iter_mut() + .for_each(|t| t.is_selected = false); + self.table_manager.selected_torrents_ids.drain(..); + self.ctx.send_action(Action::Render); + return ComponentAction::Nothing; + } + } + if action.is_quit() { self.ctx.send_action(Action::HardQuit); + return ComponentAction::Nothing; } match action { From 344d3e73c04b96dc929d2fa14cd1b23618a68249 Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 10:51:16 +0200 Subject: [PATCH 4/9] selection bottom bar --- rm-main/src/tui/tabs/torrents/mod.rs | 17 +++++++++ rm-main/src/tui/tabs/torrents/task_manager.rs | 11 ++++-- rm-main/src/tui/tabs/torrents/tasks/mod.rs | 2 ++ .../src/tui/tabs/torrents/tasks/selection.rs | 36 +++++++++++++++++++ rm-shared/src/action.rs | 1 + 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 rm-main/src/tui/tabs/torrents/tasks/selection.rs diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index 9bfc7ad..99ce9f4 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -113,6 +113,7 @@ impl Component for TorrentsTab { .iter_mut() .for_each(|t| t.is_selected = false); self.table_manager.selected_torrents_ids.drain(..); + self.task_manager.default(); self.ctx.send_action(Action::Render); return ComponentAction::Nothing; } @@ -137,6 +138,12 @@ impl Component for TorrentsTab { A::Confirm => self.show_details_popup(), A::Select => { self.table_manager.select_current_torrent(); + if !self.table_manager.selected_torrents_ids.is_empty() { + self.task_manager + .select(self.table_manager.selected_torrents_ids.len()); + } else { + self.task_manager.default(); + } self.ctx.send_action(Action::Render); } A::Pause => self.pause_current_torrent(), @@ -211,6 +218,16 @@ impl Component for TorrentsTab { UpdateAction::UpdateCurrentTorrent(_) => { self.popup_manager.handle_update_action(action) } + UpdateAction::CancelTorrentTask => { + if !self.table_manager.selected_torrents_ids.is_empty() { + self.task_manager + .select(self.table_manager.selected_torrents_ids.len()); + } else { + self.task_manager.default(); + } + self.ctx + .send_update_action(UpdateAction::SwitchToNormalMode); + } other => self.task_manager.handle_update_action(other), } } diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index 71bde29..497e76b 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -40,6 +40,7 @@ pub enum CurrentTask { Default(tasks::Default), Status(tasks::Status), Sort(tasks::Sort), + Selection(tasks::Selection), } impl CurrentTask { @@ -86,6 +87,7 @@ impl Component for TaskManager { } CurrentTask::Default(_) => (), CurrentTask::Sort(_) => (), + CurrentTask::Selection(_) => (), }; ComponentAction::Nothing } @@ -119,6 +121,7 @@ impl Component for TaskManager { CurrentTask::Status(status_bar) => status_bar.render(f, rect), CurrentTask::ChangeCategory(category_bar) => category_bar.render(f, rect), CurrentTask::Sort(sort_bar) => sort_bar.render(f, rect), + CurrentTask::Selection(selection_bar) => selection_bar.render(f, rect), } } @@ -166,6 +169,10 @@ impl TaskManager { self.current_task = CurrentTask::Default(tasks::Default::new()); } + pub fn select(&mut self, amount: usize) { + self.current_task = CurrentTask::Selection(tasks::Selection::new(amount)); + } + pub fn sort(&mut self) { self.current_task = CurrentTask::Sort(tasks::Sort::new()); } @@ -198,8 +205,6 @@ impl TaskManager { return; } - self.current_task = CurrentTask::Default(tasks::Default::new()); - self.ctx - .send_update_action(UpdateAction::SwitchToNormalMode); + self.ctx.send_update_action(UpdateAction::CancelTorrentTask); } } diff --git a/rm-main/src/tui/tabs/torrents/tasks/mod.rs b/rm-main/src/tui/tabs/torrents/tasks/mod.rs index b7fec47..edf6291 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/mod.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/mod.rs @@ -4,6 +4,7 @@ mod default; mod delete_torrent; mod filter; mod move_torrent; +mod selection; mod sort; mod status; @@ -13,5 +14,6 @@ pub use default::Default; pub use delete_torrent::Delete; pub use filter::Filter; pub use move_torrent::Move; +pub use selection::Selection; pub use sort::Sort; pub use status::{CurrentTaskState, Status}; diff --git a/rm-main/src/tui/tabs/torrents/tasks/selection.rs b/rm-main/src/tui/tabs/torrents/tasks/selection.rs new file mode 100644 index 0000000..003eaf0 --- /dev/null +++ b/rm-main/src/tui/tabs/torrents/tasks/selection.rs @@ -0,0 +1,36 @@ +use crate::tui::components::{keybinding_style, Component}; +use rm_config::CONFIG; +use rm_shared::action::Action; + +use ratatui::{prelude::*, text::Span}; + +pub struct Selection { + selection_amount: usize, +} + +impl Selection { + pub const fn new(selection_amount: usize) -> Self { + Self { selection_amount } + } +} + +impl Component for Selection { + fn render(&mut self, f: &mut Frame, rect: Rect) { + let mut line = Line::default(); + let mut line_is_empty = true; + + if let Some(keys) = CONFIG.keybindings.get_keys_for_action(Action::Close) { + line_is_empty = false; + line.push_span(Span::styled(keys, keybinding_style())); + line.push_span(Span::raw(" - clear selection")); + } + + if !line_is_empty { + line.push_span(Span::raw(" | ")); + } + + line.push_span(format!("{} selected", self.selection_amount)); + + f.render_widget(line, rect); + } +} diff --git a/rm-shared/src/action.rs b/rm-shared/src/action.rs index 1a70445..9c5ee72 100644 --- a/rm-shared/src/action.rs +++ b/rm-shared/src/action.rs @@ -57,6 +57,7 @@ pub enum UpdateAction { UpdateCurrentTorrent(Box), SearchFilterApply(String), SearchFilterClear, + CancelTorrentTask, // Search Tab SearchStarted, ProviderResult(MagneteaseResult), From 94c11fd829a494e1b47d6760ab6a642f3571732c Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 12:23:51 +0200 Subject: [PATCH 5/9] make it work with deletion --- rm-main/src/tui/tabs/torrents/mod.rs | 30 +++++++++++++++-- .../src/tui/tabs/torrents/table_manager.rs | 12 +++++++ rm-main/src/tui/tabs/torrents/task_manager.rs | 18 +++++++++-- .../tui/tabs/torrents/tasks/delete_torrent.rs | 31 +++++++++--------- rm-shared/src/status_task.rs | 32 +++++++++---------- 5 files changed, 86 insertions(+), 37 deletions(-) diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index 99ce9f4..0ba2f45 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -16,7 +16,7 @@ use ratatui::widgets::{Cell, Row, Table}; use rm_config::CONFIG; use rm_shared::status_task::StatusTask; use rustmission_torrent::RustmissionTorrent; -use transmission_rpc::types::TorrentStatus; +use transmission_rpc::types::{Id, TorrentStatus}; use crate::transmission; use rm_shared::action::{Action, ErrorMessage, UpdateAction}; @@ -148,8 +148,8 @@ impl Component for TorrentsTab { } A::Pause => self.pause_current_torrent(), A::Delete => { - if let Some(torrent) = self.table_manager.current_torrent() { - self.task_manager.delete_torrent(torrent); + if let Some((ids, first_name)) = self.get_torrents_for_a_task() { + self.task_manager.delete_torrents(ids, first_name); } } A::AddMagnet => self.task_manager.add_magnet(), @@ -219,6 +219,10 @@ impl Component for TorrentsTab { self.popup_manager.handle_update_action(action) } UpdateAction::CancelTorrentTask => { + if !self.task_manager.is_finished_status_task() { + return; + } + if !self.table_manager.selected_torrents_ids.is_empty() { self.task_manager .select(self.table_manager.selected_torrents_ids.len()); @@ -314,6 +318,26 @@ impl TorrentsTab { ); } + fn get_torrents_for_a_task(&mut self) -> Option<(Vec, String)> { + if !self.table_manager.selected_torrents_ids.is_empty() { + Some(( + self.table_manager + .selected_torrents_ids + .clone() + .into_iter() + .map(|id| Id::Id(id)) + .collect(), + "".to_string(), + )) + } else { + if let Some(t) = self.table_manager.current_torrent() { + Some((vec![t.id.clone()], t.torrent_name.to_string())) + } else { + None + } + } + } + fn show_files_popup(&mut self) { if let Some(highlighted_torrent) = self.table_manager.current_torrent() { let popup = FilesPopup::new(self.ctx.clone(), highlighted_torrent.id.clone()); diff --git a/rm-main/src/tui/tabs/torrents/table_manager.rs b/rm-main/src/tui/tabs/torrents/table_manager.rs index cca412e..5845205 100644 --- a/rm-main/src/tui/tabs/torrents/table_manager.rs +++ b/rm-main/src/tui/tabs/torrents/table_manager.rs @@ -251,13 +251,25 @@ impl TableManager { pub fn set_new_rows(&mut self, mut rows: Vec) { if !self.selected_torrents_ids.is_empty() { + let mut found_ids = vec![]; + for row in &mut rows { if let Id::Id(id) = row.id { if self.selected_torrents_ids.contains(&id) { row.is_selected = true; + found_ids.push(id); } } } + + let new_selected: Vec<_> = self + .selected_torrents_ids + .iter() + .cloned() + .filter(|id| found_ids.contains(id)) + .collect(); + + self.selected_torrents_ids = new_selected; } self.table.set_items(rows); diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index 497e76b..4df8961 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -6,6 +6,7 @@ use rm_shared::{ action::{Action, UpdateAction}, status_task::StatusTask, }; +use transmission_rpc::types::Id; use crate::tui::{ app, @@ -142,9 +143,12 @@ impl TaskManager { self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } - pub fn delete_torrent(&mut self, torrent: &RustmissionTorrent) { - self.current_task = - CurrentTask::Delete(tasks::Delete::new(self.ctx.clone(), vec![torrent.clone()])); + pub fn delete_torrents(&mut self, torrents: Vec, name_of_first: String) { + self.current_task = CurrentTask::Delete(tasks::Delete::new( + self.ctx.clone(), + torrents, + name_of_first, + )); self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } @@ -207,4 +211,12 @@ impl TaskManager { self.ctx.send_update_action(UpdateAction::CancelTorrentTask); } + + pub fn is_finished_status_task(&self) -> bool { + if let CurrentTask::Status(task) = &self.current_task { + !matches!(task.task_status, CurrentTaskState::Loading(_)) + } else { + false + } + } } diff --git a/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs index 016ac40..4b0de0b 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs @@ -5,44 +5,45 @@ use transmission_rpc::types::Id; use crate::transmission::TorrentAction; use crate::tui::app; use crate::tui::components::{Component, ComponentAction, InputManager}; -use crate::tui::tabs::torrents::rustmission_torrent::RustmissionTorrent; use rm_shared::action::{Action, UpdateAction}; use rm_shared::status_task::StatusTask; pub struct Delete { - torrents_to_delete: Vec, - ctx: app::Ctx, - input_mgr: InputManager, delete_with_files: bool, + torrents_to_delete: Vec, + name_of_first: String, + input_mgr: InputManager, + ctx: app::Ctx, } impl Delete { - pub fn new(ctx: app::Ctx, to_delete: Vec) -> Self { + pub fn new(ctx: app::Ctx, to_delete: Vec, name_of_first: String) -> Self { let prompt = String::from("Delete selected with files? (Y/n) "); Self { + delete_with_files: false, torrents_to_delete: to_delete, + name_of_first, input_mgr: InputManager::new(prompt), ctx, - delete_with_files: false, } } fn delete(&self) { - let torrents_to_delete: Vec = self - .torrents_to_delete - .iter() - .map(|x| x.id.clone()) - .collect(); if self.delete_with_files { self.ctx - .send_torrent_action(TorrentAction::DelWithFiles(torrents_to_delete)) + .send_torrent_action(TorrentAction::DelWithFiles(self.torrents_to_delete.clone())) } else { - self.ctx - .send_torrent_action(TorrentAction::DelWithoutFiles(torrents_to_delete)) + self.ctx.send_torrent_action(TorrentAction::DelWithoutFiles( + self.torrents_to_delete.clone(), + )) } - let task = StatusTask::new_del(self.torrents_to_delete[0].torrent_name.clone()); + let task = if self.torrents_to_delete.len() == 1 { + StatusTask::new_del(self.name_of_first.clone()) + } else { + StatusTask::new_del(self.torrents_to_delete.len().to_string()) + }; self.ctx .send_update_action(UpdateAction::StatusTaskSet(task)); } diff --git a/rm-shared/src/status_task.rs b/rm-shared/src/status_task.rs index 1de1d8b..ae9f731 100644 --- a/rm-shared/src/status_task.rs +++ b/rm-shared/src/status_task.rs @@ -54,15 +54,15 @@ impl StatusTask { let truncated = truncated_str(&self.what, 60); match self.task_type { - TaskType::Add => format!("Added {truncated}"), - TaskType::Delete => format!("Deleted {truncated}"), - TaskType::Move => format!("Moved {truncated}"), - TaskType::Open => format!("Opened {truncated}"), + TaskType::Add => format!(" Added {truncated}"), + TaskType::Delete => format!(" Deleted {truncated}"), + TaskType::Move => format!(" Moved {truncated}"), + TaskType::Open => format!(" Opened {truncated}"), TaskType::ChangeCategory => { if truncated.is_empty() { - "Categories cleared!".to_string() + " Categories cleared!".to_string() } else { - format!("Category set to {truncated}!") + format!(" Category set to {truncated}!") } } } @@ -72,11 +72,11 @@ impl StatusTask { let truncated = truncated_str(&self.what, 60); match self.task_type { - TaskType::Add => format!("Error adding {truncated}"), - TaskType::Delete => format!("Error deleting {truncated}"), - TaskType::Move => format!("Error moving to {truncated}"), - TaskType::Open => format!("Error opening {truncated}"), - TaskType::ChangeCategory => format!("Error changing category to {truncated}"), + TaskType::Add => format!(" Error adding {truncated}"), + TaskType::Delete => format!(" Error deleting {truncated}"), + TaskType::Move => format!(" Error moving to {truncated}"), + TaskType::Open => format!(" Error opening {truncated}"), + TaskType::ChangeCategory => format!(" Error changing category to {truncated}"), } } @@ -84,11 +84,11 @@ impl StatusTask { let truncated = truncated_str(&self.what, 60); match self.task_type { - TaskType::Add => format!("Adding {truncated}"), - TaskType::Delete => format!("Deleting {truncated}"), - TaskType::Move => format!("Moving {truncated}"), - TaskType::Open => format!("Opening {truncated}"), - TaskType::ChangeCategory => format!("Changing category to {truncated}"), + TaskType::Add => format!(" Adding {truncated}"), + TaskType::Delete => format!(" Deleting {truncated}"), + TaskType::Move => format!(" Moving {truncated}"), + TaskType::Open => format!(" Opening {truncated}"), + TaskType::ChangeCategory => format!(" Changing category to {truncated}"), } } } From 45f591e7f80b1f1413e94b3d461ede4b9d6426e7 Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 12:37:13 +0200 Subject: [PATCH 6/9] add TODO --- rm-main/src/tui/tabs/torrents/task_manager.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index 4df8961..f7c7392 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -21,6 +21,13 @@ use super::{ pub struct TaskManager { ctx: app::Ctx, current_task: CurrentTask, + // TODO: + // Put Default task in a seperate field and merge it with Status task + // and maybe with Selection task (or even Sort?). + // This way there won't be any edge cases in torrents/mod.rs anymore + // when dealing with TaskManager. + // Default task would keep the state info whether there are any tasks + // happening, whether the user is selecting torrents or is sorting them. } impl TaskManager { From 42ec00b676f9de34c0cb861297455bf475d31d9d Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 15:05:33 +0200 Subject: [PATCH 7/9] make it work with the rest of the tasks --- rm-main/src/tui/tabs/torrents/mod.rs | 24 +++++++++------- rm-main/src/tui/tabs/torrents/task_manager.rs | 28 ++++++------------- .../tabs/torrents/tasks/change_category.rs | 11 ++++---- .../tui/tabs/torrents/tasks/delete_torrent.rs | 21 +++++++------- rm-main/src/tui/tabs/torrents/tasks/mod.rs | 15 ++++++++++ .../tui/tabs/torrents/tasks/move_torrent.rs | 12 ++++---- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index 0ba2f45..6c6c1ed 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -16,6 +16,7 @@ use ratatui::widgets::{Cell, Row, Table}; use rm_config::CONFIG; use rm_shared::status_task::StatusTask; use rustmission_torrent::RustmissionTorrent; +use tasks::TorrentSelection; use transmission_rpc::types::{Id, TorrentStatus}; use crate::transmission; @@ -148,8 +149,8 @@ impl Component for TorrentsTab { } A::Pause => self.pause_current_torrent(), A::Delete => { - if let Some((ids, first_name)) = self.get_torrents_for_a_task() { - self.task_manager.delete_torrents(ids, first_name); + if let Some(torrent_selection) = self.get_currently_selected() { + self.task_manager.delete_torrents(torrent_selection); } } A::AddMagnet => self.task_manager.add_magnet(), @@ -161,13 +162,14 @@ impl Component for TorrentsTab { .map(|f| f.pattern.clone()), ), A::MoveTorrent => { - if let Some(torrent) = self.table_manager.current_torrent() { - self.task_manager.move_torrent(torrent); + if let Some(selection) = self.get_currently_selected() { + self.task_manager + .move_torrent(selection, self.ctx.session_info.download_dir.clone()); } } A::ChangeCategory => { - if let Some(torrent) = self.table_manager.current_torrent() { - self.task_manager.change_category(torrent); + if let Some(selection) = self.get_currently_selected() { + self.task_manager.change_category(selection); } } A::XdgOpen => self.xdg_open_current_torrent(), @@ -318,20 +320,22 @@ impl TorrentsTab { ); } - fn get_torrents_for_a_task(&mut self) -> Option<(Vec, String)> { + fn get_currently_selected(&mut self) -> Option { if !self.table_manager.selected_torrents_ids.is_empty() { - Some(( + Some(TorrentSelection::Many( self.table_manager .selected_torrents_ids .clone() .into_iter() .map(|id| Id::Id(id)) .collect(), - "".to_string(), )) } else { if let Some(t) = self.table_manager.current_torrent() { - Some((vec![t.id.clone()], t.torrent_name.to_string())) + Some(TorrentSelection::Single( + t.id.clone(), + t.torrent_name.to_string(), + )) } else { None } diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index f7c7392..05259b6 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -6,7 +6,6 @@ use rm_shared::{ action::{Action, UpdateAction}, status_task::StatusTask, }; -use transmission_rpc::types::Id; use crate::tui::{ app, @@ -15,7 +14,7 @@ use crate::tui::{ use super::{ rustmission_torrent::RustmissionTorrent, - tasks::{self, CurrentTaskState}, + tasks::{self, CurrentTaskState, TorrentSelection}, }; pub struct TaskManager { @@ -150,29 +149,20 @@ impl TaskManager { self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } - pub fn delete_torrents(&mut self, torrents: Vec, name_of_first: String) { - self.current_task = CurrentTask::Delete(tasks::Delete::new( - self.ctx.clone(), - torrents, - name_of_first, - )); + pub fn delete_torrents(&mut self, selection: TorrentSelection) { + self.current_task = CurrentTask::Delete(tasks::Delete::new(self.ctx.clone(), selection)); self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } - pub fn move_torrent(&mut self, torrent: &RustmissionTorrent) { - self.current_task = CurrentTask::Move(tasks::Move::new( - self.ctx.clone(), - vec![torrent.id.clone()], - torrent.download_dir.to_string(), - )); + pub fn move_torrent(&mut self, selection: TorrentSelection, current_dir: String) { + self.current_task = + CurrentTask::Move(tasks::Move::new(self.ctx.clone(), selection, current_dir)); self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } - pub fn change_category(&mut self, torrent: &RustmissionTorrent) { - self.current_task = CurrentTask::ChangeCategory(tasks::ChangeCategory::new( - self.ctx.clone(), - vec![torrent.id.clone()], - )); + pub fn change_category(&mut self, selection: TorrentSelection) { + self.current_task = + CurrentTask::ChangeCategory(tasks::ChangeCategory::new(self.ctx.clone(), selection)); self.ctx.send_update_action(UpdateAction::SwitchToInputMode); } diff --git a/rm-main/src/tui/tabs/torrents/tasks/change_category.rs b/rm-main/src/tui/tabs/torrents/tasks/change_category.rs index a4efc4a..bf89b74 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/change_category.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/change_category.rs @@ -5,25 +5,26 @@ use rm_shared::{ action::{Action, UpdateAction}, status_task::StatusTask, }; -use transmission_rpc::types::Id; use crate::tui::{ app, components::{Component, ComponentAction, InputManager}, }; +use super::TorrentSelection; + pub struct ChangeCategory { - torrents_to_change: Vec, + selection: TorrentSelection, ctx: app::Ctx, input_mgr: InputManager, } impl ChangeCategory { - pub fn new(ctx: app::Ctx, torrents_to_change: Vec) -> Self { + pub fn new(ctx: app::Ctx, selection: TorrentSelection) -> Self { let prompt = "New category: ".to_string(); Self { - torrents_to_change, + selection, input_mgr: InputManager::new(prompt) .autocompletions(CONFIG.categories.map.keys().cloned().collect()), ctx, @@ -35,7 +36,7 @@ impl ChangeCategory { let category = self.input_mgr.text(); self.ctx .send_torrent_action(crate::transmission::TorrentAction::ChangeCategory( - self.torrents_to_change.clone(), + self.selection.ids(), category.clone(), )); diff --git a/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs index 4b0de0b..aa6dc72 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/delete_torrent.rs @@ -1,6 +1,5 @@ use crossterm::event::KeyCode; use ratatui::prelude::*; -use transmission_rpc::types::Id; use crate::transmission::TorrentAction; use crate::tui::app; @@ -8,22 +7,22 @@ use crate::tui::components::{Component, ComponentAction, InputManager}; use rm_shared::action::{Action, UpdateAction}; use rm_shared::status_task::StatusTask; +use super::TorrentSelection; + pub struct Delete { delete_with_files: bool, - torrents_to_delete: Vec, - name_of_first: String, + torrents_to_delete: TorrentSelection, input_mgr: InputManager, ctx: app::Ctx, } impl Delete { - pub fn new(ctx: app::Ctx, to_delete: Vec, name_of_first: String) -> Self { + pub fn new(ctx: app::Ctx, to_delete: TorrentSelection) -> Self { let prompt = String::from("Delete selected with files? (Y/n) "); Self { delete_with_files: false, torrents_to_delete: to_delete, - name_of_first, input_mgr: InputManager::new(prompt), ctx, } @@ -32,18 +31,18 @@ impl Delete { fn delete(&self) { if self.delete_with_files { self.ctx - .send_torrent_action(TorrentAction::DelWithFiles(self.torrents_to_delete.clone())) + .send_torrent_action(TorrentAction::DelWithFiles(self.torrents_to_delete.ids())) } else { self.ctx.send_torrent_action(TorrentAction::DelWithoutFiles( - self.torrents_to_delete.clone(), + self.torrents_to_delete.ids(), )) } - let task = if self.torrents_to_delete.len() == 1 { - StatusTask::new_del(self.name_of_first.clone()) - } else { - StatusTask::new_del(self.torrents_to_delete.len().to_string()) + let task = match &self.torrents_to_delete { + TorrentSelection::Single(_, name) => StatusTask::new_del(name.clone()), + TorrentSelection::Many(ids) => StatusTask::new_del(ids.len().to_string()), }; + self.ctx .send_update_action(UpdateAction::StatusTaskSet(task)); } diff --git a/rm-main/src/tui/tabs/torrents/tasks/mod.rs b/rm-main/src/tui/tabs/torrents/tasks/mod.rs index edf6291..2668c2c 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/mod.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/mod.rs @@ -17,3 +17,18 @@ pub use move_torrent::Move; pub use selection::Selection; pub use sort::Sort; pub use status::{CurrentTaskState, Status}; +use transmission_rpc::types::Id; + +pub enum TorrentSelection { + Single(Id, String), + Many(Vec), +} + +impl TorrentSelection { + pub fn ids(&self) -> Vec { + match self { + TorrentSelection::Single(id, _) => vec![id.clone()], + TorrentSelection::Many(ids) => ids.clone(), + } + } +} diff --git a/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs b/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs index e4102bd..4ebe4e2 100644 --- a/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs +++ b/rm-main/src/tui/tabs/torrents/tasks/move_torrent.rs @@ -4,7 +4,6 @@ use rm_shared::{ action::{Action, UpdateAction}, status_task::StatusTask, }; -use transmission_rpc::types::Id; use crate::{ transmission::TorrentAction, @@ -14,18 +13,20 @@ use crate::{ }, }; +use super::TorrentSelection; + pub struct Move { - torrents_to_move: Vec, + selection: TorrentSelection, ctx: app::Ctx, input_mgr: InputManager, } impl Move { - pub fn new(ctx: app::Ctx, torrents_to_move: Vec, existing_location: String) -> Self { + pub fn new(ctx: app::Ctx, selection: TorrentSelection, existing_location: String) -> Self { let prompt = "New directory: ".to_string(); Self { - torrents_to_move, + selection, input_mgr: InputManager::new_with_value(prompt, existing_location), ctx, } @@ -34,9 +35,8 @@ impl Move { fn handle_input(&mut self, input: KeyEvent) -> ComponentAction { if input.code == KeyCode::Enter { let new_location = self.input_mgr.text(); - let torrents_to_move = self.torrents_to_move.clone(); - let torrent_action = TorrentAction::Move(torrents_to_move, new_location.clone()); + let torrent_action = TorrentAction::Move(self.selection.ids(), new_location.clone()); self.ctx.send_torrent_action(torrent_action); let task = StatusTask::new_move(new_location); From e402a73363f26b51f9e94c20475ecff50e11a713 Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 15:14:23 +0200 Subject: [PATCH 8/9] fix bug when deleting torrents --- rm-main/src/tui/tabs/torrents/mod.rs | 7 +++++++ rm-main/src/tui/tabs/torrents/task_manager.rs | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index 6c6c1ed..a843f9a 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -213,7 +213,14 @@ impl Component for TorrentsTab { } UpdateAction::UpdateTorrents(torrents) => { let torrents = torrents.into_iter().map(RustmissionTorrent::from).collect(); + self.table_manager.set_new_rows(torrents); + if self.table_manager.selected_torrents_ids.is_empty() + && self.task_manager.is_selection_task() + { + self.task_manager.default() + } + self.bottom_stats .update_selected_indicator(&self.table_manager); } diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index 05259b6..d1a1ec4 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -216,4 +216,8 @@ impl TaskManager { false } } + + pub fn is_selection_task(&self) -> bool { + matches!(self.current_task, CurrentTask::Selection(_)) + } } From 1399401d3774d850514cdf2d5093ad6e84843a4c Mon Sep 17 00:00:00 2001 From: Remigiusz Micielski Date: Fri, 6 Sep 2024 15:21:27 +0200 Subject: [PATCH 9/9] apply clippy --- rm-config/src/keymap/mod.rs | 2 +- rm-main/src/tui/tabs/torrents/mod.rs | 38 +++++++++---------- rm-main/src/tui/tabs/torrents/task_manager.rs | 5 +-- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/rm-config/src/keymap/mod.rs b/rm-config/src/keymap/mod.rs index 0f3522c..8502b8f 100644 --- a/rm-config/src/keymap/mod.rs +++ b/rm-config/src/keymap/mod.rs @@ -53,7 +53,7 @@ impl + Ord + UserAction> KeybindsHolder { } keys.entry(&keybinding.action) - .or_insert_with(Vec::new) + .or_default() .push(keybinding.keycode_string()); } diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs index a843f9a..a07abce 100644 --- a/rm-main/src/tui/tabs/torrents/mod.rs +++ b/rm-main/src/tui/tabs/torrents/mod.rs @@ -106,18 +106,16 @@ impl Component for TorrentsTab { return ComponentAction::Nothing; } - if !self.table_manager.selected_torrents_ids.is_empty() { - if action.is_soft_quit() { - self.table_manager - .table - .items - .iter_mut() - .for_each(|t| t.is_selected = false); - self.table_manager.selected_torrents_ids.drain(..); - self.task_manager.default(); - self.ctx.send_action(Action::Render); - return ComponentAction::Nothing; - } + if !self.table_manager.selected_torrents_ids.is_empty() && action.is_soft_quit() { + self.table_manager + .table + .items + .iter_mut() + .for_each(|t| t.is_selected = false); + self.table_manager.selected_torrents_ids.drain(..); + self.task_manager.default(); + self.ctx.send_action(Action::Render); + return ComponentAction::Nothing; } if action.is_quit() { @@ -334,18 +332,16 @@ impl TorrentsTab { .selected_torrents_ids .clone() .into_iter() - .map(|id| Id::Id(id)) + .map(Id::Id) .collect(), )) + } else if let Some(t) = self.table_manager.current_torrent() { + Some(TorrentSelection::Single( + t.id.clone(), + t.torrent_name.to_string(), + )) } else { - if let Some(t) = self.table_manager.current_torrent() { - Some(TorrentSelection::Single( - t.id.clone(), - t.torrent_name.to_string(), - )) - } else { - None - } + None } } diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs index d1a1ec4..6eacca3 100644 --- a/rm-main/src/tui/tabs/torrents/task_manager.rs +++ b/rm-main/src/tui/tabs/torrents/task_manager.rs @@ -12,10 +12,7 @@ use crate::tui::{ components::{Component, ComponentAction}, }; -use super::{ - rustmission_torrent::RustmissionTorrent, - tasks::{self, CurrentTaskState, TorrentSelection}, -}; +use super::tasks::{self, CurrentTaskState, TorrentSelection}; pub struct TaskManager { ctx: app::Ctx,