From 02c881ac007af6950dcb8724ea08596b7c2e9564 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 15 Jun 2020 01:07:50 +0300 Subject: [PATCH] Add save-attachment option for entire message as eml --- meli.1 | 7 ++++ src/components/mail/view.rs | 64 ++++++++++++++++++++++++++++++++++++- src/execute.rs | 31 ++++++++++++++---- 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/meli.1 b/meli.1 index b00ae5f7..083e86e4 100644 --- a/meli.1 +++ b/meli.1 @@ -121,6 +121,13 @@ See for the location of the mailcap files and .Xr mailcap 5 for their syntax. +You can save individual attachments with the +.Cm save-attachment Ar INDEX Ar path-to-file +where +.Ar INDEX +is the attachment's index in the listing. +If the zeroth index is provided, the entire message is saved. +If the path provided is a directory, the message is saved as an eml file with its filename set to the messages message-id. .Sh SEARCH Each e-mail storage backend has its default search method. .Em IMAP diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index e63e8b53..2c16bb32 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -1233,7 +1233,69 @@ impl Component for MailView { use std::io::Write; let account = &mut context.accounts[self.coordinates.0]; let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); - let op = account.operation(envelope.hash()); + let mut op = account.operation(envelope.hash()); + + if a_i == 0 { + let mut path = std::path::Path::new(path).to_path_buf(); + // Save entire message as eml + if path.is_dir() { + path.push(format!("{}.eml", envelope.message_id_display())); + } + let bytes = match op.as_bytes() { + Ok(b) => b, + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some("Failed to open e-mail".to_string()), + err.to_string(), + Some(NotificationType::ERROR), + )); + log( + format!( + "Failed to open envelope {}: {}", + envelope.message_id_display(), + err.to_string() + ), + ERROR, + ); + return true; + } + }; + let mut f = match std::fs::File::create(&path) { + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some(format!("Failed to create file at {}", path.display())), + err.to_string(), + Some(NotificationType::ERROR), + )); + log( + format!( + "Failed to create file at {}: {}", + path.display(), + err.to_string() + ), + ERROR, + ); + return true; + } + Ok(f) => f, + }; + use std::os::unix::fs::PermissionsExt; + let metadata = f.metadata().unwrap(); + let mut permissions = metadata.permissions(); + + permissions.set_mode(0o600); // Read/write for owner only. + f.set_permissions(permissions).unwrap(); + + f.write_all(bytes).unwrap(); + f.flush().unwrap(); + context.replies.push_back(UIEvent::Notification( + None, + format!("Saved at {}", &path.display()), + Some(NotificationType::INFO), + )); + + return true; + } let attachments = match envelope.body(op) { Ok(body) => body.attachments(), diff --git a/src/execute.rs b/src/execute.rs index 780c12d8..abf2d321 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -124,7 +124,9 @@ impl TokenStream { } Seq(_s) => {} RestOfStringValue => {} - IndexValue + AttachmentIndexValue + | MailboxIndexValue + | IndexValue | Filepath | AccountName | MailboxPath @@ -169,7 +171,9 @@ impl TokenStream { tokens.push((*s, *t.inner())); return tokens; } - IndexValue + AttachmentIndexValue + | MailboxIndexValue + | IndexValue | Filepath | AccountName | MailboxPath @@ -179,8 +183,8 @@ impl TokenStream { while ptr + 1 < s.len() && !s.as_bytes()[ptr].is_ascii_whitespace() { ptr += 1; } - tokens.push((&s[..ptr], *t.inner())); - *s = &s[ptr..]; + tokens.push((&s[..ptr + 1], *t.inner())); + *s = &s[ptr + 1..]; } } } @@ -220,6 +224,8 @@ pub enum Token { QuotedStringValue, RestOfStringValue, AlphanumericStringValue, + AttachmentIndexValue, + MailboxIndexValue, IndexValue, } @@ -305,7 +311,7 @@ define_commands!([ }, { tags: ["go"], desc: "go [n], switch to nth mailbox in this account", - tokens: &[One(Literal("goto")), One(IndexValue)], + tokens: &[One(Literal("goto")), One(MailboxIndexValue)], parser: ( fn goto(input: &[u8]) -> IResult<&[u8], Action> { let (input, _) = tag("go")(input)?; @@ -494,6 +500,7 @@ define_commands!([ parser:( fn create_mailbox(input: &[u8]) -> IResult<&[u8], Action> { let (input, _) = tag("create-mailbox")(input.trim())?; + let (input, _) = is_a(" ")(input)?; let (input, account) = quoted_argument(input)?; let (input, _) = is_a(" ")(input)?; let (input, path) = quoted_argument(input)?; @@ -583,7 +590,7 @@ define_commands!([ }, { tags: ["save-attachment "], desc: "save-attachment INDEX PATH", - tokens: &[One(Literal("save-attachment")), One(IndexValue), One(Filepath)], + tokens: &[One(Literal("save-attachment")), One(AttachmentIndexValue), One(Filepath)], parser:( fn save_attachment(input: &[u8]) -> IResult<&[u8], Action> { let (input, _) = tag("save-attachment")(input.trim())?; @@ -749,9 +756,21 @@ fn test_parser() { /// Get command suggestions for input pub fn command_completion_suggestions(input: &str) -> Vec { + use crate::melib::ShellExpandTrait; let mut sugg = Default::default(); for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() { let _m = tokens.matches(&mut &(*input), &mut sugg); + if _m.is_empty() { + continue; + } + if let Some((s, Filepath)) = _m.last() { + let p = std::path::Path::new(s); + sugg.extend( + p.complete(true) + .into_iter() + .map(|m| m.into()), + ); + } } sugg.into_iter() .map(|s| {