From 8cff913f43281bb09ffbea0ccd70935a52d4b21f Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Fri, 5 Jul 2024 17:12:23 +0000
Subject: [PATCH 01/10] fix: resize not triggering render (#57)
---
rm-shared/src/action.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/rm-shared/src/action.rs b/rm-shared/src/action.rs
index 56cfa1a..af86c06 100644
--- a/rm-shared/src/action.rs
+++ b/rm-shared/src/action.rs
@@ -92,6 +92,7 @@ pub fn event_to_action(
}
keymap.get(&(key.code, key.modifiers)).cloned()
}
+ Event::Resize(_, _) => Some(A::Render),
_ => None,
}
}
From f5c14ade8d09eed6295cd88e163b5360ce85c3a1 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Sat, 6 Jul 2024 11:55:24 +0000
Subject: [PATCH 02/10] docs: revamp README (#56)
---
README.md | 63 ++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 44 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index ee920eb..1bff434 100644
--- a/README.md
+++ b/README.md
@@ -1,47 +1,62 @@
+
+
+**Rustmission**
+
+Performant TUI client for Transmission daemon capable of managing hundreds of torrents.
+It boasts a rich feature set that surpasses many other clients, offering a seamless torrenting experience :3
+
+
+#
-
Rustmission
+
- TUI for the Transmission daemon
-
-
-
- ⚠️ I DO NOT PIRATE MOVIES. THE TORRENTS YOU SEE IN THIS IMAGE ARE SAMPLE DATA FABRICATED BY ARTIFICIAL INTELLIGENCE. I DO NOT CONDONE PIRACY, AND I WOULD NEVER, EVER DO SUCH THING
+ ⚠️ Torrents you see are just samples fabricated by AI. Piracy is not something we tolerate here at Intuis.
## Features
-- **Built-in Search**: Seamlessly search for magnets directly. This is a killer feature of Rustmission.
-- **Async Rust**: Utilizes Rust's async/await syntax for efficient, non-blocking IO operations.
-- **Basic Operations**: Allows to add, pause, remove, fuzzy filter your torrents.
+- **Basic operations**: Allows to add, pause, remove, fuzzy filter your torrents.
+- **Built-in magnet search**: Search for magnets without leaving your terminal.
+- **Asynchronous**: UI is always responsive.
+- **RSS**: Fetch torrents automatically with a cronjob using `--fetch-rss`
+
+## Requirements
+
+- Running [Transmission](https://github.com/transmission/transmission) daemon and its IP address
+- [Nerd Fonts](https://www.nerdfonts.com/)
## Installation
+
+
+
+
To install Rustmission, ensure you have Rust and Cargo installed on your system, and then run:
```bash
cargo install rustmission
```
-or with Nix:
+or with Nix ( :heart: [@0x61nas](https://github.com/0x61nas) ):
```bash
-nix run .
+nix-shell -p rustmission
```
-or with Brew:
+or with Brew ( :heart: [@aidanaden](https://github.com/aidanaden) ):
```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 '?'.
+Run `rustmission` in your terminal to initialize the config and make adjustments as needed. Subsequently, run `rustmission` again. For a list of keybindings, press `?` or `F1`.
## Configuration
-Rustmission stores its configuration in a TOML file located at ~/.config/rustmission/config.toml by default. You can modify this file to
-set the daemon's IP address.
+Rustmission stores its configuration in a TOML file located at `~/.config/rustmission/config.toml` by default. You can modify this file to
+set the daemon's address.
```toml
[general]
@@ -53,18 +68,28 @@ auto_hide = false
# It can also be a hex, e.g. "#3cb371"
accent_color = "LightMagenta"
-# If enabled, shows various keybindings throughout the program at the cost of a
-# little bit cluttered interface.
+# If enabled, shows various keybindings throughout the program at the cost of
+# a little bit cluttered interface.
beginner_mode = true
+# If enabled, hides header row of torrents tab
+headers_hide = false
+
[connection]
url = "http://CHANGE_ME:9091/transmission/rpc" # REQUIRED!
# If you need username and password to authenticate:
# username = "CHANGE_ME"
# password = "CHANGE_ME"
+
+# Refresh timings (in seconds)
+torrents_refresh = 5
+stats_refresh = 10
+free_space_refresh = 10
```
+There's also a self-documenting keymap config located at `~/.config/rustmission/keymap.toml` with sane defaults.
+
## Alternatives
- [Transgression](https://github.com/PanAeon/transg-tui)
- [tremc](https://github.com/tremc/tremc)
@@ -72,5 +97,5 @@ url = "http://CHANGE_ME:9091/transmission/rpc" # REQUIRED!
- [stig](https://github.com/rndusr/stig)
## Contributing
-
-Contributions are welcome! If you'd like to contribute to Rustmission, please fork the repository, make your changes, and submit a pull request!
+If you'd like to contribute make sure you fork [this repo](https://github.com/intuis/rustmission) and submit a PR!
+If you want to implement something major, create an issue first so it can be discussed.
From 5709159bdf423dfb7ed232dd25a34003a025bf6a Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 17:41:45 +0000
Subject: [PATCH 03/10] feat: configurable headers (#55)
---
Cargo.lock | 54 ++++++++
Cargo.toml | 1 +
README.md | 16 ++-
rm-config/Cargo.toml | 1 +
rm-config/defaults/config.toml | 16 ++-
rm-config/src/lib.rs | 4 +-
rm-config/src/main_config.rs | 112 ++++++++++++++--
rm-config/src/utils.rs | 4 +-
rm-main/Cargo.toml | 1 +
rm-main/src/transmission/fetchers.rs | 4 +
rm-main/src/ui/tabs/torrents/bottom_stats.rs | 2 +-
rm-main/src/ui/tabs/torrents/mod.rs | 6 +-
.../ui/tabs/torrents/rustmission_torrent.rs | 126 ++++++++++++++----
rm-main/src/ui/tabs/torrents/table_manager.rs | 114 +++++++++-------
rm-main/src/utils.rs | 4 +-
15 files changed, 359 insertions(+), 106 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index cc26bd3..1dd394f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,6 +44,21 @@ version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "anstream"
version = "0.6.14"
@@ -222,7 +237,12 @@ version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
"num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.6",
]
[[package]]
@@ -839,6 +859,29 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -1444,6 +1487,7 @@ dependencies = [
"serde",
"thiserror",
"toml",
+ "transmission-rpc",
"url",
"xdg",
]
@@ -1570,6 +1614,7 @@ version = "0.3.3"
dependencies = [
"anyhow",
"base64 0.22.1",
+ "chrono",
"clap",
"crossterm",
"futures",
@@ -2344,6 +2389,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
[[package]]
name = "windows-sys"
version = "0.48.0"
diff --git a/Cargo.toml b/Cargo.toml
index 9aef960..260402c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,6 +31,7 @@ rss = "2"
reqwest = "0.12"
regex = "1"
thiserror = "1"
+chrono = "0.4"
# Async
tokio = { version = "1", features = ["macros", "sync"] }
diff --git a/README.md b/README.md
index 1bff434..b58c21e 100644
--- a/README.md
+++ b/README.md
@@ -78,14 +78,22 @@ headers_hide = false
[connection]
url = "http://CHANGE_ME:9091/transmission/rpc" # REQUIRED!
+# Refresh timings (in seconds)
+torrents_refresh = 5
+stats_refresh = 5
+free_space_refresh = 10
+
# If you need username and password to authenticate:
# username = "CHANGE_ME"
# password = "CHANGE_ME"
-# Refresh timings (in seconds)
-torrents_refresh = 5
-stats_refresh = 10
-free_space_refresh = 10
+[torrents_tab]
+# Available fields:
+# Id, Name, SizeWhenDone, Progress, DownloadRate, UploadRate, DownloadDir,
+# Padding, UploadRatio, UploadedEver, AddedDate, ActivityDate, PeersConnected
+# SmallStatus
+headers = ["Name", "SizeWhenDone", "Progress", "DownloadRate", "UploadRate"]
+
```
There's also a self-documenting keymap config located at `~/.config/rustmission/keymap.toml` with sane defaults.
diff --git a/rm-config/Cargo.toml b/rm-config/Cargo.toml
index e1ab5f9..f903d13 100644
--- a/rm-config/Cargo.toml
+++ b/rm-config/Cargo.toml
@@ -20,3 +20,4 @@ url.workspace = true
ratatui.workspace = true
crossterm.workspace = true
thiserror.workspace = true
+transmission-rpc.workspace = true
diff --git a/rm-config/defaults/config.toml b/rm-config/defaults/config.toml
index 5cce726..7d87ef1 100644
--- a/rm-config/defaults/config.toml
+++ b/rm-config/defaults/config.toml
@@ -17,11 +17,19 @@ headers_hide = false
[connection]
url = "http://CHANGE_ME:9091/transmission/rpc" # REQUIRED!
+# Refresh timings (in seconds)
+torrents_refresh = 5
+stats_refresh = 5
+free_space_refresh = 10
+
# If you need username and password to authenticate:
# username = "CHANGE_ME"
# password = "CHANGE_ME"
-# Refresh timings (in seconds)
-torrents_refresh = 5
-stats_refresh = 10
-free_space_refresh = 10
+
+[torrents_tab]
+# Available fields:
+# Id, Name, SizeWhenDone, Progress, DownloadRate, UploadRate, DownloadDir,
+# Padding, UploadRatio, UploadedEver, AddedDate, ActivityDate, PeersConnected
+# SmallStatus
+headers = ["Name", "SizeWhenDone", "Progress", "DownloadRate", "UploadRate"]
diff --git a/rm-config/src/lib.rs b/rm-config/src/lib.rs
index a2e3a8b..59347e3 100644
--- a/rm-config/src/lib.rs
+++ b/rm-config/src/lib.rs
@@ -1,5 +1,5 @@
pub mod keymap;
-mod main_config;
+pub mod main_config;
mod utils;
use std::path::PathBuf;
@@ -11,6 +11,7 @@ use main_config::MainConfig;
pub struct Config {
pub general: main_config::General,
pub connection: main_config::Connection,
+ pub torrents_tab: main_config::TorrentsTab,
pub keybindings: KeymapConfig,
pub directories: Directories,
}
@@ -33,6 +34,7 @@ impl Config {
Ok(Self {
general: main_config.general,
connection: main_config.connection,
+ torrents_tab: main_config.torrents_tab,
keybindings: keybindings.clone(),
directories,
})
diff --git a/rm-config/src/main_config.rs b/rm-config/src/main_config.rs
index 2a45893..50f85fe 100644
--- a/rm-config/src/main_config.rs
+++ b/rm-config/src/main_config.rs
@@ -1,19 +1,21 @@
-use std::{path::PathBuf, sync::OnceLock};
+use std::{io::ErrorKind, path::PathBuf, sync::OnceLock};
use anyhow::Result;
-use ratatui::style::Color;
+use ratatui::{layout::Constraint, style::Color};
use serde::{Deserialize, Serialize};
use url::Url;
-use crate::utils::{self, put_config};
+use crate::utils::{self};
-#[derive(Serialize, Deserialize)]
+#[derive(Deserialize)]
pub struct MainConfig {
pub general: General,
pub connection: Connection,
+ #[serde(default)]
+ pub torrents_tab: TorrentsTab,
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Deserialize)]
pub struct General {
#[serde(default)]
pub auto_hide: bool,
@@ -33,7 +35,7 @@ fn default_beginner_mode() -> bool {
true
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Deserialize)]
pub struct Connection {
pub username: Option,
pub password: Option,
@@ -50,19 +52,103 @@ fn default_refresh() -> u64 {
5
}
+#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, Copy)]
+pub enum Header {
+ Name,
+ SizeWhenDone,
+ Progress,
+ Eta,
+ DownloadRate,
+ UploadRate,
+ DownloadDir,
+ Padding,
+ UploadRatio,
+ UploadedEver,
+ Id,
+ ActivityDate,
+ AddedDate,
+ PeersConnected,
+ SmallStatus,
+}
+
+impl Header {
+ pub fn default_constraint(&self) -> Constraint {
+ match self {
+ Self::Name => Constraint::Max(70),
+ Self::SizeWhenDone => Constraint::Length(12),
+ Self::Progress => Constraint::Length(12),
+ Self::Eta => Constraint::Length(12),
+ Self::DownloadRate => Constraint::Length(12),
+ Self::UploadRate => Constraint::Length(12),
+ Self::DownloadDir => Constraint::Max(70),
+ Self::Padding => Constraint::Length(2),
+ Self::UploadRatio => Constraint::Length(6),
+ Self::UploadedEver => Constraint::Length(12),
+ Self::Id => Constraint::Length(4),
+ Self::ActivityDate => Constraint::Length(14),
+ Self::AddedDate => Constraint::Length(12),
+ Self::PeersConnected => Constraint::Length(6),
+ Self::SmallStatus => Constraint::Length(1),
+ }
+ }
+
+ pub fn header_name(&self) -> &'static str {
+ match *self {
+ Self::Name => "Name",
+ Self::SizeWhenDone => "Size",
+ Self::Progress => "Progress",
+ Self::Eta => "ETA",
+ Self::DownloadRate => "Download",
+ Self::UploadRate => "Upload",
+ Self::DownloadDir => "Directory",
+ Self::Padding => "",
+ Self::UploadRatio => "Ratio",
+ Self::UploadedEver => "Up Ever",
+ Self::Id => "Id",
+ Self::ActivityDate => "Last active",
+ Self::AddedDate => "Added",
+ Self::PeersConnected => "Peers",
+ Self::SmallStatus => "",
+ }
+ }
+}
+
+#[derive(Deserialize)]
+pub struct TorrentsTab {
+ pub headers: Vec,
+}
+
+impl Default for TorrentsTab {
+ fn default() -> Self {
+ Self {
+ headers: vec![
+ Header::Name,
+ Header::SizeWhenDone,
+ Header::Progress,
+ Header::Eta,
+ Header::DownloadRate,
+ Header::UploadRate,
+ ],
+ }
+ }
+}
+
impl MainConfig {
pub(crate) const FILENAME: &'static str = "config.toml";
const DEFAULT_CONFIG: &'static str = include_str!("../defaults/config.toml");
pub(crate) fn init() -> Result {
- let Ok(config) = utils::fetch_config(Self::FILENAME) else {
- put_config(Self::DEFAULT_CONFIG, Self::FILENAME)?;
- // TODO: check if the user really changed the config.
- println!("Update {:?} and start rustmission again", Self::path());
- std::process::exit(0);
+ match utils::fetch_config::(Self::FILENAME) {
+ Ok(config) => return Ok(config),
+ Err(e) => match e {
+ utils::ConfigFetchingError::Io(e) if e.kind() == ErrorKind::NotFound => {
+ utils::put_config::(Self::DEFAULT_CONFIG, Self::FILENAME)?;
+ println!("Update {:?} and start rustmission again", Self::path());
+ std::process::exit(0);
+ }
+ _ => anyhow::bail!(e),
+ },
};
-
- Ok(config)
}
pub(crate) fn path() -> &'static PathBuf {
diff --git a/rm-config/src/utils.rs b/rm-config/src/utils.rs
index 102afde..3654cdb 100644
--- a/rm-config/src/utils.rs
+++ b/rm-config/src/utils.rs
@@ -42,9 +42,9 @@ pub fn fetch_config(config_name: &str) -> Result(
content: &'static str,
filename: &str,
-) -> Result {
+) -> Result {
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).expect("default configs are correct"))
+ Ok(toml::from_str(content)?)
}
diff --git a/rm-main/Cargo.toml b/rm-main/Cargo.toml
index d518e11..05d531f 100644
--- a/rm-main/Cargo.toml
+++ b/rm-main/Cargo.toml
@@ -34,3 +34,4 @@ rss.workspace = true
reqwest.workspace = true
regex.workspace = true
throbber-widgets-tui.workspace = true
+chrono.workspace = true
diff --git a/rm-main/src/transmission/fetchers.rs b/rm-main/src/transmission/fetchers.rs
index 45fc025..4181c78 100644
--- a/rm-main/src/transmission/fetchers.rs
+++ b/rm-main/src/transmission/fetchers.rs
@@ -72,6 +72,10 @@ pub async fn torrents(ctx: app::Ctx, table_manager: Arc>) {
TorrentGetField::RateDownload,
TorrentGetField::Status,
TorrentGetField::DownloadDir,
+ TorrentGetField::UploadedEver,
+ TorrentGetField::ActivityDate,
+ TorrentGetField::AddedDate,
+ TorrentGetField::PeersConnected,
];
let rpc_response = ctx
.client
diff --git a/rm-main/src/ui/tabs/torrents/bottom_stats.rs b/rm-main/src/ui/tabs/torrents/bottom_stats.rs
index 717c06c..eec8ab7 100644
--- a/rm-main/src/ui/tabs/torrents/bottom_stats.rs
+++ b/rm-main/src/ui/tabs/torrents/bottom_stats.rs
@@ -40,7 +40,7 @@ impl Component for BottomStats {
let download = bytes_to_human_format(stats.download_speed);
let upload = bytes_to_human_format(stats.upload_speed);
- let mut text = format!("▼ {download} | ▲ {upload}");
+ let mut text = format!(" {download} | {upload}");
if let Some(free_space) = &*self.free_space.lock().unwrap() {
let free_space = bytes_to_human_format(free_space.size_bytes);
diff --git a/rm-main/src/ui/tabs/torrents/mod.rs b/rm-main/src/ui/tabs/torrents/mod.rs
index 167cb75..b5d69fb 100644
--- a/rm-main/src/ui/tabs/torrents/mod.rs
+++ b/rm-main/src/ui/tabs/torrents/mod.rs
@@ -121,12 +121,10 @@ impl TorrentsTab {
.accent_color);
let table_widget = {
- let table = Table::new(torrent_rows, table_manager_lock.widths)
+ let table = Table::new(torrent_rows, &table_manager_lock.widths)
.highlight_style(highlight_table_style);
if !self.ctx.config.general.headers_hide {
- table.header(Row::new(
- table_manager_lock.header().iter().map(|s| s.as_str()),
- ))
+ table.header(Row::new(table_manager_lock.header().iter().cloned()))
} else {
table
}
diff --git a/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs b/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs
index d5b5e6a..7dd0137 100644
--- a/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs
+++ b/rm-main/src/ui/tabs/torrents/rustmission_torrent.rs
@@ -1,8 +1,10 @@
+use chrono::{Datelike, NaiveDateTime};
use ratatui::{
style::{Style, Stylize},
text::{Line, Span},
widgets::Row,
};
+use rm_config::main_config::Header;
use transmission_rpc::types::{Id, Torrent, TorrentStatus};
use crate::utils::{
@@ -17,31 +19,31 @@ pub struct RustmissionTorrent {
pub eta_secs: String,
pub download_speed: String,
pub upload_speed: String,
+ pub uploaded_ever: String,
+ pub upload_ratio: String,
status: TorrentStatus,
pub style: Style,
pub id: Id,
pub download_dir: String,
+ pub activity_date: NaiveDateTime,
+ pub added_date: NaiveDateTime,
+ pub peers_connected: i64,
}
impl RustmissionTorrent {
- pub fn to_row(&self) -> ratatui::widgets::Row {
- Row::new([
- Line::from(self.torrent_name.as_str()),
- Line::from(""),
- Line::from(self.size_when_done.as_str()),
- Line::from(self.progress.as_str()),
- Line::from(self.eta_secs.as_str()),
- Line::from(download_speed_format(&self.download_speed)),
- Line::from(upload_speed_format(&self.upload_speed)),
- Line::from(self.download_dir.as_str()),
- ])
- .style(self.style)
+ pub fn to_row(&self, headers: &Vec) -> ratatui::widgets::Row {
+ headers
+ .iter()
+ .map(|header| self.header_to_line(*header))
+ .collect::()
+ .style(self.style)
}
pub fn to_row_with_higlighted_indices(
&self,
highlighted_indices: Vec,
highlight_style: Style,
+ headers: &Vec,
) -> ratatui::widgets::Row {
let mut torrent_name_line = Line::default();
@@ -53,16 +55,54 @@ impl RustmissionTorrent {
}
}
- Row::new([
- Line::from(torrent_name_line),
- Line::from(""),
- Line::from(self.size_when_done.as_str()),
- Line::from(self.progress.as_str()),
- Line::from(self.eta_secs.as_str()),
- Line::from(download_speed_format(&self.download_speed)),
- Line::from(upload_speed_format(&self.upload_speed)),
- Line::from(self.download_dir.as_str()),
- ])
+ let mut cells = vec![];
+
+ for header in headers {
+ if *header == Header::Name {
+ cells.push(Line::from(torrent_name_line.clone()))
+ } else {
+ cells.push(self.header_to_line(*header))
+ }
+ }
+
+ Row::new(cells)
+ }
+
+ fn header_to_line(&self, header: Header) -> Line {
+ match header {
+ Header::Name => Line::from(self.torrent_name.as_str()),
+ Header::SizeWhenDone => Line::from(self.size_when_done.as_str()),
+ Header::Progress => Line::from(self.progress.as_str()),
+ Header::Eta => Line::from(self.eta_secs.as_str()),
+ Header::DownloadRate => Line::from(download_speed_format(&self.download_speed)),
+ Header::UploadRate => Line::from(upload_speed_format(&self.upload_speed)),
+ Header::DownloadDir => Line::from(self.download_dir.as_str()),
+ Header::Padding => Line::raw(""),
+ Header::Id => match &self.id {
+ Id::Id(id) => Line::from(id.to_string()),
+ Id::Hash(hash) => Line::from(hash.as_str()),
+ },
+ Header::UploadRatio => Line::from(self.upload_ratio.as_str()),
+ Header::UploadedEver => Line::from(self.uploaded_ever.as_str()),
+ Header::ActivityDate => time_to_line(self.activity_date),
+ Header::AddedDate => time_to_line(self.added_date),
+ Header::PeersConnected => Line::from(self.peers_connected.to_string()),
+ Header::SmallStatus => match self.status() {
+ TorrentStatus::Stopped => Line::from(""),
+ TorrentStatus::QueuedToVerify => Line::from(""),
+ TorrentStatus::Verifying => Line::from(""),
+ TorrentStatus::QueuedToDownload => Line::from(""),
+ TorrentStatus::QueuedToSeed => Line::from(""),
+ TorrentStatus::Seeding => {
+ if !self.upload_speed.is_empty() {
+ Line::from("")
+ } else {
+ Line::from("")
+ }
+ }
+ TorrentStatus::Downloading => Line::from(""),
+ },
+ }
}
pub const fn status(&self) -> TorrentStatus {
@@ -116,10 +156,30 @@ impl From<&Torrent> for RustmissionTorrent {
_ => Style::default(),
};
- let download_dir = t
- .download_dir
- .clone()
- .expect("torrent download directory requested");
+ let download_dir = t.download_dir.clone().expect("field requested");
+
+ let uploaded_ever = bytes_to_human_format(t.uploaded_ever.expect("field requested"));
+
+ let upload_ratio = {
+ let raw = t.upload_ratio.expect("field requested");
+ format!("{:.1}", raw)
+ };
+
+ let activity_date = {
+ let raw = t.activity_date.expect("field requested");
+ chrono::DateTime::from_timestamp(raw, 0)
+ .unwrap()
+ .naive_local()
+ };
+
+ let added_date = {
+ let raw = t.added_date.expect("field requested");
+ chrono::DateTime::from_timestamp(raw, 0)
+ .unwrap()
+ .naive_local()
+ };
+
+ let peers_connected = t.peers_connected.expect("field requested");
Self {
torrent_name,
@@ -132,6 +192,20 @@ impl From<&Torrent> for RustmissionTorrent {
style,
id,
download_dir,
+ uploaded_ever,
+ upload_ratio,
+ activity_date,
+ added_date,
+ peers_connected,
}
}
}
+
+fn time_to_line<'a>(time: NaiveDateTime) -> Line<'a> {
+ let today = chrono::Local::now();
+ if time.year() == today.year() && time.month() == today.month() && time.day() == today.day() {
+ Line::from(time.format("Today %H:%M").to_string())
+ } else {
+ Line::from(time.format("%y|%m|%d %H:%M").to_string())
+ }
+}
diff --git a/rm-main/src/ui/tabs/torrents/table_manager.rs b/rm-main/src/ui/tabs/torrents/table_manager.rs
index f340efa..48df59f 100644
--- a/rm-main/src/ui/tabs/torrents/table_manager.rs
+++ b/rm-main/src/ui/tabs/torrents/table_manager.rs
@@ -1,6 +1,10 @@
use fuzzy_matcher::{skim::SkimMatcherV2, FuzzyMatcher};
use ratatui::{prelude::*, widgets::Row};
-use std::sync::{Arc, Mutex};
+use rm_config::main_config::Header;
+use std::{
+ collections::HashMap,
+ sync::{Arc, Mutex},
+};
use crate::{app, ui::components::table::GenericTable};
@@ -9,31 +13,27 @@ use super::rustmission_torrent::RustmissionTorrent;
pub struct TableManager {
ctx: app::Ctx,
pub table: GenericTable,
- pub widths: [Constraint; 8],
+ pub widths: Vec,
pub filter: Arc>>,
pub torrents_displaying_no: u16,
- header: Vec,
+ headers: Vec<&'static str>,
}
impl TableManager {
pub fn new(ctx: app::Ctx, table: GenericTable) -> Self {
- let widths = Self::default_widths();
+ let widths = Self::default_widths(&ctx.config.torrents_tab.headers);
+ let mut headers = vec![];
+ for header in &ctx.config.torrents_tab.headers {
+ headers.push(header.header_name());
+ }
+
Self {
ctx,
table,
widths,
filter: Arc::new(Mutex::new(None)),
torrents_displaying_no: 0,
- header: vec![
- "Name".to_owned(),
- "".to_owned(),
- "Size".to_owned(),
- "Progress".to_owned(),
- "ETA".to_owned(),
- "Download".to_owned(),
- "Upload".to_owned(),
- "Directory".to_owned(),
- ],
+ headers,
}
}
@@ -46,13 +46,13 @@ impl TableManager {
self.table
.items
.iter()
- .map(RustmissionTorrent::to_row)
+ .map(|t| t.to_row(&self.ctx.config.torrents_tab.headers))
.collect()
}
}
- pub const fn header(&self) -> &Vec {
- &self.header
+ pub const fn header(&self) -> &Vec<&'static str> {
+ &self.headers
}
pub fn current_torrent(&mut self) -> Option<&mut RustmissionTorrent> {
@@ -94,61 +94,77 @@ impl TableManager {
for torrent in torrents {
if let Some((_, indices)) = matcher.fuzzy_indices(&torrent.torrent_name, filter) {
- rows.push(torrent.to_row_with_higlighted_indices(indices, highlight_style))
+ rows.push(torrent.to_row_with_higlighted_indices(
+ indices,
+ highlight_style,
+ &self.ctx.config.torrents_tab.headers,
+ ))
}
}
rows
}
- const fn default_widths() -> [Constraint; 8] {
- [
- Constraint::Max(70), // Name
- Constraint::Length(5), //
- Constraint::Length(12), // Size
- Constraint::Length(12), // Progress
- Constraint::Length(12), // ETA
- Constraint::Length(12), // Download
- Constraint::Length(12), // Upload
- Constraint::Max(70), // Download directory
- ]
+ fn default_widths(headers: &Vec) -> Vec {
+ let mut constraints = vec![];
+
+ for header in headers {
+ constraints.push(header.default_constraint())
+ }
+ constraints
}
- fn header_widths(&self, rows: &[RustmissionTorrent]) -> [Constraint; 8] {
+ fn header_widths(&self, rows: &[RustmissionTorrent]) -> Vec {
+ let headers = &self.ctx.config.torrents_tab.headers;
+
if !self.ctx.config.general.auto_hide {
- return Self::default_widths();
+ return Self::default_widths(&headers);
}
- let mut download_width = 0;
- let mut upload_width = 0;
- let mut progress_width = 0;
- let mut eta_width = 0;
+ let mut map = HashMap::new();
+
+ for header in headers {
+ map.insert(header, header.default_constraint());
+ }
+
+ let hidable_headers = [
+ Header::Progress,
+ Header::UploadRate,
+ Header::DownloadRate,
+ Header::Eta,
+ ];
+
+ for hidable_header in &hidable_headers {
+ map.entry(hidable_header)
+ .and_modify(|c| *c = Constraint::Length(0));
+ }
for row in rows {
if !row.download_speed.is_empty() {
- download_width = 11;
+ map.entry(&Header::DownloadRate)
+ .and_modify(|c| *c = Header::DownloadRate.default_constraint());
}
if !row.upload_speed.is_empty() {
- upload_width = 11;
+ map.entry(&Header::UploadRate)
+ .and_modify(|c| *c = Header::UploadRate.default_constraint());
}
if !row.progress.is_empty() {
- progress_width = 11;
+ map.entry(&Header::Progress)
+ .and_modify(|c| *c = Header::Progress.default_constraint());
}
if !row.eta_secs.is_empty() {
- eta_width = 11;
+ map.entry(&Header::Eta)
+ .and_modify(|c| *c = Header::Eta.default_constraint());
}
}
- [
- Constraint::Max(70), // Name
- Constraint::Length(5), //
- Constraint::Length(11), // Size
- Constraint::Length(progress_width), // Progress
- Constraint::Length(eta_width), // ETA
- Constraint::Length(download_width), // Download
- Constraint::Length(upload_width), // Upload
- Constraint::Max(70), // Download directory
- ]
+ let mut constraints = vec![];
+
+ for header in headers {
+ constraints.push(map.remove(header).expect("this header exists"))
+ }
+
+ constraints
}
}
diff --git a/rm-main/src/utils.rs b/rm-main/src/utils.rs
index 992c984..f9c2677 100644
--- a/rm-main/src/utils.rs
+++ b/rm-main/src/utils.rs
@@ -64,14 +64,14 @@ pub fn seconds_to_human_format(seconds: i64) -> String {
pub fn download_speed_format(download_speed: &str) -> String {
if download_speed.len() > 0 {
- return format!("▼ {}", download_speed);
+ return format!(" {}", download_speed);
}
download_speed.to_string()
}
pub fn upload_speed_format(upload_speed: &str) -> String {
if upload_speed.len() > 0 {
- return format!("▲ {}", upload_speed);
+ return format!(" {}", upload_speed);
}
upload_speed.to_string()
}
From 4cb3c0ece502d820ddcb7a597f164bd50ec1c8c6 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 18:18:11 +0000
Subject: [PATCH 04/10] fix: allow not specifying headers in torrents tab
config (#60)
---
rm-config/src/main_config.rs | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/rm-config/src/main_config.rs b/rm-config/src/main_config.rs
index 50f85fe..ba9f63b 100644
--- a/rm-config/src/main_config.rs
+++ b/rm-config/src/main_config.rs
@@ -115,20 +115,25 @@ impl Header {
#[derive(Deserialize)]
pub struct TorrentsTab {
+ #[serde(default = "default_headers")]
pub headers: Vec,
}
+fn default_headers() -> Vec {
+ vec![
+ Header::Name,
+ Header::SizeWhenDone,
+ Header::Progress,
+ Header::Eta,
+ Header::DownloadRate,
+ Header::UploadRate,
+ ]
+}
+
impl Default for TorrentsTab {
fn default() -> Self {
Self {
- headers: vec![
- Header::Name,
- Header::SizeWhenDone,
- Header::Progress,
- Header::Eta,
- Header::DownloadRate,
- Header::UploadRate,
- ],
+ headers: default_headers(),
}
}
}
From 1064c1a05cff038c09b6188283954c7134743187 Mon Sep 17 00:00:00 2001
From: aidan
Date: Tue, 9 Jul 2024 02:18:47 +0800
Subject: [PATCH 05/10] release: 0.4.0 (#54)
---
Cargo.lock | 6 +++---
Cargo.toml | 6 +++---
rm-main/Cargo.toml | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 1dd394f..de34d9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1478,7 +1478,7 @@ dependencies = [
[[package]]
name = "rm-config"
-version = "0.3.3"
+version = "0.4.0"
dependencies = [
"anyhow",
"crossterm",
@@ -1494,7 +1494,7 @@ dependencies = [
[[package]]
name = "rm-shared"
-version = "0.3.3"
+version = "0.4.0"
dependencies = [
"crossterm",
]
@@ -1610,7 +1610,7 @@ dependencies = [
[[package]]
name = "rustmission"
-version = "0.3.3"
+version = "0.4.0"
dependencies = [
"anyhow",
"base64 0.22.1",
diff --git a/Cargo.toml b/Cargo.toml
index 260402c..e0a4702 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ members = [
]
[workspace.package]
-version = "0.3.3"
+version = "0.4.0"
edition = "2021"
authors = ["Remigiusz Micielski "]
repository = "https://github.com/intuis/rustmission"
@@ -14,8 +14,8 @@ homepage = "https://github.com/intuis/rustmission"
license = "GPL-3.0-or-later"
[workspace.dependencies]
-rm-config = { version = "0.3", path = "rm-config" }
-rm-shared = { version = "0.3", path = "rm-shared" }
+rm-config = { version = "0.4", path = "rm-config" }
+rm-shared = { version = "0.4", path = "rm-shared" }
magnetease = "0.1"
anyhow = "1"
diff --git a/rm-main/Cargo.toml b/rm-main/Cargo.toml
index 05d531f..877187e 100644
--- a/rm-main/Cargo.toml
+++ b/rm-main/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustmission"
-version = "0.3.3"
+version = "0.4.0"
edition = "2021"
description = "TUI for Transmission daemon"
repository = "https://github.com/intuis/rustmission"
From 4009d8473bd7d3edef6bcadb2ddb1c9bc24da2f3 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 18:32:18 +0000
Subject: [PATCH 06/10] chore: add desc to rm-shared (#61)
---
rm-shared/Cargo.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/rm-shared/Cargo.toml b/rm-shared/Cargo.toml
index 66fa6de..15c7740 100644
--- a/rm-shared/Cargo.toml
+++ b/rm-shared/Cargo.toml
@@ -1,5 +1,6 @@
[package]
name = "rm-shared"
+description = "shared things for rustmission"
version.workspace = true
edition.workspace = true
authors.workspace = true
From b3ecc27288b06c88151b50792456da0589440ed6 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 19:03:00 +0000
Subject: [PATCH 07/10] ci: remove musl from targets (#62)
---
Cargo.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Cargo.toml b/Cargo.toml
index e0a4702..94565a2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,7 +56,7 @@ installers = ["shell", "homebrew"]
# A GitHub repo to push Homebrew formulas to
tap = "intuis/homebrew-tap"
# Target platforms to build apps for (Rust target-triple syntax)
-targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"]
+targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"]
# Publish jobs to run in CI
publish-jobs = ["homebrew"]
# Publish jobs to run in CI
From 27953f80c9e7d5784fd3a6fbbfdc796714f82734 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 19:12:49 +0000
Subject: [PATCH 08/10] release: 0.4.1 (#63)
---
Cargo.lock | 6 +++---
Cargo.toml | 2 +-
rm-main/Cargo.toml | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index de34d9b..0094619 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1478,7 +1478,7 @@ dependencies = [
[[package]]
name = "rm-config"
-version = "0.4.0"
+version = "0.4.1"
dependencies = [
"anyhow",
"crossterm",
@@ -1494,7 +1494,7 @@ dependencies = [
[[package]]
name = "rm-shared"
-version = "0.4.0"
+version = "0.4.1"
dependencies = [
"crossterm",
]
@@ -1610,7 +1610,7 @@ dependencies = [
[[package]]
name = "rustmission"
-version = "0.4.0"
+version = "0.4.1"
dependencies = [
"anyhow",
"base64 0.22.1",
diff --git a/Cargo.toml b/Cargo.toml
index 94565a2..809b68b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ members = [
]
[workspace.package]
-version = "0.4.0"
+version = "0.4.1"
edition = "2021"
authors = ["Remigiusz Micielski "]
repository = "https://github.com/intuis/rustmission"
diff --git a/rm-main/Cargo.toml b/rm-main/Cargo.toml
index 877187e..dda7e44 100644
--- a/rm-main/Cargo.toml
+++ b/rm-main/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustmission"
-version = "0.4.0"
+version = "0.4.1"
edition = "2021"
description = "TUI for Transmission daemon"
repository = "https://github.com/intuis/rustmission"
From ccc14f162d28bf969b7a0a50cf637df7c25c4ca0 Mon Sep 17 00:00:00 2001
From: micielski <73398428+micielski@users.noreply.github.com>
Date: Mon, 8 Jul 2024 19:42:00 +0000
Subject: [PATCH 09/10] release: 0.4.3 (#64)
---
Cargo.lock | 6 +++---
Cargo.toml | 2 +-
rm-main/Cargo.toml | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
index 0094619..86e577d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1478,7 +1478,7 @@ dependencies = [
[[package]]
name = "rm-config"
-version = "0.4.1"
+version = "0.4.3"
dependencies = [
"anyhow",
"crossterm",
@@ -1494,7 +1494,7 @@ dependencies = [
[[package]]
name = "rm-shared"
-version = "0.4.1"
+version = "0.4.3"
dependencies = [
"crossterm",
]
@@ -1610,7 +1610,7 @@ dependencies = [
[[package]]
name = "rustmission"
-version = "0.4.1"
+version = "0.4.3"
dependencies = [
"anyhow",
"base64 0.22.1",
diff --git a/Cargo.toml b/Cargo.toml
index 809b68b..e1fe58a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ members = [
]
[workspace.package]
-version = "0.4.1"
+version = "0.4.3"
edition = "2021"
authors = ["Remigiusz Micielski "]
repository = "https://github.com/intuis/rustmission"
diff --git a/rm-main/Cargo.toml b/rm-main/Cargo.toml
index dda7e44..9c13280 100644
--- a/rm-main/Cargo.toml
+++ b/rm-main/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "rustmission"
-version = "0.4.1"
+version = "0.4.3"
edition = "2021"
description = "TUI for Transmission daemon"
repository = "https://github.com/intuis/rustmission"
From c234a37f5687b872483e1581a36b781c7a73a467 Mon Sep 17 00:00:00 2001
From: aidan
Date: Sun, 14 Jul 2024 15:06:49 +0800
Subject: [PATCH 10/10] feat: add progress status for moving torrents (#66)
---
rm-main/src/transmission/action.rs | 25 +++++++++++--------
rm-main/src/ui/tabs/torrents/task_manager.rs | 1 +
.../ui/tabs/torrents/tasks/move_torrent.rs | 6 ++---
rm-main/src/ui/tabs/torrents/tasks/status.rs | 12 +++++++++
rm-shared/src/status_task.rs | 1 +
5 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/rm-main/src/transmission/action.rs b/rm-main/src/transmission/action.rs
index 4e8b956..895c072 100644
--- a/rm-main/src/transmission/action.rs
+++ b/rm-main/src/transmission/action.rs
@@ -127,23 +127,26 @@ pub async fn action_handler(ctx: app::Ctx, mut trans_rx: UnboundedReceiver {
- if let Err(e) = ctx
+ match ctx
.client
.lock()
.await
.torrent_set_location(ids, new_directory.clone(), Option::from(true))
.await
{
- let error_title = "Failed to move torrent";
- let msg = "Failed to move torrent to new directory:\n\"".to_owned()
- + new_directory.as_str()
- + "\"\n"
- + &e.to_string();
- let error_message = ErrorMessage {
- title: error_title.to_string(),
- message: msg,
- };
- ctx.send_action(Action::Error(Box::new(error_message)));
+ Ok(_) => ctx.send_action(Action::TaskSuccess),
+ Err(e) => {
+ let error_title = "Failed to move torrent";
+ let msg = "Failed to move torrent to new directory:\n\"".to_owned()
+ + new_directory.as_str()
+ + "\"\n"
+ + &e.to_string();
+ let error_message = ErrorMessage {
+ title: error_title.to_string(),
+ message: msg,
+ };
+ ctx.send_action(Action::Error(Box::new(error_message)));
+ }
}
}
}
diff --git a/rm-main/src/ui/tabs/torrents/task_manager.rs b/rm-main/src/ui/tabs/torrents/task_manager.rs
index 09648f6..7f60c13 100644
--- a/rm-main/src/ui/tabs/torrents/task_manager.rs
+++ b/rm-main/src/ui/tabs/torrents/task_manager.rs
@@ -89,6 +89,7 @@ impl Component for TaskManager {
_ => None,
},
CurrentTask::MoveBar(move_bar) => match move_bar.handle_actions(action) {
+ Some(A::TaskPending(task)) => self.pending_task(task),
Some(A::Quit) => self.cancel_task(),
Some(A::Render) => Some(A::Render),
_ => None,
diff --git a/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs b/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs
index a947ffc..f0cb237 100644
--- a/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs
+++ b/rm-main/src/ui/tabs/torrents/tasks/move_torrent.rs
@@ -1,6 +1,6 @@
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::prelude::*;
-use rm_shared::action::Action;
+use rm_shared::{action::Action, status_task::StatusTask};
use transmission_rpc::types::Id;
use crate::{
@@ -31,8 +31,8 @@ impl MoveBar {
let new_location = self.input_mgr.text();
let torrents_to_move = self.torrents_to_move.clone();
self.ctx
- .send_torrent_action(TorrentAction::Move(torrents_to_move, new_location));
- return Some(Action::Quit);
+ .send_torrent_action(TorrentAction::Move(torrents_to_move, new_location.clone()));
+ return Some(Action::TaskPending(StatusTask::Move(new_location)));
}
if input.code == KeyCode::Esc {
diff --git a/rm-main/src/ui/tabs/torrents/tasks/status.rs b/rm-main/src/ui/tabs/torrents/tasks/status.rs
index efc9da3..d07a830 100644
--- a/rm-main/src/ui/tabs/torrents/tasks/status.rs
+++ b/rm-main/src/ui/tabs/torrents/tasks/status.rs
@@ -40,6 +40,10 @@ impl Component for StatusBar {
let display_name = format_display_name(&name);
format!("Deleting {display_name}")
}
+ StatusTask::Move(name) => {
+ let display_name = format_display_name(&name);
+ format!("Moving to {display_name}")
+ }
};
let default_throbber = throbber_widgets_tui::Throbber::default()
.label(status_text)
@@ -61,6 +65,10 @@ impl Component for StatusBar {
let display_name = format_display_name(&name);
format!(" Error deleting {display_name}")
}
+ StatusTask::Move(name) => {
+ let display_name = format_display_name(&name);
+ format!(" Error moving to {display_name}")
+ }
},
CurrentTaskState::Success(_) => match &self.task {
StatusTask::Add(name) => {
@@ -71,6 +79,10 @@ impl Component for StatusBar {
let display_name = format_display_name(&name);
format!(" Deleted {display_name}")
}
+ StatusTask::Move(name) => {
+ let display_name = format_display_name(&name);
+ format!(" Location moved to {display_name}")
+ }
},
_ => return,
};
diff --git a/rm-shared/src/status_task.rs b/rm-shared/src/status_task.rs
index c550e11..79dae08 100644
--- a/rm-shared/src/status_task.rs
+++ b/rm-shared/src/status_task.rs
@@ -2,4 +2,5 @@
pub enum StatusTask {
Add(String),
Delete(String),
+ Move(String),
}