BackendOp: return future in as_bytes()

memfd
Manos Pitsidianakis 2020-07-04 17:38:57 +03:00
parent 4721073bc3
commit b3876113aa
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
19 changed files with 562 additions and 571 deletions

View File

@ -436,7 +436,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
/// let operation = Box::new(FooOp {}); /// let operation = Box::new(FooOp {});
/// ``` /// ```
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send { pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
fn as_bytes(&mut self) -> Result<&[u8]>; fn as_bytes(&mut self) -> ResultFuture<Vec<u8>>;
fn fetch_flags(&self) -> ResultFuture<Flag>; fn fetch_flags(&self) -> ResultFuture<Flag>;
fn set_flag(&mut self, flag: Flag, value: bool) -> ResultFuture<()>; fn set_flag(&mut self, flag: Flag, value: bool) -> ResultFuture<()>;
fn set_tag(&mut self, tag: String, value: bool) -> ResultFuture<()>; fn set_tag(&mut self, tag: String, value: bool) -> ResultFuture<()>;
@ -458,7 +458,7 @@ impl ReadOnlyOp {
} }
impl BackendOp for ReadOnlyOp { impl BackendOp for ReadOnlyOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
self.op.as_bytes() self.op.as_bytes()
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {

View File

@ -31,12 +31,7 @@ use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImapOp { pub struct ImapOp {
uid: usize, uid: usize,
bytes: Option<String>,
headers: Option<String>,
body: Option<String>,
mailbox_path: String,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
flags: Arc<FutureMutex<Option<Flag>>>,
connection: Arc<Mutex<ImapConnection>>, connection: Arc<Mutex<ImapConnection>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
} }
@ -52,32 +47,21 @@ impl ImapOp {
ImapOp { ImapOp {
uid, uid,
connection, connection,
bytes: None,
headers: None,
body: None,
mailbox_path,
mailbox_hash, mailbox_hash,
flags: Arc::new(FutureMutex::new(None)),
uid_store, uid_store,
} }
} }
} }
impl BackendOp for ImapOp { impl BackendOp for ImapOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if self.bytes.is_none() {
let mut bytes_cache = self.uid_store.byte_cache.lock()?; let mut bytes_cache = self.uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(self.uid).or_default(); let cache = bytes_cache.entry(self.uid).or_default();
if cache.bytes.is_some() { if cache.bytes.is_none() {
self.bytes = cache.bytes.clone();
} else {
drop(cache);
drop(bytes_cache);
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
{ {
let mut conn = let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(2, 0)))?;
try_lock(&self.connection, Some(std::time::Duration::new(2, 0)))?; conn.examine_mailbox(self.mailbox_hash, &mut response)?;
conn.examine_mailbox(self.mailbox_hash, &mut response, false)?;
conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", self.uid).as_bytes())?; conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", self.uid).as_bytes())?;
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)?; conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)?;
} }
@ -96,12 +80,10 @@ impl BackendOp for ImapOp {
if let Some((flags, _)) = flags { if let Some((flags, _)) = flags {
cache.flags = Some(flags); cache.flags = Some(flags);
} }
cache.bytes = cache.bytes = Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() });
Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() });
self.bytes = cache.bytes.clone();
} }
} let ret = cache.bytes.clone().unwrap().into_bytes();
Ok(self.bytes.as_ref().unwrap().as_bytes()) Ok(Box::pin(async move { Ok(ret) }))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {
@ -109,13 +91,9 @@ impl BackendOp for ImapOp {
let mailbox_hash = self.mailbox_hash; let mailbox_hash = self.mailbox_hash;
let uid = self.uid; let uid = self.uid;
let uid_store = self.uid_store.clone(); let uid_store = self.uid_store.clone();
let flags = self.flags.clone();
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
Ok(Box::pin(async move { Ok(Box::pin(async move {
if let Some(val) = *flags.lock().await {
return Ok(val);
}
let exists_in_cache = { let exists_in_cache = {
let mut bytes_cache = uid_store.byte_cache.lock()?; let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default(); let cache = bytes_cache.entry(uid).or_default();
@ -158,8 +136,6 @@ impl BackendOp for ImapOp {
let val = cache.flags; let val = cache.flags;
val val
}; };
let mut f = flags.lock().await;
*f = val;
Ok(val.unwrap()) Ok(val.unwrap())
} }
})) }))

View File

@ -376,9 +376,6 @@ impl MailBackend for ImapType {
}; };
Ok(Box::new(ImapOp::new( Ok(Box::new(ImapOp::new(
uid, uid,
self.uid_store.mailboxes.read().unwrap()[&mailbox_hash]
.imap_path()
.to_string(),
mailbox_hash, mailbox_hash,
self.connection.clone(), self.connection.clone(),
self.uid_store.clone(), self.uid_store.clone(),

View File

@ -30,12 +30,7 @@ use std::sync::Arc;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImapOp { pub struct ImapOp {
uid: usize, uid: usize,
bytes: Option<String>,
headers: Option<String>,
body: Option<String>,
mailbox_path: String,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
flags: Arc<FutureMutex<Option<Flag>>>,
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
} }
@ -43,7 +38,6 @@ pub struct ImapOp {
impl ImapOp { impl ImapOp {
pub fn new( pub fn new(
uid: usize, uid: usize,
mailbox_path: String,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
@ -51,36 +45,32 @@ impl ImapOp {
ImapOp { ImapOp {
uid, uid,
connection, connection,
bytes: None,
headers: None,
body: None,
mailbox_path,
mailbox_hash, mailbox_hash,
flags: Arc::new(FutureMutex::new(None)),
uid_store, uid_store,
} }
} }
} }
impl BackendOp for ImapOp { impl BackendOp for ImapOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if self.bytes.is_none() { let mut response = String::with_capacity(8 * 1024);
let mut bytes_cache = self.uid_store.byte_cache.lock()?; let connection = self.connection.clone();
let cache = bytes_cache.entry(self.uid).or_default(); let mailbox_hash = self.mailbox_hash;
if cache.bytes.is_some() { let uid = self.uid;
self.bytes = cache.bytes.clone(); let uid_store = self.uid_store.clone();
} else { Ok(Box::pin(async move {
drop(cache); let exists_in_cache = {
drop(bytes_cache); let mut bytes_cache = uid_store.byte_cache.lock()?;
let ret: Result<()> = futures::executor::block_on(async { let cache = bytes_cache.entry(uid).or_default();
cache.bytes.is_some()
};
if !exists_in_cache {
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
{ {
let mut conn = self.connection.lock().await; let mut conn = connection.lock().await;
conn.examine_mailbox(self.mailbox_hash, &mut response, false) conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?; .await?;
conn.send_command( conn.send_command(format!("UID FETCH {} (FLAGS RFC822)", uid).as_bytes())
format!("UID FETCH {} (FLAGS RFC822)", self.uid).as_bytes(),
)
.await?; .await?;
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
.await?; .await?;
@ -91,25 +81,27 @@ impl BackendOp for ImapOp {
response.lines().collect::<Vec<&str>>().len() response.lines().collect::<Vec<&str>>().len()
); );
let UidFetchResponse { let UidFetchResponse {
uid, flags, body, .. uid: _uid,
flags: _flags,
body,
..
} = protocol_parser::uid_fetch_response(&response)?.1; } = protocol_parser::uid_fetch_response(&response)?.1;
assert_eq!(uid, self.uid); assert_eq!(_uid, uid);
assert!(body.is_some()); assert!(body.is_some());
let mut bytes_cache = self.uid_store.byte_cache.lock()?; let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(self.uid).or_default(); let cache = bytes_cache.entry(uid).or_default();
if let Some((flags, _)) = flags { if let Some((_flags, _)) = _flags {
//self.flags.set(Some(flags)); //flags.lock().await.set(Some(_flags));
cache.flags = Some(flags); cache.flags = Some(_flags);
} }
cache.bytes = cache.bytes =
Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() }); Some(unsafe { std::str::from_utf8_unchecked(body.unwrap()).to_string() });
self.bytes = cache.bytes.clone();
Ok(())
});
ret?;
} }
} let mut bytes_cache = uid_store.byte_cache.lock()?;
Ok(self.bytes.as_ref().unwrap().as_bytes()) let cache = bytes_cache.entry(uid).or_default();
let ret = cache.bytes.clone().unwrap().into_bytes();
Ok(ret)
}))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {
@ -118,12 +110,8 @@ impl BackendOp for ImapOp {
let mailbox_hash = self.mailbox_hash; let mailbox_hash = self.mailbox_hash;
let uid = self.uid; let uid = self.uid;
let uid_store = self.uid_store.clone(); let uid_store = self.uid_store.clone();
let flags = self.flags.clone();
Ok(Box::pin(async move { Ok(Box::pin(async move {
if let Some(val) = *flags.lock().await {
return Ok(val);
}
let exists_in_cache = { let exists_in_cache = {
let mut bytes_cache = uid_store.byte_cache.lock()?; let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default(); let cache = bytes_cache.entry(uid).or_default();
@ -158,7 +146,6 @@ impl BackendOp for ImapOp {
} }
let (_uid, (_flags, _)) = v[0]; let (_uid, (_flags, _)) = v[0];
assert_eq!(uid, uid); assert_eq!(uid, uid);
*flags.lock().await = Some(_flags);
let mut bytes_cache = uid_store.byte_cache.lock()?; let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default(); let cache = bytes_cache.entry(uid).or_default();
cache.flags = Some(_flags); cache.flags = Some(_flags);
@ -170,8 +157,6 @@ impl BackendOp for ImapOp {
let val = cache.flags; let val = cache.flags;
val val
}; };
let mut f = flags.lock().await;
*f = val;
Ok(val.unwrap()) Ok(val.unwrap())
} }
})) }))

View File

@ -56,7 +56,7 @@ impl JmapOp {
} }
impl BackendOp for JmapOp { impl BackendOp for JmapOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if self.bytes.is_none() { if self.bytes.is_none() {
let mut store_lck = self.store.write().unwrap(); let mut store_lck = self.store.write().unwrap();
if !(store_lck.byte_cache.contains_key(&self.hash) if !(store_lck.byte_cache.contains_key(&self.hash)
@ -86,7 +86,8 @@ impl BackendOp for JmapOp {
} }
self.bytes = store_lck.byte_cache[&self.hash].bytes.clone(); self.bytes = store_lck.byte_cache[&self.hash].bytes.clone();
} }
Ok(&self.bytes.as_ref().unwrap().as_bytes()) let ret = self.bytes.as_ref().unwrap().as_bytes().to_vec();
Ok(Box::pin(async move { Ok(ret) }))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {

View File

@ -90,12 +90,13 @@ impl MaildirOp {
} }
impl<'a> BackendOp for MaildirOp { impl<'a> BackendOp for MaildirOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if self.slice.is_none() { if self.slice.is_none() {
self.slice = Some(Mmap::open_path(self.path(), Protection::Read)?); self.slice = Some(Mmap::open_path(self.path(), Protection::Read)?);
} }
/* Unwrap is safe since we use ? above. */ /* Unwrap is safe since we use ? above. */
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() }) let ret = Ok((unsafe { self.slice.as_ref().unwrap().as_slice() }).to_vec());
Ok(Box::pin(async move { ret }))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {

View File

@ -182,14 +182,16 @@ impl MboxOp {
} }
impl BackendOp for MboxOp { impl BackendOp for MboxOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if self.slice.is_none() { if self.slice.is_none() {
self.slice = Some(Mmap::open_path(&self.path, Protection::Read)?); self.slice = Some(Mmap::open_path(&self.path, Protection::Read)?);
} }
/* Unwrap is safe since we use ? above. */ /* Unwrap is safe since we use ? above. */
Ok(unsafe { let ret = Ok((unsafe {
&self.slice.as_ref().unwrap().as_slice()[self.offset..self.offset + self.length] &self.slice.as_ref().unwrap().as_slice()[self.offset..self.offset + self.length]
}) })
.to_vec());
Ok(Box::pin(async move { ret }))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {

View File

@ -646,7 +646,7 @@ struct NotmuchOp {
} }
impl BackendOp for NotmuchOp { impl BackendOp for NotmuchOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
let mut message: *mut notmuch_message_t = std::ptr::null_mut(); let mut message: *mut notmuch_message_t = std::ptr::null_mut();
let index_lck = self.index.write().unwrap(); let index_lck = self.index.write().unwrap();
unsafe { unsafe {
@ -662,7 +662,8 @@ impl BackendOp for NotmuchOp {
let mut response = String::new(); let mut response = String::new();
f.read_to_string(&mut response)?; f.read_to_string(&mut response)?;
self.bytes = Some(response); self.bytes = Some(response);
Ok(self.bytes.as_ref().unwrap().as_bytes()) let ret = Ok(self.bytes.as_ref().unwrap().as_bytes().to_vec());
Ok(Box::pin(async move { ret }))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {

View File

@ -229,8 +229,8 @@ impl Envelope {
pub fn from_token(mut operation: Box<dyn BackendOp>, hash: EnvelopeHash) -> Result<Envelope> { pub fn from_token(mut operation: Box<dyn BackendOp>, hash: EnvelopeHash) -> Result<Envelope> {
let mut e = Envelope::new(hash); let mut e = Envelope::new(hash);
e.flags = futures::executor::block_on(operation.fetch_flags()?)?; e.flags = futures::executor::block_on(operation.fetch_flags()?)?;
let bytes = operation.as_bytes()?; let bytes = futures::executor::block_on(operation.as_bytes()?)?;
e.populate_headers(bytes)?; e.populate_headers(&bytes)?;
Ok(e) Ok(e)
} }
@ -413,11 +413,6 @@ impl Envelope {
_strings.join(", ") _strings.join(", ")
} }
/// Requests bytes from backend and thus can fail
pub fn bytes(&self, mut operation: Box<dyn BackendOp>) -> Result<Vec<u8>> {
operation.as_bytes().map(|v| v.to_vec())
}
pub fn body_bytes(&self, bytes: &[u8]) -> Attachment { pub fn body_bytes(&self, bytes: &[u8]) -> Attachment {
let builder = AttachmentBuilder::new(bytes); let builder = AttachmentBuilder::new(bytes);
builder.build() builder.build()
@ -439,8 +434,8 @@ impl Envelope {
/// Requests bytes from backend and thus can fail /// Requests bytes from backend and thus can fail
pub fn body(&self, mut operation: Box<dyn BackendOp>) -> Result<Attachment> { pub fn body(&self, mut operation: Box<dyn BackendOp>) -> Result<Attachment> {
debug!("searching body for {:?}", self.message_id_display()); debug!("searching body for {:?}", self.message_id_display());
let file = operation.as_bytes()?; let bytes = futures::executor::block_on(operation.as_bytes()?)?;
Ok(self.body_bytes(file)) Ok(self.body_bytes(&bytes))
} }
pub fn subject(&self) -> Cow<str> { pub fn subject(&self) -> Cow<str> {

View File

@ -138,8 +138,8 @@ impl Draft {
let mut ret = Draft::default(); let mut ret = Draft::default();
//TODO: Inform user if error //TODO: Inform user if error
{ {
let bytes = op.as_bytes().unwrap_or(&[]); let bytes = futures::executor::block_on(op.as_bytes()?)?;
for (k, v) in envelope.headers(bytes).unwrap_or_else(|_| Vec::new()) { for (k, v) in envelope.headers(&bytes).unwrap_or_else(|_| Vec::new()) {
if ignore_header(k.as_bytes()) { if ignore_header(k.as_bytes()) {
continue; continue;
} }

View File

@ -238,9 +238,10 @@ impl Composer {
Some(NotificationType::ERROR), Some(NotificationType::ERROR),
)); ));
} }
Ok(mut op) => { Ok(op) => {
let parent_bytes = op.as_bytes(); //FIXME
ret.draft = Draft::new_reply(&parent_message, parent_bytes.unwrap()); //let parent_bytes = op.as_bytes();
//ret.draft = Draft::new_reply(&parent_message, parent_bytes.unwrap());
} }
} }
let subject = parent_message.subject(); let subject = parent_message.subject();

View File

@ -203,6 +203,8 @@ pub trait MailListingTrait: ListingTrait {
} }
ListingAction::CopyTo(ref mailbox_path) => { ListingAction::CopyTo(ref mailbox_path) => {
drop(envelope); drop(envelope);
/*
* FIXME
match account match account
.mailbox_by_path(mailbox_path) .mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| { .and_then(|mailbox_hash| {
@ -219,10 +221,13 @@ pub trait MailListingTrait: ListingTrait {
} }
Ok(fut) => {} Ok(fut) => {}
} }
*/
continue; continue;
} }
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => { ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope); drop(envelope);
/*
* FIXME
if let Err(err) = op.as_bytes().map(|b| b.to_vec()).and_then(|bytes| { if let Err(err) = op.as_bytes().map(|b| b.to_vec()).and_then(|bytes| {
let account_pos = context let account_pos = context
.accounts .accounts
@ -245,10 +250,13 @@ pub trait MailListingTrait: ListingTrait {
)); ));
return; return;
} }
*/
continue; continue;
} }
ListingAction::MoveTo(ref mailbox_path) => { ListingAction::MoveTo(ref mailbox_path) => {
drop(envelope); drop(envelope);
/*
* FIXME
if let Err(err) = if let Err(err) =
account account
.mailbox_by_path(mailbox_path) .mailbox_by_path(mailbox_path)
@ -264,10 +272,12 @@ pub trait MailListingTrait: ListingTrait {
)); ));
return; return;
} }
*/
continue; continue;
} }
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => { ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope); drop(envelope);
/* FIXME
if let Err(err) = op if let Err(err) = op
.as_bytes() .as_bytes()
.map(|b| b.to_vec()) .map(|b| b.to_vec())
@ -302,10 +312,12 @@ pub trait MailListingTrait: ListingTrait {
)); ));
return; return;
} }
*/
continue; continue;
} }
ListingAction::Tag(Remove(ref tag_str)) => { ListingAction::Tag(Remove(ref tag_str)) => {
if let Err(err) = op.set_tag(tag_str.to_string(), false) { match op.set_tag(tag_str.to_string(), false) {
Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Could not set tag.".to_string()), Some("Could not set tag.".to_string()),
err.to_string(), err.to_string(),
@ -313,9 +325,14 @@ pub trait MailListingTrait: ListingTrait {
)); ));
return; return;
} }
Ok(fut) => {
//FIXME
}
}
} }
ListingAction::Tag(Add(ref tag_str)) => { ListingAction::Tag(Add(ref tag_str)) => {
if let Err(err) = op.set_tag(tag_str.to_string(), true) { match op.set_tag(tag_str.to_string(), true) {
Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Could not set tag.".to_string()), Some("Could not set tag.".to_string()),
err.to_string(), err.to_string(),
@ -323,6 +340,10 @@ pub trait MailListingTrait: ListingTrait {
)); ));
return; return;
} }
Ok(fut) => {
// FIXME
}
}
} }
_ => unreachable!(), _ => unreachable!(),
} }

View File

@ -214,7 +214,7 @@ impl MailListingTrait for PlainListing {
let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context); let env_hash = self.get_env_under_cursor(self.cursor_pos.2, context);
let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash); let temp = (self.new_cursor_pos.0, self.new_cursor_pos.1, env_hash);
if !force && old_cursor_pos == self.new_cursor_pos { if !force && old_cursor_pos == self.new_cursor_pos {
self.view.update(temp); self.view.update(temp, context);
} else if self.unfocused { } else if self.unfocused {
self.view = MailView::new(temp, None, None, context); self.view = MailView::new(temp, None, None, context);
} }

View File

@ -632,7 +632,7 @@ impl Component for ThreadListing {
); );
if let Some(ref mut v) = self.view { if let Some(ref mut v) = self.view {
v.update(coordinates); v.update(coordinates, context);
} else { } else {
self.view = Some(MailView::new(coordinates, None, None, context)); self.view = Some(MailView::new(coordinates, None, None, context));
} }

View File

@ -20,9 +20,13 @@
*/ */
use super::*; use super::*;
use crate::conf::accounts::JobRequest;
use crate::jobs::{oneshot, JobId};
use melib::list_management; use melib::list_management;
use melib::parser::BytesExt; use melib::parser::BytesExt;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::collections::HashSet;
use std::io::Write;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
@ -98,11 +102,31 @@ pub struct MailView {
headers_cursor: usize, headers_cursor: usize,
force_draw_headers: bool, force_draw_headers: bool,
theme_default: ThemeAttribute, theme_default: ThemeAttribute,
active_jobs: HashSet<JobId>,
state: MailViewState,
cmd_buf: String, cmd_buf: String,
id: ComponentId, id: ComponentId,
} }
#[derive(Debug)]
pub enum MailViewState {
Init,
LoadingBody {
job_id: JobId,
chan: oneshot::Receiver<Result<Vec<u8>>>,
},
Loaded {
body: Result<Vec<u8>>,
},
}
impl Default for MailViewState {
fn default() -> Self {
MailViewState::Init
}
}
impl Clone for MailView { impl Clone for MailView {
fn clone(&self) -> Self { fn clone(&self) -> Self {
MailView { MailView {
@ -111,6 +135,8 @@ impl Clone for MailView {
pager: self.pager.clone(), pager: self.pager.clone(),
mode: ViewMode::Normal, mode: ViewMode::Normal,
attachment_tree: self.attachment_tree.clone(), attachment_tree: self.attachment_tree.clone(),
state: MailViewState::default(),
active_jobs: self.active_jobs.clone(),
..*self ..*self
} }
} }
@ -129,9 +155,9 @@ impl MailView {
coordinates: (usize, MailboxHash, EnvelopeHash), coordinates: (usize, MailboxHash, EnvelopeHash),
pager: Option<Pager>, pager: Option<Pager>,
subview: Option<Box<dyn Component>>, subview: Option<Box<dyn Component>>,
context: &Context, context: &mut Context,
) -> Self { ) -> Self {
MailView { let mut ret = MailView {
coordinates, coordinates,
pager: pager.unwrap_or_default(), pager: pager.unwrap_or_default(),
subview, subview,
@ -146,9 +172,61 @@ impl MailView {
force_draw_headers: false, force_draw_headers: false,
theme_default: crate::conf::value(context, "mail.view.body"), theme_default: crate::conf::value(context, "mail.view.body"),
active_jobs: Default::default(),
state: MailViewState::default(),
cmd_buf: String::with_capacity(4), cmd_buf: String::with_capacity(4),
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
};
ret.init_futures(context);
ret
}
fn init_futures(&mut self, context: &mut Context) {
debug!("init_futures");
let account = &mut context.accounts[self.coordinates.0];
if debug!(account.contains_key(self.coordinates.2)) {
{
match account
.operation(self.coordinates.2)
.and_then(|mut op| op.as_bytes())
{
Ok(fut) => {
let (chan, job_id) = account.job_executor.spawn_specialized(fut);
debug!(&job_id);
self.active_jobs.insert(job_id.clone());
account.active_jobs.insert(job_id, JobRequest::AsBytes);
self.state = MailViewState::LoadingBody { job_id, chan };
}
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("Could not get message: {}", err)),
));
}
}
}
if !account.collection.get_env(self.coordinates.2).is_seen() {
match account
.operation(self.coordinates.2)
.and_then(|mut op| op.set_flag(Flag::SEEN, true))
{
Ok(fut) => {
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
account
.active_jobs
.insert(job_id, JobRequest::SetFlags(self.coordinates.2, rcvr));
}
Err(e) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Could not set message as seen: {}",
e
)),
));
}
}
}
} }
} }
@ -164,8 +242,6 @@ impl MailView {
body, body,
Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| { Some(Box::new(move |a: &'closure Attachment, v: &mut Vec<u8>| {
if a.content_type().is_text_html() { if a.content_type().is_text_html() {
use std::io::Write;
/* FIXME: duplication with view/html.rs */ /* FIXME: duplication with view/html.rs */
if let Some(filter_invocation) = if let Some(filter_invocation) =
mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter) mailbox_settings!(context[coordinates.0][&coordinates.1].pager.html_filter)
@ -285,12 +361,187 @@ impl MailView {
} }
} }
pub fn update(&mut self, new_coordinates: (usize, MailboxHash, EnvelopeHash)) { pub fn update(
&mut self,
new_coordinates: (usize, MailboxHash, EnvelopeHash),
context: &mut Context,
) {
self.coordinates = new_coordinates; self.coordinates = new_coordinates;
self.mode = ViewMode::Normal; self.mode = ViewMode::Normal;
self.initialised = false; self.initialised = false;
self.init_futures(context);
self.set_dirty(true); self.set_dirty(true);
} }
fn open_attachment(&mut self, lidx: usize, context: &mut Context, use_mailcap: bool) {
let attachments = if let MailViewState::Loaded { ref body } = self.state {
match body {
Ok(body) => AttachmentBuilder::new(body).build().attachments(),
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: {}", err.to_string()),
ERROR,
);
return;
}
}
} else {
return;
};
if let Some(u) = attachments.get(lidx) {
if use_mailcap {
if let Ok(()) = crate::mailcap::MailcapEntry::execute(u, context) {
self.set_dirty(true);
} else {
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
"no mailcap entry found for {}",
u.content_type()
))));
}
} else {
match u.content_type() {
ContentType::MessageRfc822 => {
match EnvelopeWrapper::new(u.body().to_vec()) {
Ok(wrapper) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(EnvelopeView::new(
wrapper,
None,
None,
self.coordinates.0,
)),
)))));
}
Err(e) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("{}", e)),
));
}
}
return;
}
ContentType::Text { .. } | ContentType::PGPSignature => {
self.mode = ViewMode::Attachment(lidx);
self.initialised = false;
self.dirty = true;
}
ContentType::Multipart { .. } => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(
"Multipart attachments are not supported yet.".to_string(),
),
));
return;
}
ContentType::Other { ref name, .. } => {
let attachment_type = u.mime_type();
let binary = query_default_app(&attachment_type);
let mut name_opt = name.as_ref().and_then(|n| {
melib::email::parser::encodings::phrase(n.as_bytes(), false)
.map(|(_, v)| v)
.ok()
.and_then(|n| String::from_utf8(n).ok())
});
if name_opt.is_none() {
name_opt = name.as_ref().map(|n| n.clone());
}
if let Ok(binary) = binary {
let p = create_temp_file(
&decode(u, None),
name_opt.as_ref().map(String::as_str),
None,
true,
);
match debug!(context.plugin_manager.activate_hook(
"attachment-view",
p.path().display().to_string().into_bytes()
)) {
Ok(crate::plugins::FilterResult::Ansi(s)) => {
if let Some(buf) = crate::terminal::ansi::ansi_to_cellbuffer(&s)
{
let raw_buf = RawBuffer::new(buf, name_opt);
self.mode = ViewMode::Ansi(raw_buf);
self.initialised = false;
self.dirty = true;
return;
}
}
Ok(crate::plugins::FilterResult::UiMessage(s)) => {
context.replies.push_back(UIEvent::Notification(
None,
s,
Some(NotificationType::ERROR),
));
}
_ => {}
}
match Command::new(&binary)
.arg(p.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
Ok(child) => {
context.temp_files.push(p);
context.children.push(child);
}
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to start {}: {}",
binary.display(),
err
)),
));
}
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if name.is_some() {
format!(
"Couldn't find a default application for file {} (type {})",
name.as_ref().unwrap(),
attachment_type
)
} else {
format!(
"Couldn't find a default application for type {}",
attachment_type
)
}),
));
return;
}
}
ContentType::OctetStream { ref name } => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to open {}. application/octet-stream isn't supported yet",
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
)),
));
return;
}
}
}
} else {
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
"Attachment `{}` not found.",
lidx
))));
return;
}
}
} }
impl Component for MailView { impl Component for MailView {
@ -302,32 +553,13 @@ impl Component for MailView {
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
let y: usize = { let y: usize = {
let account = &mut context.accounts[self.coordinates.0]; let account = &context.accounts[self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */ * arrive */
self.dirty = false; self.dirty = false;
return; return;
} }
let (hash, is_seen) = {
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
(envelope.hash(), envelope.is_seen())
};
if !is_seen {
if let Err(e) = account.operation(hash).and_then(|op| {
let mut envelope: EnvelopeRefMut =
account.collection.get_env_mut(self.coordinates.2);
envelope.set_seen(op)
}) {
context
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage(format!(
"Could not set message as seen: {}",
e
))));
}
}
let account = &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 headers = crate::conf::value(context, "mail.view.headers"); let headers = crate::conf::value(context, "mail.view.headers");
@ -550,14 +782,8 @@ impl Component for MailView {
}; };
if !self.initialised { if !self.initialised {
self.initialised = true; let bytes = if let MailViewState::Loaded { ref body } = self.state {
let body = { match body {
let account = &mut context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
match account
.operation(envelope.hash())
.and_then(|op| envelope.body(op))
{
Ok(body) => body, Ok(body) => body,
Err(e) => { Err(e) => {
clear_area( clear_area(
@ -573,18 +799,23 @@ impl Component for MailView {
e.to_string(), e.to_string(),
Some(NotificationType::ERROR), Some(NotificationType::ERROR),
)); ));
log( log(format!("Failed to open envelope: {}", e.to_string()), ERROR);
format!(
"Failed to open envelope {}: {}",
envelope.message_id_display(),
e.to_string()
),
ERROR,
);
return; return;
} }
} }
} else {
clear_area(
grid,
(set_y(upper_left, y), bottom_right),
self.theme_default,
);
context
.dirty_areas
.push_back((set_y(upper_left, y), bottom_right));
return;
}; };
self.initialised = true;
let body = AttachmentBuilder::new(bytes).build();
match self.mode { match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => { ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
let attachment = &body.attachments()[aidx]; let attachment = &body.attachments()[aidx];
@ -631,32 +862,13 @@ impl Component for MailView {
ViewMode::Subview | ViewMode::ContactSelector(_) => {} ViewMode::Subview | ViewMode::ContactSelector(_) => {}
ViewMode::Source(source) => { ViewMode::Source(source) => {
let text = { let text = {
let account = &context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
let mut op = match account.operation(envelope.hash()) {
Ok(op) => op,
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
};
if source == Source::Raw { if source == Source::Raw {
op.as_bytes() String::from_utf8_lossy(bytes).into_owned()
.map(|v| String::from_utf8_lossy(v).into_owned())
.unwrap_or_else(|e| e.to_string())
} else { } else {
/* Decode each header value */ /* Decode each header value */
let mut ret = op let mut ret = melib::email::parser::headers::headers(bytes)
.as_bytes()
.and_then(|b| {
melib::email::parser::headers::headers(b)
.map(|(_, v)| v) .map(|(_, v)| v)
.map_err(|err| err.into()) .map_err(|err| err.into())
})
.and_then(|headers| { .and_then(|headers| {
Ok(headers Ok(headers
.into_iter() .into_iter()
@ -675,9 +887,7 @@ impl Component for MailView {
.join(&b"\n"[..])) .join(&b"\n"[..]))
}) })
.map(|v| String::from_utf8_lossy(&v).into_owned()) .map(|v| String::from_utf8_lossy(&v).into_owned())
.unwrap_or_else(|e| e.to_string()); .unwrap_or_else(|err: MeliError| err.to_string());
drop(envelope);
drop(account);
ret.push_str("\n\n"); ret.push_str("\n\n");
ret.extend(self.attachment_to_text(&body, context).chars()); ret.extend(self.attachment_to_text(&body, context).chars());
ret ret
@ -810,6 +1020,27 @@ impl Component for MailView {
self.pager.set_dirty(true); self.pager.set_dirty(true);
return true; return true;
} }
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
if self.active_jobs.contains(job_id) =>
{
match self.state {
MailViewState::LoadingBody {
job_id: ref id,
ref mut chan,
} if job_id == id => {
let bytes_result = chan.try_recv().unwrap().unwrap();
debug!("bytes_result");
self.state = MailViewState::Loaded { body: bytes_result };
}
MailViewState::Init => {
self.init_futures(context);
}
_ => {}
}
self.active_jobs.remove(job_id);
self.set_dirty(true);
return true;
}
_ => { _ => {
if self.pager.process_event(event, context) { if self.pager.process_event(event, context) {
return true; return true;
@ -933,56 +1164,17 @@ impl Component for MailView {
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
match self.state {
{ MailViewState::LoadingBody { .. } => {}
let account = &mut context.accounts[self.coordinates.0]; MailViewState::Loaded { .. } => {
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); self.open_attachment(lidx, context, true);
let attachments = match account
.operation(envelope.hash())
.and_then(|op| 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;
} }
}; MailViewState::Init => {
drop(envelope); self.init_futures(context);
drop(account);
if let Some(u) = attachments.get(lidx) {
if let Ok(()) = crate::mailcap::MailcapEntry::execute(u, context) {
self.set_dirty(true);
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"no mailcap entry found for {}",
u.content_type()
)),
));
} }
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Attachment `{}` not found.",
lidx
)),
));
} }
return true; return true;
} }
}
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["open_attachment"]) if shortcut!(key == shortcuts[MailView::DESCRIPTION]["open_attachment"])
&& !self.cmd_buf.is_empty() && !self.cmd_buf.is_empty()
@ -993,171 +1185,17 @@ impl Component for MailView {
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
match self.state {
{ MailViewState::LoadingBody { .. } => {}
let account = &mut context.accounts[self.coordinates.0]; MailViewState::Loaded { .. } => {
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); self.open_attachment(lidx, context, false);
let attachments = match account
.operation(envelope.hash())
.and_then(|op| 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;
} }
}; MailViewState::Init => {
if let Some(u) = attachments.get(lidx) { self.init_futures(context);
match u.content_type() {
ContentType::MessageRfc822 => {
match EnvelopeWrapper::new(u.body().to_vec()) {
Ok(wrapper) => {
context.replies.push_back(UIEvent::Action(Tab(New(Some(
Box::new(EnvelopeView::new(
wrapper,
None,
None,
self.coordinates.0,
)),
)))));
}
Err(e) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("{}", e)),
));
} }
} }
return true; return true;
} }
ContentType::Text { .. } | ContentType::PGPSignature => {
self.mode = ViewMode::Attachment(lidx);
self.initialised = false;
self.dirty = true;
}
ContentType::Multipart { .. } => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(
"Multipart attachments are not supported yet.".to_string(),
),
));
return true;
}
ContentType::Other { ref name, .. } => {
let attachment_type = u.mime_type();
let binary = query_default_app(&attachment_type);
let mut name_opt = name.as_ref().and_then(|n| {
melib::email::parser::encodings::phrase(n.as_bytes(), false)
.map(|(_, v)| v)
.ok()
.and_then(|n| String::from_utf8(n).ok())
});
if name_opt.is_none() {
name_opt = name.as_ref().map(|n| n.clone());
}
if let Ok(binary) = binary {
let p = create_temp_file(
&decode(u, None),
name_opt.as_ref().map(String::as_str),
None,
true,
);
match debug!(context.plugin_manager.activate_hook(
"attachment-view",
p.path().display().to_string().into_bytes()
)) {
Ok(crate::plugins::FilterResult::Ansi(s)) => {
if let Some(buf) =
crate::terminal::ansi::ansi_to_cellbuffer(&s)
{
let raw_buf = RawBuffer::new(buf, name_opt);
self.mode = ViewMode::Ansi(raw_buf);
self.initialised = false;
self.dirty = true;
return true;
}
}
Ok(crate::plugins::FilterResult::UiMessage(s)) => {
context.replies.push_back(UIEvent::Notification(
None,
s,
Some(NotificationType::ERROR),
));
}
_ => {}
}
match Command::new(&binary)
.arg(p.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
Ok(child) => {
context.temp_files.push(p);
context.children.push(child);
}
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Failed to start {}: {}",
binary.display(),
err
)),
));
}
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(if name.is_some() {
format!(
"Couldn't find a default application for file {} (type {})",
name.as_ref().unwrap(), attachment_type
)
} else {
format!( "Couldn't find a default application for type {}", attachment_type)
}
,
)));
return true;
}
}
ContentType::OctetStream { ref name } => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(
format!(
"Failed to open {}. application/octet-stream isn't supported yet",
name.as_ref().map(|n| n.as_str()).unwrap_or("file")
)
),
));
return true;
}
}
} else {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Attachment `{}` not found.",
lidx
)),
));
return true;
}
};
}
UIEvent::Input(ref key) UIEvent::Input(ref key)
if shortcut!(key == shortcuts[MailView::DESCRIPTION]["toggle_expand_headers"]) => if shortcut!(key == shortcuts[MailView::DESCRIPTION]["toggle_expand_headers"]) =>
{ {
@ -1175,29 +1213,25 @@ impl Component for MailView {
context context
.replies .replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); .push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
match self.state {
MailViewState::Init => {
self.init_futures(context);
}
MailViewState::LoadingBody { .. } => {}
MailViewState::Loaded { ref body } => {
let url = { let url = {
let account = &mut context.accounts[self.coordinates.0];
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
let finder = LinkFinder::new(); let finder = LinkFinder::new();
let t = match account let t = match body {
.operation(envelope.hash()) Ok(body) => {
.and_then(|op| envelope.body(op)) AttachmentBuilder::new(&body).build().text().to_string()
{ }
Ok(body) => body.text().to_string(),
Err(e) => { Err(e) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()), Some("Failed to open e-mail".to_string()),
e.to_string(), e.to_string(),
Some(NotificationType::ERROR), Some(NotificationType::ERROR),
)); ));
log( log(e.to_string(), ERROR);
format!(
"Failed to open envelope {}: {}",
envelope.message_id_display(),
e.to_string()
),
ERROR,
);
return true; return true;
} }
}; };
@ -1206,7 +1240,10 @@ impl Component for MailView {
u.as_str().to_string() u.as_str().to_string()
} else { } else {
context.replies.push_back(UIEvent::StatusEvent( context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("Link `{}` not found.", lidx)), StatusEvent::DisplayMessage(format!(
"Link `{}` not found.",
lidx
)),
)); ));
return true; return true;
} }
@ -1229,6 +1266,8 @@ impl Component for MailView {
)); ));
} }
} }
}
}
return true; return true;
} }
UIEvent::Input(ref key) UIEvent::Input(ref key)
@ -1248,11 +1287,15 @@ impl Component for MailView {
self.coordinates.2 = new_hash; self.coordinates.2 = new_hash;
} }
UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => { UIEvent::Action(View(ViewAction::SaveAttachment(a_i, ref path))) => {
use std::io::Write; let account = &context.accounts[self.coordinates.0];
let account = &mut context.accounts[self.coordinates.0]; if !account.contains_key(self.coordinates.2) {
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); /* The envelope has been renamed or removed, so wait for the appropriate event to
let mut op = match account.operation(envelope.hash()) { * arrive */
Ok(b) => b, return true;
}
let bytes = if let MailViewState::Loaded { ref body } = self.state {
match body {
Ok(body) => body,
Err(err) => { Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Failed to open e-mail".to_string()), Some("Failed to open e-mail".to_string()),
@ -1260,42 +1303,23 @@ impl Component for MailView {
Some(NotificationType::ERROR), Some(NotificationType::ERROR),
)); ));
log( log(
format!( format!("Failed to open envelope: {}", err.to_string()),
"Failed to open envelope {}: {}",
envelope.message_id_display(),
err.to_string()
),
ERROR, ERROR,
); );
return true; return true;
} }
}
} else {
return true;
}; };
if a_i == 0 { if a_i == 0 {
let mut path = std::path::Path::new(path).to_path_buf(); let mut path = std::path::Path::new(path).to_path_buf();
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
// Save entire message as eml // Save entire message as eml
if path.is_dir() { if path.is_dir() {
path.push(format!("{}.eml", envelope.message_id_display())); 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) { let mut f = match std::fs::File::create(&path) {
Err(err) => { Err(err) => {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
@ -1333,25 +1357,7 @@ impl Component for MailView {
return true; return true;
} }
let attachments = match envelope.body(op) { let attachments = AttachmentBuilder::new(bytes).build().attachments();
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) { if let Some(u) = attachments.get(a_i) {
match u.content_type() { match u.content_type() {
ContentType::MessageRfc822 ContentType::MessageRfc822
@ -1440,7 +1446,6 @@ impl Component for MailView {
} }
} }
UIEvent::Action(MailingListAction(ref e)) => { UIEvent::Action(MailingListAction(ref e)) => {
let unsafe_context = context as *mut Context;
let account = &context.accounts[self.coordinates.0]; let account = &context.accounts[self.coordinates.0];
if !account.contains_key(self.coordinates.2) { if !account.contains_key(self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to /* The envelope has been renamed or removed, so wait for the appropriate event to
@ -1448,13 +1453,14 @@ impl Component for MailView {
return true; return true;
} }
let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2); let envelope: EnvelopeRef = account.collection.get_env(self.coordinates.2);
if let Some(actions) = list_management::ListActions::detect(&envelope) { let detect = list_management::ListActions::detect(&envelope);
if let Some(ref actions) = detect {
match e { match e {
MailingListAction::ListPost if actions.post.is_some() => { MailingListAction::ListPost if actions.post.is_some() => {
/* open composer */ /* open composer */
let mut failure = true; let mut failure = true;
if let list_management::ListAction::Email(list_post_addr) = if let list_management::ListAction::Email(list_post_addr) =
actions.post.unwrap()[0] actions.post.as_ref().unwrap()[0]
{ {
if let Ok(mailto) = Mailto::try_from(list_post_addr) { if let Ok(mailto) = Mailto::try_from(list_post_addr) {
let draft: Draft = mailto.into(); let draft: Draft = mailto.into();
@ -1476,12 +1482,12 @@ impl Component for MailView {
} }
MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => { MailingListAction::ListUnsubscribe if actions.unsubscribe.is_some() => {
/* autosend or open unsubscribe option*/ /* autosend or open unsubscribe option*/
let unsubscribe = actions.unsubscribe.unwrap(); let unsubscribe = actions.unsubscribe.as_ref().unwrap();
for option in unsubscribe { for option in unsubscribe.iter() {
/* TODO: Ask for confirmation before proceding with an action */ /* TODO: Ask for confirmation before proceding with an action */
match option { match option {
list_management::ListAction::Email(email) => { list_management::ListAction::Email(email) => {
if let Ok(mailto) = Mailto::try_from(email) { if let Ok(mailto) = Mailto::try_from(*email) {
let mut draft: Draft = mailto.into(); let mut draft: Draft = mailto.into();
draft.headers_mut().insert( draft.headers_mut().insert(
"From".into(), "From".into(),
@ -1490,15 +1496,14 @@ impl Component for MailView {
self.coordinates.0, self.coordinates.0,
), ),
); );
/* Manually drop stuff because borrowck doesn't do it
* on its own */
drop(detect);
drop(envelope);
drop(account);
return super::compose::send_draft( return super::compose::send_draft(
ToggleFlag::False, ToggleFlag::False,
/* FIXME: refactor to avoid unsafe. context,
*
* actions contains byte slices from the envelope's
* headers send_draft only needs a mut ref for
* context to push back replies and save the sent
* message */
unsafe { &mut *(unsafe_context) },
self.coordinates.0, self.coordinates.0,
draft, draft,
SpecialUsageMailbox::Sent, SpecialUsageMailbox::Sent,
@ -1564,6 +1569,7 @@ impl Component for MailView {
} }
false false
} }
fn is_dirty(&self) -> bool { fn is_dirty(&self) -> bool {
self.dirty self.dirty
|| self.pager.is_dirty() || self.pager.is_dirty()
@ -1576,6 +1582,7 @@ impl Component for MailView {
false false
} }
} }
fn set_dirty(&mut self, value: bool) { fn set_dirty(&mut self, value: bool) {
self.dirty = value; self.dirty = value;
match self.mode { match self.mode {
@ -1590,6 +1597,7 @@ impl Component for MailView {
_ => {} _ => {}
} }
} }
fn get_shortcuts(&self, context: &Context) -> ShortcutMaps { fn get_shortcuts(&self, context: &Context) -> ShortcutMaps {
let mut map = if let Some(ref sbv) = self.subview { let mut map = if let Some(ref sbv) = self.subview {
sbv.get_shortcuts(context) sbv.get_shortcuts(context)

View File

@ -95,7 +95,6 @@ impl EnvelopeView {
&body, &body,
Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| { Some(Box::new(|a: &Attachment, v: &mut Vec<u8>| {
if a.content_type().is_text_html() { if a.content_type().is_text_html() {
use std::io::Write;
let settings = &context.settings; let settings = &context.settings;
if let Some(filter_invocation) = settings.pager.html_filter.as_ref() { if let Some(filter_invocation) = settings.pager.html_filter.as_ref() {
let command_obj = Command::new("sh") let command_obj = Command::new("sh")

View File

@ -961,7 +961,7 @@ impl Component for ThreadView {
self.coordinates.1, self.coordinates.1,
self.entries[self.current_pos()].msg_hash, self.entries[self.current_pos()].msg_hash,
); );
self.mailview.update(coordinates); self.mailview.update(coordinates, context);
} }
if self.entries.len() == 1 { if self.entries.len() == 1 {

View File

@ -156,6 +156,7 @@ pub enum JobRequest {
DeleteMailbox(oneshot::Receiver<Result<HashMap<MailboxHash, Mailbox>>>), DeleteMailbox(oneshot::Receiver<Result<HashMap<MailboxHash, Mailbox>>>),
//RenameMailbox, //RenameMailbox,
Search, Search,
AsBytes,
SetMailboxPermissions(MailboxHash, oneshot::Receiver<Result<()>>), SetMailboxPermissions(MailboxHash, oneshot::Receiver<Result<()>>),
SetMailboxSubscription(MailboxHash, oneshot::Receiver<Result<()>>), SetMailboxSubscription(MailboxHash, oneshot::Receiver<Result<()>>),
Watch(JoinHandle), Watch(JoinHandle),
@ -175,6 +176,7 @@ impl core::fmt::Debug for JobRequest {
JobRequest::DeleteMailbox(_) => write!(f, "{}", "JobRequest::DeleteMailbox"), JobRequest::DeleteMailbox(_) => write!(f, "{}", "JobRequest::DeleteMailbox"),
//JobRequest::RenameMailbox, //JobRequest::RenameMailbox,
JobRequest::Search => write!(f, "{}", "JobRequest::Search"), JobRequest::Search => write!(f, "{}", "JobRequest::Search"),
JobRequest::AsBytes => write!(f, "{}", "JobRequest::AsBytes"),
JobRequest::SetMailboxPermissions(_, _) => { JobRequest::SetMailboxPermissions(_, _) => {
write!(f, "{}", "JobRequest::SetMailboxPermissions") write!(f, "{}", "JobRequest::SetMailboxPermissions")
} }
@ -1599,7 +1601,7 @@ impl Account {
} }
} }
//JobRequest::RenameMailbox, //JobRequest::RenameMailbox,
JobRequest::Search => { JobRequest::Search | JobRequest::AsBytes => {
self.sender self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent( .send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::JobFinished(job_id.clone()), StatusEvent::JobFinished(job_id.clone()),

View File

@ -296,22 +296,24 @@ struct PluginOp {
} }
impl BackendOp for PluginOp { impl BackendOp for PluginOp {
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> ResultFuture<Vec<u8>> {
if let Some(ref bytes) = self.bytes { let hash = self.hash;
return Ok(bytes.as_bytes()); let channel = self.channel.clone();
} Ok(Box::pin(async move {
if let Ok(mut channel) = channel.try_lock() {
if let Ok(mut channel) = self.channel.try_lock() {
channel.write_ref(&rmpv::ValueRef::Ext(BACKEND_OP_FN, b"as_bytes"))?; channel.write_ref(&rmpv::ValueRef::Ext(BACKEND_OP_FN, b"as_bytes"))?;
debug!(channel.expect_ack())?; debug!(channel.expect_ack())?;
channel.write_ref(&rmpv::ValueRef::Integer(self.hash.into()))?; channel.write_ref(&rmpv::ValueRef::Integer(hash.into()))?;
debug!(channel.expect_ack())?; debug!(channel.expect_ack())?;
let bytes: Result<PluginResult<String>> = channel.from_read(); let bytes: Result<PluginResult<String>> = channel.from_read();
self.bytes = Some(bytes.map(Into::into).and_then(std::convert::identity)?); Ok(bytes
Ok(self.bytes.as_ref().map(String::as_bytes).unwrap()) .map(Into::into)
.and_then(std::convert::identity)?
.into_bytes())
} else { } else {
Err(MeliError::new("busy")) Err(MeliError::new("busy"))
} }
}))
} }
fn fetch_flags(&self) -> ResultFuture<Flag> { fn fetch_flags(&self) -> ResultFuture<Flag> {