mail/view: show multipart/alternative files properly in attachment list

Show entire multipart/alternative alternatives in attachment list
instead of only the displayed one, in order for the user to be able to
switch alternatives at will.
jmap-eventsource
Manos Pitsidianakis 2020-11-28 15:13:02 +02:00
parent 98c1ece28d
commit 883b3e3a4f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 127 additions and 104 deletions

View File

@ -92,6 +92,11 @@ impl ViewMode {
#[derive(Debug)]
pub enum AttachmentDisplay {
Alternative {
inner: Attachment,
shown_display: usize,
display: Vec<AttachmentDisplay>,
},
InlineText {
inner: Attachment,
comment: Option<String>,
@ -436,6 +441,17 @@ impl MailView {
for d in displays {
use AttachmentDisplay::*;
match d {
Alternative {
inner: _,
shown_display,
display,
} => {
acc.push_str(&self.attachment_displays_to_text(
&display[*shown_display..(*shown_display + 1)],
context,
show_comments,
));
}
InlineText {
inner: _,
text,
@ -554,10 +570,26 @@ impl MailView {
let mut paths = Vec::with_capacity(displays.len());
let mut cur_path = vec![];
let mut idx = 0;
for (i, d) in displays.iter().enumerate() {
fn append_entry(
(idx, (depth, att_display)): (&mut usize, (usize, &AttachmentDisplay)),
branches: &mut SmallVec<[bool; 8]>,
paths: &mut Vec<Vec<usize>>,
cur_path: &mut Vec<usize>,
has_sibling: bool,
s: &mut String,
) {
use AttachmentDisplay::*;
cur_path.push(i);
match d {
let mut default_alternative: Option<usize> = None;
let (att, sub_att_display_vec) = match att_display {
Alternative {
inner,
shown_display,
display,
} => {
default_alternative = Some(*shown_display);
(inner, display.as_slice())
}
InlineText {
inner,
text: _,
@ -565,41 +597,95 @@ impl MailView {
}
| InlineOther { inner }
| Attachment { inner }
| SignedPending {
| EncryptedPending { inner, handle: _ }
| EncryptedFailed { inner, error: _ } => (inner, &[][..]),
SignedPending {
inner,
display: _,
display,
handle: _,
job_id: _,
}
| SignedUnverified { inner, display: _ }
| SignedUnverified { inner, display }
| SignedFailed {
inner,
display: _,
display,
error: _,
}
| SignedVerified {
inner,
display: _,
display,
description: _,
}
| EncryptedPending { inner, handle: _ }
| EncryptedFailed { inner, error: _ }
| EncryptedSuccess {
inner: _,
plaintext: inner,
plaintext_display: _,
plaintext_display: display,
description: _,
} => {
attachment_tree(
(&mut idx, (0, inner)),
&mut branches,
&mut paths,
&mut cur_path,
i + 1 < displays.len(),
&mut acc,
);
} => (inner, display.as_slice()),
};
s.extend(format!("\n[{}]", idx).chars());
for &b in branches.iter() {
if b {
s.push('|');
} else {
s.push(' ');
}
s.push(' ');
}
if depth > 0 {
if has_sibling {
s.push('|');
} else {
s.push(' ');
}
s.push_str("\\_ ");
} else {
s.push(' ');
s.push(' ');
}
s.extend(att.to_string().chars());
paths.push(cur_path.clone());
match att.content_type {
ContentType::Multipart { .. } => {
let mut iter = (0..sub_att_display_vec.len()).peekable();
if has_sibling {
branches.push(true);
} else {
branches.push(false);
}
while let Some(i) = iter.next() {
*idx += 1;
cur_path.push(i);
append_entry(
(idx, (depth + 1, &sub_att_display_vec[i])),
branches,
paths,
cur_path,
iter.peek() != None,
s,
);
if Some(i) == default_alternative {
s.push_str(" (displayed by default)");
}
cur_path.pop();
}
branches.pop();
}
_ => {}
}
}
for (i, d) in displays.iter().enumerate() {
cur_path.push(i);
append_entry(
(&mut idx, (0, d)),
&mut branches,
&mut paths,
&mut cur_path,
i + 1 < displays.len(),
&mut acc,
);
cur_path.pop();
idx += 1;
}
@ -692,6 +778,11 @@ impl MailView {
{
match kind {
MultipartType::Alternative => {
if parts.is_empty() {
return;
}
let mut display = vec![];
let mut chosen_attachment_idx = 0;
if let Some(text_attachment_pos) =
parts.iter().position(|a| a.content_type == "text/plain")
{
@ -708,33 +799,21 @@ impl MailView {
parts.iter().position(|a| a.content_type == "text/html")
{
/* Select html alternative since text/plain is empty */
rec(
&parts[text_attachment_pos],
context,
coordinates,
acc,
active_jobs,
);
} else {
for a in parts {
rec(a, context, coordinates, acc, active_jobs);
}
chosen_attachment_idx = text_attachment_pos;
}
} else {
/* Select text/plain alternative */
rec(
&parts[text_attachment_pos],
context,
coordinates,
acc,
active_jobs,
);
}
} else {
for a in parts {
rec(a, context, coordinates, acc, active_jobs);
chosen_attachment_idx = text_attachment_pos;
}
}
for a in parts {
rec(a, context, coordinates, &mut display, active_jobs);
}
acc.push(AttachmentDisplay::Alternative {
inner: a.clone(),
shown_display: chosen_attachment_idx,
display,
});
}
MultipartType::Signed => {
#[cfg(not(feature = "gpgme"))]
@ -868,7 +947,12 @@ impl MailView {
let first = path[0];
use AttachmentDisplay::*;
let root_attachment = match &display[first] {
InlineText {
Alternative {
inner,
shown_display: _,
display: _,
}
| InlineText {
inner,
text: _,
comment: _,
@ -2441,67 +2525,6 @@ impl Component for MailView {
}
}
fn attachment_tree(
(idx, (depth, att)): (&mut usize, (usize, &Attachment)),
branches: &mut SmallVec<[bool; 8]>,
paths: &mut Vec<Vec<usize>>,
cur_path: &mut Vec<usize>,
has_sibling: bool,
s: &mut String,
) {
s.extend(format!("\n[{}]", idx).chars());
for &b in branches.iter() {
if b {
s.push('|');
} else {
s.push(' ');
}
s.push(' ');
}
if depth > 0 {
if has_sibling {
s.push('|');
} else {
s.push(' ');
}
s.push_str("\\_ ");
} else {
s.push(' ');
s.push(' ');
}
s.extend(att.to_string().chars());
paths.push(cur_path.clone());
match att.content_type {
ContentType::Multipart {
parts: ref sub_att_vec,
..
} => {
let mut iter = (0..sub_att_vec.len()).peekable();
if has_sibling {
branches.push(true);
} else {
branches.push(false);
}
while let Some(i) = iter.next() {
*idx += 1;
cur_path.push(i);
attachment_tree(
(idx, (depth + 1, &sub_att_vec[i])),
branches,
paths,
cur_path,
iter.peek() != None,
s,
);
cur_path.pop();
}
branches.pop();
}
_ => {}
}
}
fn save_attachment(path: &std::path::Path, bytes: &[u8]) -> Result<()> {
let mut f = std::fs::File::create(path)?;
let mut permissions = f.metadata()?.permissions();