From fc6c2ab20d8acba9c7a562cf89d5aa6b967eb982 Mon Sep 17 00:00:00 2001
From: Remigiusz Micielski <rmicielski@purelymail.com>
Date: Sat, 26 Oct 2024 15:03:01 +0200
Subject: [PATCH 1/2] feat: renaming torrents

---
 rm-config/defaults/keymap.toml                |  1 +
 rm-config/src/keymap/actions/torrents_tab.rs  |  3 +
 rm-main/src/transmission/action.rs            | 18 +++++
 rm-main/src/tui/tabs/torrents/mod.rs          |  6 ++
 rm-main/src/tui/tabs/torrents/task_manager.rs | 14 ++++
 rm-main/src/tui/tabs/torrents/tasks/mod.rs    |  2 +
 rm-main/src/tui/tabs/torrents/tasks/rename.rs | 74 +++++++++++++++++++
 rm-shared/src/action.rs                       |  1 +
 rm-shared/src/status_task.rs                  | 11 +++
 9 files changed, 130 insertions(+)
 create mode 100644 rm-main/src/tui/tabs/torrents/tasks/rename.rs

diff --git a/rm-config/defaults/keymap.toml b/rm-config/defaults/keymap.toml
index a873e3c..b5750c6 100644
--- a/rm-config/defaults/keymap.toml
+++ b/rm-config/defaults/keymap.toml
@@ -43,6 +43,7 @@ keybindings = [
 keybindings = [
   { on = "a", action = "AddMagnet" },
   { on = "m", action = "MoveTorrent" },
+  { on = "r", action = "Rename" },
   { on = "c", action = "ChangeCategory" },
   { on = "p", action = "Pause" },
   { on = "f", action = "ShowFiles" },
diff --git a/rm-config/src/keymap/actions/torrents_tab.rs b/rm-config/src/keymap/actions/torrents_tab.rs
index d783fc3..858e58f 100644
--- a/rm-config/src/keymap/actions/torrents_tab.rs
+++ b/rm-config/src/keymap/actions/torrents_tab.rs
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
 pub enum TorrentsAction {
     AddMagnet,
     MoveTorrent,
+    Rename,
     Pause,
     Delete,
     ShowFiles,
@@ -23,6 +24,7 @@ impl UserAction for TorrentsAction {
             TorrentsAction::ShowFiles => "show files",
             TorrentsAction::ShowStats => "show statistics",
             TorrentsAction::ChangeCategory => "change category",
+            TorrentsAction::Rename => "rename torrent path",
         }
     }
 }
@@ -37,6 +39,7 @@ impl From<TorrentsAction> for Action {
             TorrentsAction::ShowFiles => Action::ShowFiles,
             TorrentsAction::ShowStats => Action::ShowStats,
             TorrentsAction::ChangeCategory => Action::ChangeCategory,
+            TorrentsAction::Rename => Action::Rename,
         }
     }
 }
diff --git a/rm-main/src/transmission/action.rs b/rm-main/src/transmission/action.rs
index 0bf2c99..99a8539 100644
--- a/rm-main/src/transmission/action.rs
+++ b/rm-main/src/transmission/action.rs
@@ -23,6 +23,8 @@ pub enum TorrentAction {
     Start(Vec<Id>),
     // Torrent ID, Directory to move to
     Move(Vec<Id>, String),
+    // Torrent ID, Current name, Name to change to
+    Rename(Id, String, String),
     // Torrent ID, Category to set
     ChangeCategory(Vec<Id>, String),
     // Delete Torrents with these given IDs (without files)
@@ -231,6 +233,22 @@ pub async fn action_handler(
                     }
                 }
             }
+            TorrentAction::Rename(id, current_name, new_name) => {
+                match client
+                    .torrent_rename_path(vec![id], current_name, new_name)
+                    .await
+                {
+                    Ok(_) => action_tx.send(UpdateAction::StatusTaskSuccess).unwrap(),
+                    Err(err) => {
+                        let msg = "Failed to rename a torrent";
+                        let err_message = ErrorMessage::new(FAILED_TO_COMMUNICATE, msg, err);
+                        action_tx
+                            .send(UpdateAction::Error(Box::new(err_message)))
+                            .unwrap();
+                        action_tx.send(UpdateAction::StatusTaskFailure).unwrap();
+                    }
+                }
+            }
         }
     }
 }
diff --git a/rm-main/src/tui/tabs/torrents/mod.rs b/rm-main/src/tui/tabs/torrents/mod.rs
index 52e7930..60578f8 100644
--- a/rm-main/src/tui/tabs/torrents/mod.rs
+++ b/rm-main/src/tui/tabs/torrents/mod.rs
@@ -151,6 +151,12 @@ impl Component for TorrentsTab {
                     self.task_manager.delete_torrents(torrent_selection);
                 }
             }
+            A::Rename => {
+                if let Some(TorrentSelection::Single(id, curr_name)) = self.get_currently_selected()
+                {
+                    self.task_manager.rename(id, curr_name);
+                }
+            }
             A::AddMagnet => self.task_manager.add_magnet(),
             A::Search => self.task_manager.search(
                 &self
diff --git a/rm-main/src/tui/tabs/torrents/task_manager.rs b/rm-main/src/tui/tabs/torrents/task_manager.rs
index e11a515..1b693d8 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,
@@ -45,6 +46,7 @@ pub enum CurrentTask {
     Status(tasks::Status),
     Sort(tasks::Sort),
     Selection(tasks::Selection),
+    Rename(tasks::Rename),
 }
 
 impl CurrentTask {
@@ -89,6 +91,11 @@ impl Component for TaskManager {
                     self.cancel_task()
                 }
             }
+            CurrentTask::Rename(rename_bar) => {
+                if rename_bar.handle_actions(action).is_quit() {
+                    self.cancel_task()
+                }
+            }
             CurrentTask::Default(_) => (),
             CurrentTask::Sort(_) => (),
             CurrentTask::Selection(_) => (),
@@ -126,6 +133,7 @@ impl Component for TaskManager {
             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),
+            CurrentTask::Rename(rename_bar) => rename_bar.render(f, rect),
         }
     }
 
@@ -146,6 +154,12 @@ impl TaskManager {
         self.ctx.send_update_action(UpdateAction::SwitchToInputMode);
     }
 
+    pub fn rename(&mut self, id: Id, curr_name: String) {
+        self.current_task =
+            CurrentTask::Rename(tasks::Rename::new(self.ctx.clone(), id, curr_name));
+        self.ctx.send_update_action(UpdateAction::SwitchToInputMode);
+    }
+
     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);
diff --git a/rm-main/src/tui/tabs/torrents/tasks/mod.rs b/rm-main/src/tui/tabs/torrents/tasks/mod.rs
index 2668c2c..d5c1241 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 rename;
 mod selection;
 mod sort;
 mod status;
@@ -14,6 +15,7 @@ pub use default::Default;
 pub use delete_torrent::Delete;
 pub use filter::Filter;
 pub use move_torrent::Move;
+pub use rename::Rename;
 pub use selection::Selection;
 pub use sort::Sort;
 pub use status::{CurrentTaskState, Status};
diff --git a/rm-main/src/tui/tabs/torrents/tasks/rename.rs b/rm-main/src/tui/tabs/torrents/tasks/rename.rs
new file mode 100644
index 0000000..48c0fd0
--- /dev/null
+++ b/rm-main/src/tui/tabs/torrents/tasks/rename.rs
@@ -0,0 +1,74 @@
+use crossterm::event::KeyCode;
+use ratatui::{prelude::*, Frame};
+use rm_shared::{
+    action::{Action, UpdateAction},
+    status_task::StatusTask,
+};
+use transmission_rpc::types::Id;
+
+use crate::{
+    transmission::TorrentAction,
+    tui::{
+        app,
+        components::{Component, ComponentAction, InputManager},
+    },
+};
+
+pub struct Rename {
+    id: Id,
+    curr_name: String,
+    input_mgr: InputManager,
+    ctx: app::Ctx,
+}
+
+impl Rename {
+    pub fn new(ctx: app::Ctx, to_rename: Id, curr_name: String) -> Self {
+        let prompt = String::from("New name ");
+
+        Self {
+            id: to_rename,
+            ctx,
+            input_mgr: InputManager::new_with_value(prompt, curr_name.clone()),
+            curr_name,
+        }
+    }
+
+    fn rename(&self) {
+        let task = StatusTask::new_rename(self.curr_name.clone());
+
+        self.ctx
+            .send_update_action(UpdateAction::StatusTaskSet(task));
+        self.ctx.send_torrent_action(TorrentAction::Rename(
+            self.id.clone(),
+            self.curr_name.clone(),
+            self.input_mgr.text(),
+        ))
+    }
+}
+
+impl Component for Rename {
+    fn handle_actions(&mut self, action: Action) -> crate::tui::components::ComponentAction {
+        match action {
+            Action::Input(input) => {
+                if input.code == KeyCode::Esc {
+                    return ComponentAction::Quit;
+                } else if input.code == KeyCode::Enter {
+                    self.rename();
+                    return ComponentAction::Quit;
+                }
+
+                if self.input_mgr.handle_key(input).is_some() {
+                    self.ctx.send_action(Action::Render);
+                }
+
+                ComponentAction::Nothing
+            }
+
+            _ => ComponentAction::Nothing,
+        }
+    }
+
+    fn render(&mut self, f: &mut Frame, rect: Rect) {
+        self.input_mgr.render(f, rect)
+    }
+}
diff --git a/rm-shared/src/action.rs b/rm-shared/src/action.rs
index 9c5ee72..ea21076 100644
--- a/rm-shared/src/action.rs
+++ b/rm-shared/src/action.rs
@@ -41,6 +41,7 @@ pub enum Action {
     AddMagnet,
     MoveTorrent,
     ChangeCategory,
+    Rename,
     // Search Tab
     ShowProvidersInfo,
 }
diff --git a/rm-shared/src/status_task.rs b/rm-shared/src/status_task.rs
index ae9f731..0e99bb3 100644
--- a/rm-shared/src/status_task.rs
+++ b/rm-shared/src/status_task.rs
@@ -9,6 +9,7 @@ pub struct StatusTask {
 enum TaskType {
     Add,
     Delete,
+    Rename,
     Move,
     Open,
     ChangeCategory,
@@ -22,6 +23,13 @@ impl StatusTask {
         }
     }
 
+    pub fn new_rename(what: impl Into<String>) -> Self {
+        StatusTask {
+            task_type: TaskType::Rename,
+            what: what.into(),
+        }
+    }
+
     pub fn new_del(what: impl Into<String>) -> Self {
         StatusTask {
             task_type: TaskType::Delete,
@@ -65,6 +73,7 @@ impl StatusTask {
                     format!(" Category set to {truncated}!")
                 }
             }
+            TaskType::Rename => format!("Renamed {truncated}"),
         }
     }
 
@@ -77,6 +86,7 @@ impl StatusTask {
             TaskType::Move => format!(" Error moving to {truncated}"),
             TaskType::Open => format!(" Error opening {truncated}"),
             TaskType::ChangeCategory => format!(" Error changing category to {truncated}"),
+            TaskType::Rename => format!(" Error renaming {truncated}"),
         }
     }
 
@@ -89,6 +99,7 @@ impl StatusTask {
             TaskType::Move => format!(" Moving {truncated}"),
             TaskType::Open => format!(" Opening {truncated}"),
             TaskType::ChangeCategory => format!(" Changing category to {truncated}"),
+            TaskType::Rename => format!(" Renaming {truncated}"),
         }
     }
 }

From fd96069bae751dc8743aeb8470f40b6c8582b924 Mon Sep 17 00:00:00 2001
From: Remigiusz Micielski <rmicielski@purelymail.com>
Date: Sat, 26 Oct 2024 15:09:06 +0200
Subject: [PATCH 2/2] add rename to details

---
 rm-main/src/tui/tabs/torrents/popups/details.rs | 16 ++++++++++++++++
 rm-main/src/tui/tabs/torrents/tasks/rename.rs   |  8 +++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/rm-main/src/tui/tabs/torrents/popups/details.rs b/rm-main/src/tui/tabs/torrents/popups/details.rs
index 2b2f523..564f852 100644
--- a/rm-main/src/tui/tabs/torrents/popups/details.rs
+++ b/rm-main/src/tui/tabs/torrents/popups/details.rs
@@ -37,6 +37,10 @@ impl Component for DetailsPopup {
                 self.ctx.send_action(Action::ShowFiles);
                 ComponentAction::Quit
             }
+            Action::Rename => {
+                self.ctx.send_action(Action::Rename);
+                ComponentAction::Quit
+            }
             Action::ChangeCategory => {
                 self.ctx.send_action(Action::ChangeCategory);
                 ComponentAction::Quit
@@ -103,6 +107,17 @@ impl Component for DetailsPopup {
             keybinding_style(),
         ));
 
+        let mut rename_line = Line::default();
+        rename_line.push_span(Span::raw("Rename: "));
+        rename_line.push_span(Span::styled(
+            CONFIG
+                .keybindings
+                .torrents_tab
+                .get_keys_for_action_joined(TorrentsAction::Rename)
+                .unwrap_or_default(),
+            keybinding_style(),
+        ));
+
         let mut delete_line = Line::default();
         delete_line.push_span(Span::raw("Delete: "));
         delete_line.push_span(Span::styled(
@@ -158,6 +173,7 @@ impl Component for DetailsPopup {
         lines.push(padding_line);
         lines.push(delete_line);
         lines.push(show_files_line);
+        lines.push(rename_line);
         lines.push(move_location_line);
         lines.push(change_category_line);
 
diff --git a/rm-main/src/tui/tabs/torrents/tasks/rename.rs b/rm-main/src/tui/tabs/torrents/tasks/rename.rs
index 48c0fd0..cde4143 100644
--- a/rm-main/src/tui/tabs/torrents/tasks/rename.rs
+++ b/rm-main/src/tui/tabs/torrents/tasks/rename.rs
@@ -23,7 +23,7 @@ pub struct Rename {
 
 impl Rename {
     pub fn new(ctx: app::Ctx, to_rename: Id, curr_name: String) -> Self {
-        let prompt = String::from("New name ");
+        let prompt = String::from("New name: ");
 
         Self {
             id: to_rename,
@@ -34,6 +34,12 @@ impl Rename {
     }
 
     fn rename(&self) {
+        let new_name = self.input_mgr.text();
+
+        if self.curr_name == new_name {
+            return;
+        }
+
         let task = StatusTask::new_rename(self.curr_name.clone());
 
         self.ctx