summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2019-11-10 13:33:22 +0200
committerManos Pitsidianakis <el13635@mail.ntua.gr>2019-11-10 13:33:22 +0200
commit06d99c7f92a97a90cac602207d12133ea5fdbf3e (patch)
tree2cc05f74c8150be7f37c4c3c8ec758e86e3e52bb
parent580f0be8a4ce01ef008915c7ed840c947eee99b1 (diff)
ui: Add save attachment command
use as `save-attachment ATTACHMENT_INDEX PATH`
-rw-r--r--melib/src/email/attachments.rs14
-rw-r--r--ui/src/components/mail/view.rs106
-rw-r--r--ui/src/components/utilities.rs4
-rw-r--r--ui/src/execute.rs23
-rw-r--r--ui/src/execute/actions.rs5
5 files changed, 144 insertions, 8 deletions
diff --git a/melib/src/email/attachments.rs b/melib/src/email/attachments.rs
index 7f54ea80..88475d20 100644
--- a/melib/src/email/attachments.rs
+++ b/melib/src/email/attachments.rs
@@ -172,7 +172,15 @@ impl AttachmentBuilder {
let mut name: Option<String> = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"name") {
- name = Some(String::from_utf8_lossy(v).into());
+ if let Ok(v) = crate::email::parser::phrase(v.trim())
+ .to_full_result()
+ .as_ref()
+ .and_then(|r| Ok(String::from_utf8_lossy(r).to_string()))
+ {
+ name = Some(v);
+ } else {
+ name = Some(String::from_utf8_lossy(v).into());
+ }
break;
}
}
@@ -334,6 +342,10 @@ impl fmt::Display for Attachment {
ContentType::OctetStream { ref name } => {
write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type()))
}
+ ContentType::Other {
+ name: Some(ref name),
+ ..
+ } => write!(f, "\"{}\", [{}]", name, self.mime_type()),
ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()),
ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
ContentType::Multipart {
diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs
index df5f06a5..9a9f6028 100644
--- a/ui/src/components/mail/view.rs
+++ b/ui/src/components/mail/view.rs
@@ -957,6 +957,112 @@ impl Component for MailView {
UIEvent::EnvelopeRename(old_hash, new_hash) if self.coordinates.2 == old_hash => {
self.coordinates.2 = new_hash;
}
+ UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => {
+ 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 attachments = match envelope.body(op) {
+ Ok(body) => body.attachments(),
+ Err(e) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some("Failed to open e-mail".to_string()),
+ e.to_string(),
+ Some(NotificationType::ERROR),
+ ));
+ log(
+ format!(
+ "Failed to open envelope {}: {}",
+ envelope.message_id_display(),
+ e.to_string()
+ ),
+ ERROR,
+ );
+ return true;
+ }
+ };
+ if let Some(u) = attachments.get(a_i) {
+ match u.content_type() {
+ ContentType::MessageRfc822
+ | ContentType::Text { .. }
+ | ContentType::PGPSignature => {
+ debug!(path);
+ let mut f = match std::fs::File::create(path) {
+ Err(e) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!("Failed to create file at {}", path)),
+ e.to_string(),
+ Some(NotificationType::ERROR),
+ ));
+ log(
+ format!(
+ "Failed to create file at {}: {}",
+ path,
+ e.to_string()
+ ),
+ ERROR,
+ );
+ return true;
+ }
+ Ok(f) => f,
+ };
+
+ f.write_all(&decode(u, None)).unwrap();
+ f.flush().unwrap();
+ }
+
+ ContentType::Multipart { .. } => {
+ context.replies.push_back(UIEvent::StatusEvent(
+ StatusEvent::DisplayMessage(
+ "Multipart attachments are not supported yet.".to_string(),
+ ),
+ ));
+ return true;
+ }
+ ContentType::OctetStream { name: ref _name }
+ | ContentType::Other {
+ name: ref _name, ..
+ } => {
+ let mut f = match std::fs::File::create(path.trim()) {
+ Err(e) => {
+ context.replies.push_back(UIEvent::Notification(
+ Some(format!("Failed to create file at {}", path)),
+ e.to_string(),
+ Some(NotificationType::ERROR),
+ ));
+ log(
+ format!(
+ "Failed to create file at {}: {}",
+ path,
+ e.to_string()
+ ),
+ ERROR,
+ );
+ return true;
+ }
+ Ok(f) => f,
+ };
+
+ f.write_all(&decode(u, None)).unwrap();
+ f.flush().unwrap();
+ }
+ }
+ context.replies.push_back(UIEvent::Notification(
+ None,
+ format!("Saved at {}", &path),
+ Some(NotificationType::INFO),
+ ));
+ } else {
+ context
+ .replies
+ .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
+ "Attachment `{}` not found.",
+ a_i
+ ))));
+ return true;
+ }
+ }
UIEvent::Action(MailingListAction(ref e)) => {
let unsafe_context = context as *mut Context;
let account = &context.accounts[self.coordinates.0];
diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs
index 91e06a12..29f1d5a5 100644
--- a/ui/src/components/utilities.rs
+++ b/ui/src/components/utilities.rs
@@ -531,7 +531,7 @@ impl Component for Pager {
UIEvent::ChangeMode(UIMode::Normal) => {
self.dirty = true;
}
- UIEvent::Action(Pager(Pipe(ref bin, ref args))) => {
+ UIEvent::Action(View(Pipe(ref bin, ref args))) => {
use std::io::Write;
use std::process::{Command, Stdio};
let mut command_obj = match Command::new(bin)
@@ -987,6 +987,7 @@ impl Component for StatusBar {
self.cmd_history.push(self.ex_buffer.as_str().to_string());
}
self.ex_buffer.clear();
+ self.ex_buffer_cmd_history_pos.take();
}
UIMode::Execute => {
self.height = 2;
@@ -1019,6 +1020,7 @@ impl Component for StatusBar {
UIEvent::ExInput(Key::Ctrl('u')) => {
self.dirty = true;
self.ex_buffer.clear();
+ self.ex_buffer_cmd_history_pos.take();
return true;
}
UIEvent::ExInput(k @ Key::Backspace) | UIEvent::ExInput(k @ Key::Ctrl('h')) => {
diff --git a/ui/src/execute.rs b/ui/src/execute.rs
index 8741058f..6387a32e 100644
--- a/ui/src/execute.rs
+++ b/ui/src/execute.rs
@@ -32,8 +32,8 @@ pub use crate::actions::Action::{self, *};
pub use crate::actions::ComposeAction::{self, *};
pub use crate::actions::ListingAction::{self, *};
pub use crate::actions::MailingListAction::{self, *};
-pub use crate::actions::PagerAction::{self, *};
pub use crate::actions::TabAction::{self, *};
+pub use crate::actions::ViewAction::{self, *};
use std::str::FromStr;
/* Create a const table with every command part that can be auto-completed and its description */
@@ -180,12 +180,12 @@ define_commands!([
>> is_a!(" ")
>> args: separated_list!(is_a!(" "), is_not!(" "))
>> ({
- Pager(Pipe(bin.to_string(), args.into_iter().map(|v| String::from_utf8(v.to_vec()).unwrap()).collect::<Vec<String>>()))
+ View(Pipe(bin.to_string(), args.into_iter().map(|v| String::from_utf8(v.to_vec()).unwrap()).collect::<Vec<String>>()))
})) | do_parse!(
ws!(tag!("pipe"))
>> bin: ws!(map_res!(is_not!(" "), std::str::from_utf8))
>> ({
- Pager(Pipe(bin.to_string(), Vec::new()))
+ View(Pipe(bin.to_string(), Vec::new()))
})
))
);
@@ -320,6 +320,19 @@ define_commands!([
)
);
)
+ },
+ { tags: ["save-attachment "],
+ desc: "save-attachment INDEX PATH",
+ parser:(
+ named!( save_attachment<Action>,
+ do_parse!(
+ ws!(tag!("save-attachment"))
+ >> idx: map_res!(map_res!(is_not!(" "), std::str::from_utf8), usize::from_str)
+ >> path: ws!(map_res!(call!(not_line_ending), std::str::from_utf8))
+ >> (View(SaveAttachment(idx, path.to_string())))
+ )
+ );
+ )
}
]);
@@ -379,6 +392,8 @@ named!(
named!(account_action<Action>, alt_complete!(reindex));
+named!(view<Action>, alt_complete!(pipe | save_attachment));
+
named!(pub parse_command<Action>,
- alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv | pipe | compose_action | create_folder | sub_folder | unsub_folder | delete_folder | rename_folder | account_action )
+ alt_complete!( goto | listing_action | sort | subsort | close | mailinglist | setenv | printenv | view | compose_action | create_folder | sub_folder | unsub_folder | delete_folder | rename_folder | account_action )
);
diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs
index a76a678c..07f79c57 100644
--- a/ui/src/execute/actions.rs
+++ b/ui/src/execute/actions.rs
@@ -63,8 +63,9 @@ pub enum MailingListAction {
}
#[derive(Debug)]
-pub enum PagerAction {
+pub enum ViewAction {
Pipe(String, Vec<String>),
+ SaveAttachment(usize, String),
}
#[derive(Debug)]
@@ -88,7 +89,7 @@ pub enum Action {
Tab(TabAction),
ToggleThreadSnooze,
MailingListAction(MailingListAction),
- Pager(PagerAction),
+ View(ViewAction),
SetEnv(String, String),
PrintEnv(String),
Compose(ComposeAction),