From 8d1f1e5ed1213ad74dcc3a569e1c98e56dae6c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Wed, 31 Mar 2021 22:57:58 +0200 Subject: [PATCH] add copy/move/delete features --- CHANGELOG.md | 6 ++++ README.md | 27 +++++++++++++++++ src/flag/cli.rs | 8 +---- src/imap/model.rs | 9 +++--- src/main.rs | 4 +-- src/mbox/cli.rs | 10 +++++-- src/msg/cli.rs | 76 ++++++++++++++++++++++++++++++++++++++--------- src/msg/model.rs | 2 +- 8 files changed, 112 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b462dd4..272106e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - IDLE support [#29] - Improve choice after editing msg [#30] - Flags management [#41] +- Copy feature [#35] +- Move feature [#31] +- Delete feature [#36] ### Changed @@ -83,7 +86,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#25]: https://github.com/soywod/himalaya/issues/25 [#29]: https://github.com/soywod/himalaya/issues/29 [#30]: https://github.com/soywod/himalaya/issues/30 +[#31]: https://github.com/soywod/himalaya/issues/31 [#32]: https://github.com/soywod/himalaya/issues/32 +[#34]: https://github.com/soywod/himalaya/issues/34 +[#35]: https://github.com/soywod/himalaya/issues/35 [#38]: https://github.com/soywod/himalaya/issues/38 [#39]: https://github.com/soywod/himalaya/issues/39 [#40]: https://github.com/soywod/himalaya/issues/40 diff --git a/README.md b/README.md index 6629d71..70c12e2 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ Minimalist CLI email client, written in Rust. * [Write a new message](#write-a-new-message) * [Reply to a message](#reply-to-a-message) * [Forward a message](#forward-a-message) + * [Copy a message](#copy-a-message) + * [Move a message](#move-a-message) + * [Delete a message](#delete-a-message) * [Listen to new messages](#listen-to-new-messages) * [License](https://github.com/soywod/himalaya/blob/master/LICENSE) * [Changelog](https://github.com/soywod/himalaya/blob/master/CHANGELOG.md) @@ -167,6 +170,30 @@ himalaya forward 5123 Opens your default editor to forward a message. +### Copy a message + +```bash +himalaya copy 5123 Sent +``` + +Copies a message to the targetted mailbox. + +### Move a message + +```bash +himalaya move 5123 Drafts +``` + +Moves a message to the targetted mailbox. + +### Delete a message + +```bash +himalaya delete 5123 +``` + +Moves a message. + ### Listen to new messages ```bash diff --git a/src/flag/cli.rs b/src/flag/cli.rs index 09cc641..3581ea7 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -1,6 +1,7 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use error_chain::error_chain; +use crate::msg::cli::uid_arg; use crate::{config::model::Config, imap::model::ImapConnector}; error_chain! { @@ -10,13 +11,6 @@ error_chain! { } } -fn uid_arg<'a, 'b>() -> Arg<'a, 'b> { - Arg::with_name("uid") - .help("Message UID") - .value_name("UID") - .required(true) -} - fn flags_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name("flags") .help("IMAP flags (see https://tools.ietf.org/html/rfc3501#page-11)") diff --git a/src/imap/model.rs b/src/imap/model.rs index 8b993eb..2040f03 100644 --- a/src/imap/model.rs +++ b/src/imap/model.rs @@ -5,6 +5,7 @@ use std::net::TcpStream; use crate::{ config::model::{Account, Config}, + flag::model::Flag, mbox::model::{Mbox, Mboxes}, msg::model::Msg, }; @@ -203,7 +204,7 @@ impl<'ic> ImapConnector<'ic> { match self .sess - .uid_fetch(uid, "BODY[]") + .uid_fetch(uid, "(FLAGS BODY[])") .chain_err(|| "Cannot fetch bodies")? .first() { @@ -212,10 +213,10 @@ impl<'ic> ImapConnector<'ic> { } } - pub fn append_msg(&mut self, mbox: &str, msg: &[u8]) -> Result<()> { + pub fn append_msg(&mut self, mbox: &str, msg: &[u8], flags: &[Flag]) -> Result<()> { self.sess - .append_with_flags(mbox, msg, &[imap::types::Flag::Seen]) - .chain_err(|| format!("Cannot append message to `{}` with \\Seen flag", mbox))?; + .append_with_flags(mbox, msg, flags) + .chain_err(|| format!("Cannot append message to `{}`", mbox))?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index b1b9579..48851d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ use crate::{ config::cli::account_arg, flag::cli::{flag_matches, flag_subcmds}, imap::cli::{imap_matches, imap_subcmds}, - mbox::cli::{mbox_arg, mbox_matches, mbox_subcmds}, + mbox::cli::{mbox_matches, mbox_source_arg, mbox_subcmds}, msg::cli::{msg_matches, msg_subcmds}, output::cli::output_arg, }; @@ -55,7 +55,7 @@ fn run() -> Result<()> { .author(env!("CARGO_PKG_AUTHORS")) .arg(output_arg()) .arg(account_arg()) - .arg(mbox_arg()) + .arg(mbox_source_arg()) .subcommands(flag_subcmds()) .subcommands(imap_subcmds()) .subcommands(mbox_subcmds()) diff --git a/src/mbox/cli.rs b/src/mbox/cli.rs index 0a59c07..4ebb688 100644 --- a/src/mbox/cli.rs +++ b/src/mbox/cli.rs @@ -12,15 +12,21 @@ error_chain! { } } -pub fn mbox_arg<'a>() -> Arg<'a, 'a> { +pub fn mbox_source_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("mailbox") .short("m") .long("mailbox") .help("Selects a specific mailbox") - .value_name("STRING") + .value_name("MAILBOX") .default_value("INBOX") } +pub fn mbox_target_arg<'a>() -> Arg<'a, 'a> { + Arg::with_name("target") + .help("Specifies the targetted mailbox") + .value_name("TARGET") +} + pub fn mbox_subcmds<'a>() -> Vec> { vec![SubCommand::with_name("mailboxes") .aliases(&["mailbox", "mboxes", "mbox", "m"]) diff --git a/src/msg/cli.rs b/src/msg/cli.rs index 60413c1..383a8ba 100644 --- a/src/msg/cli.rs +++ b/src/msg/cli.rs @@ -1,11 +1,13 @@ use clap::{self, App, Arg, ArgMatches, SubCommand}; use error_chain::error_chain; -use std::fs; +use std::{fs, ops::Deref}; use crate::{ config::model::Config, + flag::model::Flag, imap::model::ImapConnector, input, + mbox::cli::mbox_target_arg, msg::model::{Attachments, Msg, Msgs, ReadableMsg}, output::utils::print, smtp, @@ -22,9 +24,9 @@ error_chain! { } } -fn uid_arg<'a>() -> Arg<'a, 'a> { +pub fn uid_arg<'a>() -> Arg<'a, 'a> { Arg::with_name("uid") - .help("Message UID") + .help("Specifies the targetted message") .value_name("UID") .required(true) } @@ -95,16 +97,30 @@ pub fn msg_subcmds<'a>() -> Vec> { ), SubCommand::with_name("attachments") .aliases(&["attach", "att", "a"]) - .about("Downloads all attachments from an email") + .about("Downloads all message attachments") .arg(uid_arg()), SubCommand::with_name("reply") .aliases(&["rep", "re"]) - .about("Answers to an email") + .about("Answers to a message") .arg(uid_arg()) .arg(reply_all_arg()), SubCommand::with_name("forward") .aliases(&["fwd", "f"]) - .about("Forwards an email") + .about("Forwards a message") + .arg(uid_arg()), + SubCommand::with_name("copy") + .aliases(&["cp", "c"]) + .about("Copy a message to the targetted mailbox") + .arg(uid_arg()) + .arg(mbox_target_arg()), + SubCommand::with_name("move") + .aliases(&["mv", "m"]) + .about("Move a message to the targetted mailbox") + .arg(uid_arg()) + .arg(mbox_target_arg()), + SubCommand::with_name("delete") + .aliases(&["remove", "rm", "del", "d"]) + .about("Delete a message") .arg(uid_arg()), SubCommand::with_name("template") .aliases(&["tpl", "t"]) @@ -243,13 +259,13 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { println!("Sending…"); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; - imap_conn.append_msg("Sent", &msg.formatted())?; + imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; println!("Done!"); break; } input::Choice::Draft => { println!("Saving to draft…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?)?; + imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; println!("Done!"); break; } @@ -294,6 +310,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let tpl = msg.build_forward_tpl(&config, &account)?; print(&output_fmt, &tpl)?; + break; } break; @@ -318,14 +335,14 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { input::Choice::Send => { println!("Sending…"); smtp::send(&account, &msg.to_sendable_msg()?)?; - imap_conn.append_msg("Sent", &msg.to_vec()?)?; + imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; imap_conn.add_flags(mbox, uid, "\\Answered")?; println!("Done!"); break; } input::Choice::Draft => { println!("Saving to draft…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?)?; + imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; println!("Done!"); break; } @@ -356,13 +373,13 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { input::Choice::Send => { println!("Sending…"); smtp::send(&account, &msg.to_sendable_msg()?)?; - imap_conn.append_msg("Sent", &msg.to_vec()?)?; + imap_conn.append_msg("Sent", &msg.to_vec()?, &[Flag::Seen])?; println!("Done!"); break; } input::Choice::Draft => { println!("Saving to draft…"); - imap_conn.append_msg("Drafts", &msg.to_vec()?)?; + imap_conn.append_msg("Drafts", &msg.to_vec()?, &[Flag::Seen])?; println!("Done!"); break; } @@ -379,13 +396,44 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { break; } + if let Some(matches) = matches.subcommand_matches("copy") { + let uid = matches.value_of("uid").unwrap(); + let target = matches.value_of("target").unwrap(); + + let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); + let mut flags = msg.flags.deref().to_vec(); + flags.push(Flag::Seen); + + imap_conn.append_msg(target, &msg.to_vec()?, &flags)?; + break; + } + + if let Some(matches) = matches.subcommand_matches("move") { + let uid = matches.value_of("uid").unwrap(); + let target = matches.value_of("target").unwrap(); + + let msg = Msg::from(imap_conn.read_msg(&mbox, &uid)?); + let mut flags = msg.flags.deref().to_vec(); + flags.push(Flag::Seen); + + imap_conn.append_msg(target, &msg.to_vec()?, msg.flags.deref())?; + imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; + break; + } + + if let Some(matches) = matches.subcommand_matches("delete") { + let uid = matches.value_of("uid").unwrap(); + imap_conn.add_flags(mbox, uid, "\\Seen \\Deleted")?; + break; + } + if let Some(matches) = matches.subcommand_matches("send") { let msg = matches.value_of("message").unwrap(); let msg = Msg::from(msg.to_string()); let msg = msg.to_sendable_msg()?; smtp::send(&account, &msg)?; - imap_conn.append_msg("Sent", &msg.formatted())?; + imap_conn.append_msg("Sent", &msg.formatted(), &[Flag::Seen])?; break; } @@ -393,7 +441,7 @@ pub fn msg_matches(matches: &ArgMatches) -> Result<()> { let msg = matches.value_of("message").unwrap(); let msg = Msg::from(msg.to_string()); - imap_conn.append_msg(mbox, &msg.to_vec()?)?; + imap_conn.append_msg(mbox, &msg.to_vec()?, &[Flag::Seen])?; break; } diff --git a/src/msg/model.rs b/src/msg/model.rs index 46888ee..080c72a 100644 --- a/src/msg/model.rs +++ b/src/msg/model.rs @@ -567,7 +567,7 @@ impl<'m> DisplayTable<'m, Msg<'m>> for Msgs<'m> { impl<'m> From<&'m imap::types::ZeroCopy>> for Msgs<'m> { fn from(fetches: &'m imap::types::ZeroCopy>) -> Self { - Self(fetches.iter().map(Msg::from).collect::>()) + Self(fetches.iter().rev().map(Msg::from).collect::>()) } }