Add save-attachment option for entire message as eml

memfd
Manos Pitsidianakis 2020-06-15 01:07:50 +03:00
parent d7e4bd9379
commit 02c881ac00
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
3 changed files with 95 additions and 7 deletions

7
meli.1
View File

@ -121,6 +121,13 @@ See
for the location of the mailcap files and for the location of the mailcap files and
.Xr mailcap 5 .Xr mailcap 5
for their syntax. 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 .Sh SEARCH
Each e-mail storage backend has its default search method. Each e-mail storage backend has its default search method.
.Em IMAP .Em IMAP

View File

@ -1233,7 +1233,69 @@ impl Component for MailView {
use std::io::Write; use std::io::Write;
let account = &mut context.accounts[self.coordinates.0]; let account = &mut context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); 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) { let attachments = match envelope.body(op) {
Ok(body) => body.attachments(), Ok(body) => body.attachments(),

View File

@ -124,7 +124,9 @@ impl TokenStream {
} }
Seq(_s) => {} Seq(_s) => {}
RestOfStringValue => {} RestOfStringValue => {}
IndexValue AttachmentIndexValue
| MailboxIndexValue
| IndexValue
| Filepath | Filepath
| AccountName | AccountName
| MailboxPath | MailboxPath
@ -169,7 +171,9 @@ impl TokenStream {
tokens.push((*s, *t.inner())); tokens.push((*s, *t.inner()));
return tokens; return tokens;
} }
IndexValue AttachmentIndexValue
| MailboxIndexValue
| IndexValue
| Filepath | Filepath
| AccountName | AccountName
| MailboxPath | MailboxPath
@ -179,8 +183,8 @@ impl TokenStream {
while ptr + 1 < s.len() && !s.as_bytes()[ptr].is_ascii_whitespace() { while ptr + 1 < s.len() && !s.as_bytes()[ptr].is_ascii_whitespace() {
ptr += 1; ptr += 1;
} }
tokens.push((&s[..ptr], *t.inner())); tokens.push((&s[..ptr + 1], *t.inner()));
*s = &s[ptr..]; *s = &s[ptr + 1..];
} }
} }
} }
@ -220,6 +224,8 @@ pub enum Token {
QuotedStringValue, QuotedStringValue,
RestOfStringValue, RestOfStringValue,
AlphanumericStringValue, AlphanumericStringValue,
AttachmentIndexValue,
MailboxIndexValue,
IndexValue, IndexValue,
} }
@ -305,7 +311,7 @@ define_commands!([
}, },
{ tags: ["go"], { tags: ["go"],
desc: "go [n], switch to nth mailbox in this account", desc: "go [n], switch to nth mailbox in this account",
tokens: &[One(Literal("goto")), One(IndexValue)], tokens: &[One(Literal("goto")), One(MailboxIndexValue)],
parser: ( parser: (
fn goto(input: &[u8]) -> IResult<&[u8], Action> { fn goto(input: &[u8]) -> IResult<&[u8], Action> {
let (input, _) = tag("go")(input)?; let (input, _) = tag("go")(input)?;
@ -494,6 +500,7 @@ define_commands!([
parser:( parser:(
fn create_mailbox(input: &[u8]) -> IResult<&[u8], Action> { fn create_mailbox(input: &[u8]) -> IResult<&[u8], Action> {
let (input, _) = tag("create-mailbox")(input.trim())?; let (input, _) = tag("create-mailbox")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, account) = quoted_argument(input)?; let (input, account) = quoted_argument(input)?;
let (input, _) = is_a(" ")(input)?; let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?; let (input, path) = quoted_argument(input)?;
@ -583,7 +590,7 @@ define_commands!([
}, },
{ tags: ["save-attachment "], { tags: ["save-attachment "],
desc: "save-attachment INDEX PATH", desc: "save-attachment INDEX PATH",
tokens: &[One(Literal("save-attachment")), One(IndexValue), One(Filepath)], tokens: &[One(Literal("save-attachment")), One(AttachmentIndexValue), One(Filepath)],
parser:( parser:(
fn save_attachment(input: &[u8]) -> IResult<&[u8], Action> { fn save_attachment(input: &[u8]) -> IResult<&[u8], Action> {
let (input, _) = tag("save-attachment")(input.trim())?; let (input, _) = tag("save-attachment")(input.trim())?;
@ -749,9 +756,21 @@ fn test_parser() {
/// Get command suggestions for input /// Get command suggestions for input
pub fn command_completion_suggestions(input: &str) -> Vec<String> { pub fn command_completion_suggestions(input: &str) -> Vec<String> {
use crate::melib::ShellExpandTrait;
let mut sugg = Default::default(); let mut sugg = Default::default();
for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() { for (_tags, _desc, tokens) in COMMAND_COMPLETION.iter() {
let _m = tokens.matches(&mut &(*input), &mut sugg); 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() sugg.into_iter()
.map(|s| { .map(|s| {