/*
* meli - accounts module.
*
* Copyright 2017 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see .
*/
//! Account management from user configuration.
use std::{
collections::{BTreeMap, HashMap, HashSet, VecDeque},
convert::TryFrom,
fs,
future::Future,
io,
ops::{Index, IndexMut},
os::unix::fs::PermissionsExt,
pin::Pin,
result,
sync::{Arc, RwLock},
time::Duration,
};
use futures::{future::FutureExt, stream::StreamExt};
use indexmap::IndexMap;
use melib::{
backends::*,
email::*,
error::{Error, ErrorKind, Result},
log,
text::GlobMatch,
thread::Threads,
AddressBook, Collection, LogLevel, SortField, SortOrder,
};
use smallvec::SmallVec;
#[cfg(feature = "sqlite3")]
use crate::command::actions::AccountAction;
use crate::{
conf::{AccountConf, FileMailboxConf},
jobs::{JobId, JoinHandle},
types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification},
MainLoopHandler, StatusEvent, ThreadEvent,
};
mod backend_ops;
mod jobs;
mod mailbox;
pub use jobs::*;
pub use mailbox::*;
#[macro_export]
macro_rules! try_recv_timeout {
($oneshot:expr) => {{
const _3_MS: std::time::Duration = std::time::Duration::from_millis(95);
let now = std::time::Instant::now();
let mut res = Ok(None);
while now + _3_MS >= std::time::Instant::now() {
res = $oneshot.try_recv().map_err(|_| Error::new("canceled"));
if res.as_ref().map(|r| r.is_some()).unwrap_or(false) || res.is_err() {
break;
}
}
res
}};
}
#[macro_export]
macro_rules! is_variant {
($n:ident, $($var:tt)+) => {
#[inline]
pub fn $n(&self) -> bool {
matches!(self, Self::$($var)*)
}
};
}
#[derive(Clone, Debug, Default)]
pub enum IsOnline {
#[default]
Uninit,
True,
Err {
value: Error,
retries: u64,
},
}
impl IsOnline {
is_variant! { is_uninit, Uninit }
is_variant! { is_true, True }
is_variant! { is_err, Err { .. } }
fn set_err(&mut self, err: Error) {
if let Self::Err { ref mut value, .. } = self {
*value = err;
} else {
*self = Self::Err {
value: err,
retries: 1,
};
}
}
pub fn is_recoverable(err: &Error) -> bool {
!(err.kind.is_authentication()
|| err.kind.is_configuration()
|| err.kind.is_bug()
|| err.kind.is_external()
|| (err.kind.is_network() && !err.kind.is_network_down())
|| err.kind.is_not_implemented()
|| err.kind.is_not_supported()
|| err.kind.is_protocol_error()
|| err.kind.is_protocol_not_supported()
|| err.kind.is_value_error())
}
}
#[derive(Debug)]
pub struct Account {
pub name: String,
pub hash: AccountHash,
pub is_online: IsOnline,
pub mailbox_entries: IndexMap,
pub mailboxes_order: Vec,
pub tree: Vec,
pub sent_mailbox: Option,
pub collection: Collection,
pub address_book: AddressBook,
pub settings: AccountConf,
pub backend: Arc>>,
pub main_loop_handler: MainLoopHandler,
pub active_jobs: HashMap,
pub active_job_instants: BTreeMap,
pub event_queue: VecDeque<(MailboxHash, RefreshEvent)>,
pub backend_capabilities: MailBackendCapabilities,
}
impl Drop for Account {
fn drop(&mut self) {
if let Ok(data_dir) = xdg::BaseDirectories::with_profile("meli", &self.name) {
if let Ok(data) = data_dir.place_data_file("addressbook") {
/* place result in cache directory */
let f = match fs::File::create(data) {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e);
return;
}
};
let metadata = f.metadata().unwrap();
let mut permissions = metadata.permissions();
permissions.set_mode(0o600); // Read/write for owner only.
f.set_permissions(permissions).unwrap();
let writer = io::BufWriter::new(f);
if let Err(err) = serde_json::to_writer(writer, &self.address_book) {
eprintln!("{}", err);
};
};
/*
if let Ok(data) = data_dir.place_data_file("mailbox") {
/* place result in cache directory */
let f = match fs::File::create(data) {
Ok(f) => f,
Err(e) => {
eprintln!("{}", e);
return;
}
};
let metadata = f.metadata().unwrap();
let mut permissions = metadata.permissions();
permissions.set_mode(0o600); // Read/write for owner only.
f.set_permissions(permissions).unwrap();
let writer = io::BufWriter::new(f);
if let Err(err) = bincode::Options::serialize_into(
bincode::config::DefaultOptions::new(),
writer,
&self.collection,
) {
eprintln!("{}", err);
};
};
*/
}
}
}
impl Account {
pub fn new(
hash: AccountHash,
name: String,
mut settings: AccountConf,
map: &Backends,
main_loop_handler: MainLoopHandler,
event_consumer: BackendEventConsumer,
) -> Result {
let s = settings.clone();
let backend = map.get(&settings.account().format)(
settings.account(),
Box::new(move |path: &str| {
// disjoint-capture-in-closures
let _ = &s;
s.account.subscribed_mailboxes.is_empty()
|| (s.mailbox_confs.contains_key(path)
&& s.mailbox_confs[path].mailbox_conf().subscribe.is_true())
|| s.account
.subscribed_mailboxes
.iter()
.any(|m| path.matches_glob(m))
}),
event_consumer,
)?;
let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
let mut address_book = AddressBook::with_account(settings.account());
if let Ok(data) = data_dir.place_data_file("addressbook") {
if data.exists() {
let reader = io::BufReader::new(fs::File::open(data).unwrap());
let result: result::Result = serde_json::from_reader(reader);
if let Ok(data_t) = result {
for (id, c) in data_t.cards {
if !address_book.card_exists(id) && !c.external_resource() {
address_book.add_card(c);
}
}
}
}
};
if settings.conf.search_backend == crate::conf::SearchBackend::Auto {
if backend.capabilities().supports_search {
settings.conf.search_backend = crate::conf::SearchBackend::None;
} else {
#[cfg(feature = "sqlite3")]
{
settings.conf.search_backend = crate::conf::SearchBackend::Sqlite3;
}
#[cfg(not(feature = "sqlite3"))]
{
settings.conf.search_backend = crate::conf::SearchBackend::None;
}
}
}
let mut active_jobs = HashMap::default();
let mut active_job_instants = BTreeMap::default();
if let Ok(mailboxes_job) = backend.mailboxes() {
if let Ok(online_job) = backend.is_online() {
let handle = if backend.capabilities().is_async {
main_loop_handler
.job_executor
.spawn_specialized("mailboxes".into(), online_job.then(|_| mailboxes_job))
} else {
main_loop_handler
.job_executor
.spawn_blocking("mailboxes".into(), online_job.then(|_| mailboxes_job))
};
let job_id = handle.job_id;
active_jobs.insert(job_id, JobRequest::Mailboxes { handle });
active_job_instants.insert(std::time::Instant::now(), job_id);
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::NewJob(job_id),
)));
}
}
#[cfg(feature = "sqlite3")]
if settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
let db_path = match crate::sqlite3::AccountCache::db_path(&name) {
Err(err) => {
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Error with setting up an sqlite3 search database for account `{}`: {}",
name, err
)),
)));
None
}
Ok(path) => path,
};
if let Some(db_path) = db_path {
if !db_path.exists() {
log::info!(
"An sqlite3 search database for account `{}` seems to be missing, a new \
one will be created.",
name
);
main_loop_handler.send(ThreadEvent::UIEvent(UIEvent::Action(
(name.clone(), AccountAction::ReIndex).into(),
)));
}
}
}
Ok(Self {
hash,
name,
is_online: if !backend.capabilities().is_remote {
IsOnline::True
} else {
IsOnline::Uninit
},
mailbox_entries: Default::default(),
mailboxes_order: Default::default(),
tree: Default::default(),
address_book,
sent_mailbox: Default::default(),
collection: backend.collection(),
settings,
main_loop_handler,
active_jobs,
active_job_instants,
event_queue: VecDeque::with_capacity(8),
backend_capabilities: backend.capabilities(),
backend: Arc::new(RwLock::new(backend)),
})
}
fn init(&mut self, mut ref_mailboxes: HashMap) -> Result<()> {
self.backend_capabilities = self.backend.read().unwrap().capabilities();
let mut mailbox_entries: IndexMap =
IndexMap::with_capacity_and_hasher(ref_mailboxes.len(), Default::default());
let mut mailboxes_order: Vec = Vec::with_capacity(ref_mailboxes.len());
let mut sent_mailbox = None;
/* Keep track of which mailbox config values we encounter in the actual
* mailboxes returned by the backend. For each of the actual
* mailboxes, delete the key from the hash set. If any are left, they
* are misconfigurations (eg misspelling) and a warning is shown to the
* user */
let mut mailbox_conf_hash_set = self
.settings
.mailbox_confs
.keys()
.cloned()
.collect::>();
for f in ref_mailboxes.values_mut() {
if let Some(conf) = self.settings.mailbox_confs.get_mut(f.path()) {
mailbox_conf_hash_set.remove(f.path());
conf.mailbox_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
Some(f.special_usage())
} else {
let tmp = SpecialUsageMailbox::detect_usage(f.name());
if let Some(tmp) = tmp.filter(|&v| v != SpecialUsageMailbox::Normal) {
let _ = f.set_special_usage(tmp);
}
tmp
};
match conf.mailbox_conf.usage {
Some(SpecialUsageMailbox::Sent) => {
sent_mailbox = Some(f.hash());
}
None => {
if f.special_usage() == SpecialUsageMailbox::Sent {
sent_mailbox = Some(f.hash());
}
}
_ => {}
}
mailbox_entries.insert(
f.hash(),
MailboxEntry::new(
MailboxStatus::None,
f.path().to_string(),
f.clone(),
conf.clone(),
),
);
} else {
let mut new = FileMailboxConf::default();
new.mailbox_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
Some(f.special_usage())
} else {
let tmp = SpecialUsageMailbox::detect_usage(f.name());
if let Some(tmp) = tmp.filter(|&v| v != SpecialUsageMailbox::Normal) {
let _ = f.set_special_usage(tmp);
}
tmp
};
if new.mailbox_conf.usage == Some(SpecialUsageMailbox::Sent) {
sent_mailbox = Some(f.hash());
}
mailbox_entries.insert(
f.hash(),
MailboxEntry::new(MailboxStatus::None, f.path().to_string(), f.clone(), new),
);
}
}
for missing_mailbox in &mailbox_conf_hash_set {
log::warn!(
"Account `{}` mailbox `{}` configured but not present in account's mailboxes. Is \
it misspelled?",
&self.name,
missing_mailbox,
);
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Account `{}` mailbox `{}` configured but not present in account's \
mailboxes. Is it misspelled?",
&self.name, missing_mailbox,
)),
)));
}
if !mailbox_conf_hash_set.is_empty() {
let mut mailbox_comma_sep_list_string = mailbox_entries
.values()
.map(|e| e.name.as_str())
.fold(String::new(), |mut acc, el| {
acc.push('`');
acc.push_str(el);
acc.push('`');
acc.push_str(", ");
acc
});
mailbox_comma_sep_list_string
.drain(mailbox_comma_sep_list_string.len().saturating_sub(2)..);
log::warn!(
"Account `{}` has the following mailboxes: [{}]",
&self.name,
mailbox_comma_sep_list_string,
);
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Account `{}` has the following mailboxes: [{}]",
&self.name, mailbox_comma_sep_list_string,
)),
)));
}
let mut tree: Vec = Vec::new();
for (h, f) in ref_mailboxes.iter() {
if !f.is_subscribed() {
/* Skip unsubscribed mailbox */
continue;
}
mailbox_entries.entry(*h).and_modify(|entry| {
if entry.conf.mailbox_conf.autoload
|| (entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Inbox
|| entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Sent)
{
let total = entry.ref_mailbox.count().ok().unwrap_or((0, 0)).1;
entry.status = MailboxStatus::Parsing(0, total);
if let Ok(mailbox_job) = self.backend.write().unwrap().fetch(*h) {
let mailbox_job = mailbox_job.into_future();
let handle = if self.backend_capabilities.is_async {
self.main_loop_handler
.job_executor
.spawn_specialized("fetch-mailbox".into(), mailbox_job)
} else {
self.main_loop_handler
.job_executor
.spawn_blocking("fetch-mailbox".into(), mailbox_job)
};
let job_id = handle.job_id;
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::NewJob(job_id),
)));
self.active_jobs.insert(
job_id,
JobRequest::Fetch {
mailbox_hash: *h,
handle,
},
);
self.active_job_instants
.insert(std::time::Instant::now(), job_id);
}
}
});
self.collection.new_mailbox(*h);
}
build_mailboxes_order(&mut tree, &mailbox_entries, &mut mailboxes_order);
self.mailboxes_order = mailboxes_order;
self.mailbox_entries = mailbox_entries;
self.tree = tree;
self.sent_mailbox = sent_mailbox;
Ok(())
}
pub fn reload(&mut self, event: RefreshEvent, mailbox_hash: MailboxHash) -> Option {
if !self.mailbox_entries[&mailbox_hash].status.is_available()
&& !self.mailbox_entries[&mailbox_hash].status.is_parsing()
{
self.event_queue.push_back((mailbox_hash, event));
return None;
}
{
//let mailbox: &mut Mailbox =
// self.mailboxes[idx].as_mut().unwrap().as_mut().unwrap();
match event.kind {
RefreshEventKind::Update(old_hash, envelope) => {
if !self.collection.contains_key(&old_hash) {
return self.reload(
RefreshEvent {
account_hash: event.account_hash,
mailbox_hash: event.mailbox_hash,
kind: RefreshEventKind::Create(envelope),
},
mailbox_hash,
);
}
#[cfg(feature = "sqlite3")]
self.update_cached_env(*envelope.clone(), Some(old_hash));
self.collection.update(old_hash, *envelope, mailbox_hash);
return Some(EnvelopeUpdate(old_hash));
}
RefreshEventKind::NewFlags(env_hash, (flags, tags)) => {
if !self.collection.contains_key(&env_hash) {
return None;
}
self.collection
.envelopes
.write()
.unwrap()
.entry(env_hash)
.and_modify(|entry| {
entry.tags_mut().clear();
entry.tags_mut().extend(
tags.into_iter().map(|h| TagHash::from_bytes(h.as_bytes())),
);
entry.set_flags(flags);
});
#[cfg(feature = "sqlite3")]
if let Some(env) = {
let temp = self
.collection
.envelopes
.read()
.unwrap()
.get(&env_hash)
.cloned();
temp
} {
self.update_cached_env(env, None);
}
self.collection.update_flags(env_hash, mailbox_hash);
return Some(EnvelopeUpdate(env_hash));
}
RefreshEventKind::Rename(old_hash, new_hash) => {
log::trace!("rename {} to {}", old_hash, new_hash);
if !self.collection.rename(old_hash, new_hash, mailbox_hash) {
return Some(EnvelopeRename(old_hash, new_hash));
}
#[cfg(feature = "sqlite3")]
if let Some(env) = {
let temp = self
.collection
.envelopes
.read()
.unwrap()
.get(&new_hash)
.cloned();
temp
} {
self.update_cached_env(env, Some(old_hash));
}
return Some(EnvelopeRename(old_hash, new_hash));
}
RefreshEventKind::Create(envelope) => {
let env_hash = envelope.hash();
if self.collection.contains_key(&env_hash)
&& self
.collection
.get_mailbox(mailbox_hash)
.contains(&env_hash)
{
return None;
}
let (is_seen, is_draft) =
{ (envelope.is_seen(), envelope.flags().contains(Flag::DRAFT)) };
let (subject, from) = {
(
envelope.subject().into_owned(),
envelope.field_from_to_string(),
)
};
#[cfg(feature = "sqlite3")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
let handle = self.main_loop_handler.job_executor.spawn_specialized(
"sqlite3::insert".into(),
crate::sqlite3::AccountCache::insert(
(*envelope).clone(),
self.backend.clone(),
self.name.clone(),
),
);
self.insert_job(
handle.job_id,
JobRequest::Generic {
name: format!(
"Update envelope {} in sqlite3 cache",
envelope.message_id_display()
)
.into(),
handle,
log_level: LogLevel::TRACE,
on_finish: None,
},
);
}
if self.collection.insert(*envelope, mailbox_hash) {
/* is a duplicate */
return None;
}
if self.mailbox_entries[&mailbox_hash]
.conf
.mailbox_conf
.ignore
.is_true()
{
return Some(UIEvent::MailboxUpdate((self.hash, mailbox_hash)));
}
let thread_hash = self.collection.get_env(env_hash).thread();
if self
.collection
.get_threads(mailbox_hash)
.thread_nodes()
.contains_key(&thread_hash)
{
let thread = self.collection.get_threads(mailbox_hash).find_group(
self.collection.get_threads(mailbox_hash)[&thread_hash].group,
);
if self
.collection
.get_threads(mailbox_hash)
.thread_ref(thread)
.snoozed()
{
return Some(UIEvent::MailboxUpdate((self.hash, mailbox_hash)));
}
}
if is_seen || is_draft {
return Some(UIEvent::MailboxUpdate((self.hash, mailbox_hash)));
}
return Some(Notification {
title: Some(format!("new e-mail from: {}", from).into()),
body: format!(
"{}\n{} {}",
subject,
self.name,
self.mailbox_entries[&mailbox_hash].name()
)
.into(),
source: None,
kind: Some(crate::types::NotificationType::NewMail),
});
}
RefreshEventKind::Remove(env_hash) => {
if !self.collection.contains_key(&env_hash) {
return None;
}
#[cfg(feature = "sqlite3")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
let fut = crate::sqlite3::AccountCache::remove(self.name.clone(), env_hash);
let handle = self
.main_loop_handler
.job_executor
.spawn_specialized("remove envelope from cache".into(), fut);
self.insert_job(
handle.job_id,
JobRequest::Refresh {
mailbox_hash,
handle,
},
);
}
let thread_hash = self.collection.get_env(env_hash).thread();
if !self
.collection
.get_threads(mailbox_hash)
.thread_nodes()
.contains_key(&thread_hash)
{
return None;
}
let thread_hash = self
.collection
.get_threads(mailbox_hash)
.find_group(self.collection.get_threads(mailbox_hash)[&thread_hash].group);
self.collection.remove(env_hash, mailbox_hash);
return Some(EnvelopeRemove(env_hash, thread_hash));
}
RefreshEventKind::Rescan => {
self.watch();
}
RefreshEventKind::Failure(err) => {
log::trace!("RefreshEvent Failure: {}", err.to_string());
while let Some((job_id, _)) =
self.active_jobs.iter().find(|(_, j)| j.is_watch())
{
let job_id = *job_id;
let j = self.active_jobs.remove(&job_id);
drop(j);
}
self.watch();
return Some(Notification {
title: Some("Account watch failed".into()),
body: err.to_string().into(),
kind: Some(crate::types::NotificationType::Error(err.kind)),
source: Some(err),
});
}
RefreshEventKind::MailboxCreate(_new_mailbox) => {}
RefreshEventKind::MailboxDelete(_mailbox_hash) => {}
RefreshEventKind::MailboxRename {
old_mailbox_hash: _,
new_mailbox: _,
} => {}
RefreshEventKind::MailboxSubscribe(_mailbox_hash) => {}
RefreshEventKind::MailboxUnsubscribe(_mailbox_hash) => {}
}
}
None
}
pub fn refresh(&mut self, mailbox_hash: MailboxHash) -> Result<()> {
if let Some(ref refresh_command) = self.settings.conf().refresh_command {
let child = std::process::Command::new("sh")
.args(["-c", refresh_command])
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()?;
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("Running command {}", refresh_command)),
)));
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::Fork(
crate::ForkType::Generic(child),
)));
return Ok(());
}
let refresh_job = self.backend.write().unwrap().refresh(mailbox_hash);
if let Ok(refresh_job) = refresh_job {
let handle = if self.backend_capabilities.is_async {
self.main_loop_handler
.job_executor
.spawn_specialized("refresh".into(), refresh_job)
} else {
self.main_loop_handler
.job_executor
.spawn_blocking("refresh".into(), refresh_job)
};
self.insert_job(
handle.job_id,
JobRequest::Refresh {
mailbox_hash,
handle,
},
);
}
Ok(())
}
pub fn watch(&mut self) {
if self.settings.account().manual_refresh
|| matches!(self.is_online, IsOnline::Err { ref value, ..} if !IsOnline::is_recoverable(value))
{
return;
}
if !self.active_jobs.values().any(|j| j.is_watch()) {
match self.backend.read().unwrap().watch() {
Ok(fut) => {
let handle = if self.backend_capabilities.is_async {
self.main_loop_handler
.job_executor
.spawn_specialized("watch".into(), fut)
} else {
self.main_loop_handler
.job_executor
.spawn_blocking("watch".into(), fut)
};
self.active_jobs
.insert(handle.job_id, JobRequest::Watch { handle });
}
Err(e)
if e.kind == ErrorKind::NotSupported || e.kind == ErrorKind::NotImplemented => {
}
Err(e) => {
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!(
"Account `{}` watch action returned error: {}",
&self.name, e
)),
)));
}
}
}
}
pub fn len(&self) -> usize {
self.tree.len()
}
pub fn is_empty(&self) -> bool {
self.tree.is_empty()
}
pub fn list_mailboxes(&self) -> Vec {
let mut ret = Vec::with_capacity(self.mailbox_entries.len());
fn rec(node: &MailboxNode, ret: &mut Vec) {
ret.push(node.clone());
for c in node.children.iter() {
rec(c, ret);
}
}
for node in &self.tree {
rec(node, &mut ret);
}
ret
}
pub fn mailboxes_order(&self) -> &[MailboxHash] {
&self.mailboxes_order
}
pub fn name(&self) -> &str {
&self.name
}
pub fn hash(&self) -> AccountHash {
self.hash
}
pub fn load(&mut self, mailbox_hash: MailboxHash) -> result::Result<(), usize> {
if mailbox_hash.is_null() {
return Err(0);
}
match self.mailbox_entries[&mailbox_hash].status {
MailboxStatus::Available | MailboxStatus::Parsing(_, _)
if self
.collection
.mailboxes
.read()
.unwrap()
.contains_key(&mailbox_hash) =>
{
Ok(())
}
MailboxStatus::None => {
if !self.active_jobs.values().any(|j| j.is_fetch(mailbox_hash)) {
let mailbox_job = self.backend.write().unwrap().fetch(mailbox_hash);
match mailbox_job {
Ok(mailbox_job) => {
let mailbox_job = mailbox_job.into_future();
let handle = if self.backend_capabilities.is_async {
self.main_loop_handler
.job_executor
.spawn_specialized("mailbox_fetch".into(), mailbox_job)
} else {
self.main_loop_handler
.job_executor
.spawn_blocking("mailbox_fetch".into(), mailbox_job)
};
self.insert_job(
handle.job_id,
JobRequest::Fetch {
mailbox_hash,
handle,
},
);
}
Err(err) => {
self.mailbox_entries
.entry(mailbox_hash)
.and_modify(|entry| {
entry.status = MailboxStatus::Failed(err);
});
self.main_loop_handler
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(mailbox_hash)));
}
}
}
self.collection.new_mailbox(mailbox_hash);
Err(0)
}
_ => Err(0),
}
}
pub fn save_special(
&mut self,
bytes: &[u8],
mailbox_type: SpecialUsageMailbox,
flags: Flag,
) -> Result {
let mut saved_at: Option = None;
for mailbox in &[
self.special_use_mailbox(mailbox_type),
self.special_use_mailbox(SpecialUsageMailbox::Inbox),
self.special_use_mailbox(SpecialUsageMailbox::Normal),
] {
if let Some(mailbox_hash) = mailbox {
if let Err(err) = self.save(bytes, *mailbox_hash, Some(flags)) {
log::error!("Could not save in '{}' mailbox: {}.", *mailbox_hash, err);
} else {
saved_at = Some(*mailbox_hash);
break;
}
} else {
continue;
}
}
if let Some(mailbox_hash) = saved_at {
Ok(mailbox_hash)
} else {
let file = crate::types::File::create_temp_file(bytes, None, None, Some("eml"), false)?;
log::trace!("message saved in {}", file.path().display());
log::info!(
"Message was stored in {} so that you can restore it manually.",
file.path().display()
);
Err(Error::new(format!(
"Message was stored in {} so that you can restore it manually.",
file.path().display()
))
.set_summary("Could not save in any mailbox"))
}
}
pub fn save(
&mut self,
bytes: &[u8],
mailbox_hash: MailboxHash,
flags: Option,
) -> Result<()> {
if self.settings.account.read_only {
return Err(Error::new(format!(
"Account {} is read-only.",
self.name.as_str()
)));
}
let job = self
.backend
.write()
.unwrap()
.save(bytes.to_vec(), mailbox_hash, flags)?;
let handle = if self.backend_capabilities.is_async {
self.main_loop_handler
.job_executor
.spawn_specialized("save".into(), job)
} else {
self.main_loop_handler
.job_executor
.spawn_blocking("save".into(), job)
};
self.insert_job(
handle.job_id,
JobRequest::SaveMessage {
bytes: bytes.to_vec(),
mailbox_hash,
handle,
},
);
Ok(())
}
pub fn send(
&mut self,
message: String,
send_mail: crate::conf::composing::SendMail,
#[allow(unused_variables)] complete_in_background: bool,
) -> Result