meli/src/conf/accounts.rs

2330 lines
96 KiB
Rust
Raw Normal View History

/*
* 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 <http://www.gnu.org/licenses/>.
*/
2018-08-06 19:20:34 +00:00
/*!
* Account management from user configuration.
*/
use super::{AccountConf, FileMailboxConf};
use crate::jobs::{JobExecutor, JobId, JoinHandle};
use indexmap::IndexMap;
use melib::backends::*;
use melib::email::*;
2019-07-18 17:16:51 +00:00
use melib::error::{MeliError, Result};
use melib::text_processing::GlobMatch;
2020-07-23 10:39:58 +00:00
use melib::thread::{SortField, SortOrder, Threads};
2019-02-15 07:06:42 +00:00
use melib::AddressBook;
use melib::Collection;
use smallvec::SmallVec;
2020-07-24 18:48:29 +00:00
use std::collections::BTreeMap;
use std::collections::{HashMap, HashSet};
2019-02-15 07:06:42 +00:00
2019-06-18 18:13:58 +00:00
use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification};
use crate::{StatusEvent, ThreadEvent};
use crossbeam::Sender;
use futures::{
future::FutureExt,
stream::{Stream, StreamExt},
};
use std::borrow::Cow;
use std::collections::VecDeque;
use std::convert::TryFrom;
2019-02-15 07:06:42 +00:00
use std::fs;
use std::future::Future;
2019-02-15 07:06:42 +00:00
use std::io;
use std::ops::{Index, IndexMut};
use std::os::unix::fs::PermissionsExt;
2020-06-28 12:39:33 +00:00
use std::pin::Pin;
2018-08-03 10:46:08 +00:00
use std::result;
use std::sync::{Arc, RwLock};
2018-08-03 10:46:08 +00:00
2020-07-05 10:22:48 +00:00
#[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(|_| MeliError::new("canceled"));
if res.as_ref().map(|r| r.is_some()).unwrap_or(false) || res.is_err() {
break;
}
}
res
}};
}
#[derive(Debug)]
pub enum MailboxStatus {
Available,
Failed(MeliError),
/// first argument is done work, and second is total work
Parsing(usize, usize),
None,
}
impl Default for MailboxStatus {
fn default() -> Self {
2020-03-02 10:06:19 +00:00
MailboxStatus::None
}
}
impl MailboxStatus {
pub fn is_available(&self) -> bool {
if let MailboxStatus::Available = self {
true
} else {
false
}
}
pub fn is_parsing(&self) -> bool {
if let MailboxStatus::Parsing(_, _) = self {
true
} else {
false
}
}
}
2019-10-07 13:42:44 +00:00
#[derive(Debug)]
pub struct MailboxEntry {
pub status: MailboxStatus,
pub name: String,
pub ref_mailbox: Mailbox,
pub conf: FileMailboxConf,
}
2019-10-07 13:42:44 +00:00
impl MailboxEntry {
pub fn status(&self) -> String {
match self.status {
MailboxStatus::Available => format!(
"{} [{} messages]",
self.name(),
self.ref_mailbox.count().ok().unwrap_or((0, 0)).1
),
MailboxStatus::Failed(ref e) => e.to_string(),
MailboxStatus::None => "Retrieving mailbox.".to_string(),
MailboxStatus::Parsing(done, total) => {
format!("Parsing messages. [{}/{}]", done, total)
}
}
}
2019-10-07 13:42:44 +00:00
pub fn name(&self) -> &str {
if let Some(name) = self.conf.mailbox_conf.alias.as_ref() {
name
} else {
&self.ref_mailbox.name()
}
}
}
#[derive(Debug)]
pub struct Account {
name: String,
hash: AccountHash,
pub is_online: Result<()>,
pub(crate) mailbox_entries: IndexMap<MailboxHash, MailboxEntry>,
pub(crate) mailboxes_order: Vec<MailboxHash>,
tree: Vec<MailboxNode>,
sent_mailbox: Option<MailboxHash>,
pub(crate) collection: Collection,
2019-02-15 07:06:42 +00:00
pub(crate) address_book: AddressBook,
pub(crate) settings: AccountConf,
pub(crate) backend: Arc<RwLock<Box<dyn MailBackend>>>,
pub job_executor: Arc<JobExecutor>,
pub active_jobs: HashMap<JobId, JobRequest>,
2020-07-24 18:48:29 +00:00
pub active_job_instants: BTreeMap<std::time::Instant, JobId>,
sender: Sender<ThreadEvent>,
event_queue: VecDeque<(MailboxHash, RefreshEvent)>,
pub backend_capabilities: MailBackendCapabilities,
}
pub enum JobRequest {
Mailboxes {
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
},
Fetch {
mailbox_hash: MailboxHash,
handle: JoinHandle<(
2020-06-28 12:39:33 +00:00
Option<Result<Vec<Envelope>>>,
Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>,
)>,
},
2020-07-16 19:54:50 +00:00
Generic {
name: Cow<'static, str>,
logging_level: melib::LoggingLevel,
handle: JoinHandle<Result<()>>,
2020-08-25 12:39:43 +00:00
on_finish: Option<crate::types::CallbackFn>,
2020-07-16 19:54:50 +00:00
},
IsOnline {
handle: JoinHandle<Result<()>>,
},
Refresh {
mailbox_hash: MailboxHash,
handle: JoinHandle<Result<()>>,
},
SetFlags {
env_hashes: EnvelopeHashBatch,
handle: JoinHandle<Result<()>>,
},
SaveMessage {
bytes: Vec<u8>,
mailbox_hash: MailboxHash,
handle: JoinHandle<Result<()>>,
},
SendMessage,
SendMessageBackground {
handle: JoinHandle<Result<()>>,
},
CopyTo {
dest_mailbox_hash: MailboxHash,
handle: JoinHandle<Result<Vec<u8>>>,
},
DeleteMessages {
env_hashes: EnvelopeHashBatch,
handle: JoinHandle<Result<()>>,
},
CreateMailbox {
path: String,
handle: JoinHandle<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
},
DeleteMailbox {
mailbox_hash: MailboxHash,
handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
},
2020-06-28 21:18:24 +00:00
//RenameMailbox,
Search {
handle: JoinHandle<Result<()>>,
},
AsBytes {
handle: JoinHandle<Result<()>>,
},
SetMailboxPermissions {
mailbox_hash: MailboxHash,
handle: JoinHandle<Result<()>>,
},
SetMailboxSubscription {
mailbox_hash: MailboxHash,
handle: JoinHandle<Result<()>>,
},
2020-08-25 18:12:28 +00:00
Watch {
handle: JoinHandle<Result<()>>,
2020-08-25 18:12:28 +00:00
},
}
2020-07-29 11:27:43 +00:00
impl Drop for JobRequest {
fn drop(&mut self) {
match self {
JobRequest::Generic { handle, .. } |
JobRequest::IsOnline { handle, .. } |
JobRequest::Refresh { handle, .. } |
JobRequest::SetFlags { handle, .. } |
JobRequest::SaveMessage { handle, .. } |
2020-07-29 11:27:43 +00:00
//JobRequest::RenameMailbox,
JobRequest::Search { handle, .. } |
JobRequest::AsBytes { handle, .. } |
JobRequest::SetMailboxPermissions { handle, .. } |
JobRequest::SetMailboxSubscription { handle, .. } |
JobRequest::Watch { handle, .. } |
JobRequest::SendMessageBackground { handle, .. } => {
handle.cancel();
2020-07-29 11:27:43 +00:00
}
JobRequest::DeleteMessages { handle, .. } => {
handle.cancel();
2020-07-29 11:27:43 +00:00
}
JobRequest::CreateMailbox { handle, .. } => {
handle.cancel();
}
JobRequest::DeleteMailbox { handle, .. } => {
handle.cancel();
2020-07-29 11:27:43 +00:00
}
JobRequest::Fetch { handle, .. } => {
handle.cancel();
}
JobRequest::Mailboxes { handle, .. } => {
handle.cancel();
}
JobRequest::CopyTo { handle, .. } => { handle.cancel(); }
JobRequest::SendMessage => {}
2020-07-29 11:27:43 +00:00
}
}
}
2020-06-28 12:39:33 +00:00
impl core::fmt::Debug for JobRequest {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
2020-07-16 19:54:50 +00:00
JobRequest::Generic { name, .. } => write!(f, "JobRequest::Generic({})", name),
JobRequest::Mailboxes { .. } => write!(f, "JobRequest::Mailboxes"),
JobRequest::Fetch { mailbox_hash, .. } => {
write!(f, "JobRequest::Fetch({})", mailbox_hash)
}
JobRequest::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
JobRequest::Refresh { .. } => write!(f, "JobRequest::Refresh"),
JobRequest::SetFlags { .. } => write!(f, "JobRequest::SetFlags"),
JobRequest::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
JobRequest::CopyTo { .. } => write!(f, "JobRequest::CopyTo"),
JobRequest::DeleteMessages { .. } => write!(f, "JobRequest::DeleteMessages"),
JobRequest::CreateMailbox { .. } => write!(f, "JobRequest::CreateMailbox"),
JobRequest::DeleteMailbox { mailbox_hash, .. } => {
write!(f, "JobRequest::DeleteMailbox({})", mailbox_hash)
}
2020-06-28 21:18:24 +00:00
//JobRequest::RenameMailbox,
JobRequest::Search { .. } => write!(f, "JobRequest::Search"),
JobRequest::AsBytes { .. } => write!(f, "JobRequest::AsBytes"),
JobRequest::SetMailboxPermissions { .. } => {
2020-07-05 12:28:55 +00:00
write!(f, "JobRequest::SetMailboxPermissions")
2020-06-28 21:18:24 +00:00
}
JobRequest::SetMailboxSubscription { .. } => {
2020-07-05 12:28:55 +00:00
write!(f, "JobRequest::SetMailboxSubscription")
2020-06-28 21:18:24 +00:00
}
2020-08-25 18:12:28 +00:00
JobRequest::Watch { .. } => write!(f, "JobRequest::Watch"),
JobRequest::SendMessage => write!(f, "JobRequest::SendMessage"),
JobRequest::SendMessageBackground { .. } => {
write!(f, "JobRequest::SendMessageBackground")
}
2020-06-28 12:39:33 +00:00
}
}
}
2020-09-16 10:17:03 +00:00
impl core::fmt::Display for JobRequest {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
JobRequest::Generic { name, .. } => write!(f, "{}", name),
JobRequest::Mailboxes { .. } => write!(f, "Get mailbox list"),
JobRequest::Fetch { .. } => write!(f, "Mailbox fetch"),
JobRequest::IsOnline { .. } => write!(f, "Online status check"),
JobRequest::Refresh { .. } => write!(f, "Refresh mailbox"),
JobRequest::SetFlags { env_hashes, .. } => write!(
2020-09-16 10:17:03 +00:00
f,
"Set flags for {} message{}",
env_hashes.len(),
if env_hashes.len() == 1 { "" } else { "s" }
2020-09-16 10:17:03 +00:00
),
JobRequest::SaveMessage { .. } => write!(f, "Save message"),
JobRequest::CopyTo { .. } => write!(f, "Copy message."),
JobRequest::DeleteMessages { env_hashes, .. } => write!(
2020-09-16 10:17:03 +00:00
f,
"Delete {} message{}",
env_hashes.len(),
if env_hashes.len() == 1 { "" } else { "s" }
2020-09-16 10:17:03 +00:00
),
JobRequest::CreateMailbox { path, .. } => write!(f, "Create mailbox {}", path),
JobRequest::DeleteMailbox { .. } => write!(f, "Delete mailbox"),
//JobRequest::RenameMailbox,
JobRequest::Search { .. } => write!(f, "Search"),
JobRequest::AsBytes { .. } => write!(f, "Message body fetch"),
JobRequest::SetMailboxPermissions { .. } => write!(f, "Set mailbox permissions"),
JobRequest::SetMailboxSubscription { .. } => write!(f, "Set mailbox subscription"),
2020-09-16 10:17:03 +00:00
JobRequest::Watch { .. } => write!(f, "Background watch"),
JobRequest::SendMessageBackground { .. } | JobRequest::SendMessage => {
2020-09-16 10:17:03 +00:00
write!(f, "Sending message")
}
}
}
}
impl JobRequest {
2020-09-16 10:17:03 +00:00
pub fn is_watch(&self) -> bool {
2020-07-05 10:22:48 +00:00
match self {
2020-08-25 18:12:28 +00:00
JobRequest::Watch { .. } => true,
2020-07-05 10:22:48 +00:00
_ => false,
}
}
2020-09-16 10:17:03 +00:00
pub fn is_fetch(&self, mailbox_hash: MailboxHash) -> bool {
match self {
JobRequest::Fetch {
mailbox_hash: h, ..
} if *h == mailbox_hash => true,
_ => false,
}
}
2020-09-16 10:17:03 +00:00
pub fn is_online(&self) -> bool {
match self {
JobRequest::IsOnline { .. } => true,
_ => false,
}
}
}
2019-02-15 07:06:42 +00:00
impl Drop for Account {
fn drop(&mut self) {
2019-11-22 11:59:00 +00:00
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();
2019-11-22 11:59:00 +00:00
let writer = io::BufWriter::new(f);
if let Err(err) = serde_json::to_writer(writer, &self.address_book) {
eprintln!("{}", err);
return;
};
2019-02-15 07:06:42 +00:00
};
2020-02-28 07:16:50 +00:00
/*
2019-11-22 11:59:00 +00:00
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();
2019-11-22 11:59:00 +00:00
let writer = io::BufWriter::new(f);
if let Err(err) = bincode::Options::serialize_into(
bincode::config::DefaultOptions::new(),
writer,
&self.collection,
) {
2019-11-22 11:59:00 +00:00
eprintln!("{}", err);
};
2019-05-14 18:47:47 +00:00
};
2020-02-28 07:16:50 +00:00
*/
2019-11-22 11:59:00 +00:00
}
2019-02-15 07:06:42 +00:00
}
}
#[derive(Serialize, Debug, Clone, Default)]
pub struct MailboxNode {
pub hash: MailboxHash,
pub depth: usize,
pub indentation: u32,
pub has_sibling: bool,
pub children: Vec<MailboxNode>,
}
impl Account {
pub fn new(
hash: AccountHash,
name: String,
mut settings: AccountConf,
map: &Backends,
job_executor: Arc<JobExecutor>,
sender: Sender<ThreadEvent>,
2020-08-19 22:55:24 +00:00
event_consumer: BackendEventConsumer,
) -> Result<Self> {
let s = settings.clone();
let backend = map.get(settings.account().format())(
settings.account(),
Box::new(move |path: &str| {
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))
}),
2020-08-19 22:55:24 +00:00
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<AddressBook, _> = 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();
2020-07-24 18:48:29 +00:00
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 {
job_executor.spawn_specialized(online_job.then(|_| mailboxes_job))
} else {
job_executor.spawn_blocking(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);
2020-09-10 18:01:40 +00:00
sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::NewJob(job_id),
)))
.unwrap();
}
}
Ok(Account {
hash,
name,
is_online: if !backend.capabilities().is_remote {
Ok(())
} else {
Err(MeliError::new("Attempting connection."))
},
mailbox_entries: Default::default(),
mailboxes_order: Default::default(),
tree: Default::default(),
address_book,
sent_mailbox: Default::default(),
collection: backend.collection(),
settings,
sender,
job_executor,
active_jobs,
2020-07-24 18:48:29 +00:00
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<MailboxHash, Mailbox>) -> Result<()> {
self.backend_capabilities = self.backend.read().unwrap().capabilities();
let mut mailbox_entries: IndexMap<MailboxHash, MailboxEntry> =
IndexMap::with_capacity_and_hasher(ref_mailboxes.len(), Default::default());
let mut mailboxes_order: Vec<MailboxHash> = 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::<HashSet<String>>();
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 {
ref_mailbox: f.clone(),
name: f.path().to_string(),
status: MailboxStatus::None,
conf: 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 {
ref_mailbox: f.clone(),
name: f.path().to_string(),
status: MailboxStatus::None,
conf: new,
},
);
}
2019-04-10 15:58:09 +00:00
}
for missing_mailbox in &mailbox_conf_hash_set {
melib::log(
format!(
"Account `{}` mailbox `{}` configured but not present in account's mailboxes. Is it misspelled?",
&self.name, missing_mailbox,
),
melib::WARN,
);
self.sender
.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,
)),
)))
.unwrap();
}
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() - 2..);
melib::log(
format!(
"Account `{}` has the following mailboxes: [{}]",
&self.name, mailbox_comma_sep_list_string,
),
melib::WARN,
);
}
let mut tree: Vec<MailboxNode> = Vec::new();
for (h, f) in ref_mailboxes.iter() {
if !f.is_subscribed() {
/* Skip unsubscribed mailbox */
2019-08-23 18:32:32 +00:00
continue;
}
mailbox_entries.entry(*h).and_modify(|entry| {
2020-07-24 19:07:43 +00:00
if entry.conf.mailbox_conf.autoload
|| (entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Inbox
|| entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Sent)
2020-07-24 19:07:43 +00:00
{
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.job_executor.spawn_specialized(mailbox_job)
} else {
self.job_executor.spawn_blocking(mailbox_job)
};
let job_id = handle.job_id;
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::NewJob(job_id),
)))
.unwrap();
self.active_jobs.insert(
job_id,
JobRequest::Fetch {
mailbox_hash: *h,
handle,
},
);
self.active_job_instants
.insert(std::time::Instant::now(), job_id);
}
2020-03-02 10:06:19 +00:00
}
});
2020-03-12 07:47:39 +00:00
self.collection.new_mailbox(*h);
}
2019-08-23 18:32:32 +00:00
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<UIEvent> {
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();
2020-08-26 16:13:18 +00:00
match event.kind {
RefreshEventKind::Update(old_hash, envelope) => {
2020-08-26 16:13:18 +00:00
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")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
match crate::sqlite3::remove(old_hash).map(|_| {
crate::sqlite3::insert(
(*envelope).clone(),
self.backend.clone(),
self.name.clone(),
)
}) {
Err(err) => {
melib::log(
format!(
"Failed to update envelope {} in cache: {}",
envelope.message_id_display(),
err.to_string()
),
melib::ERROR,
);
}
Ok(job) => {
let handle = self.job_executor.spawn_blocking(job);
self.insert_job(
handle.job_id,
JobRequest::Generic {
name: format!(
"Update envelope {} in sqlite3 cache",
envelope.message_id_display()
)
.into(),
handle,
logging_level: melib::LoggingLevel::TRACE,
on_finish: None,
},
);
}
}
}
self.collection.update(old_hash, *envelope, mailbox_hash);
return Some(EnvelopeUpdate(old_hash));
2018-10-14 16:49:16 +00:00
}
RefreshEventKind::NewFlags(env_hash, (flags, tags)) => {
2020-08-26 16:13:18 +00:00
if !self.collection.contains_key(&env_hash) {
return None;
}
self.collection
.envelopes
.write()
.unwrap()
.entry(env_hash)
.and_modify(|entry| {
entry.labels_mut().clear();
entry
.labels_mut()
.extend(tags.into_iter().map(|h| tag_hash!(h)));
entry.set_flags(flags);
});
#[cfg(feature = "sqlite3")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
match crate::sqlite3::remove(env_hash).map(|_| {
crate::sqlite3::insert(
self.collection.envelopes.read().unwrap()[&env_hash].clone(),
self.backend.clone(),
self.name.clone(),
)
}) {
Ok(job) => {
let handle = self.job_executor.spawn_blocking(job);
self.insert_job(
handle.job_id,
JobRequest::Generic {
name: format!(
"Update envelope {} in sqlite3 cache",
self.collection.envelopes.read().unwrap()[&env_hash]
.message_id_display()
)
.into(),
handle,
logging_level: melib::LoggingLevel::TRACE,
on_finish: None,
},
);
}
Err(err) => {
melib::log(
format!(
"Failed to update envelope {} in cache: {}",
self.collection.envelopes.read().unwrap()[&env_hash]
.message_id_display(),
err.to_string()
),
melib::ERROR,
);
}
}
}
self.collection.update_flags(env_hash, mailbox_hash);
return Some(EnvelopeUpdate(env_hash));
}
2018-10-14 16:49:16 +00:00
RefreshEventKind::Rename(old_hash, new_hash) => {
2019-05-01 16:20:33 +00:00
debug!("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 self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
match crate::sqlite3::remove(old_hash).map(|_| {
crate::sqlite3::insert(
self.collection.envelopes.read().unwrap()[&new_hash].clone(),
self.backend.clone(),
self.name.clone(),
)
}) {
Err(err) => {
melib::log(
format!(
"Failed to update envelope {} in cache: {}",
&self.collection.envelopes.read().unwrap()[&new_hash]
.message_id_display(),
err.to_string()
),
melib::ERROR,
);
}
Ok(job) => {
let handle = self.job_executor.spawn_blocking(job);
self.insert_job(
handle.job_id,
JobRequest::Generic {
name: format!(
"Update envelope {} in sqlite3 cache",
self.collection.envelopes.read().unwrap()[&new_hash]
.message_id_display()
)
.into(),
handle,
logging_level: melib::LoggingLevel::TRACE,
on_finish: None,
},
);
}
}
}
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(),
)
};
2019-11-05 14:42:55 +00:00
#[cfg(feature = "sqlite3")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
let handle = self.job_executor.spawn_blocking(crate::sqlite3::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,
logging_level: melib::LoggingLevel::TRACE,
on_finish: None,
},
);
2019-11-05 14:42:55 +00:00
}
2020-03-12 07:47:39 +00:00
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(
Some(format!("new e-mail from: {}", from)),
format!(
"{}\n{} {}",
subject,
self.name,
self.mailbox_entries[&mailbox_hash].name()
),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::NewMail),
));
}
RefreshEventKind::Remove(env_hash) => {
2020-08-26 16:13:18 +00:00
if !self.collection.contains_key(&env_hash) {
return None;
}
#[cfg(feature = "sqlite3")]
if self.settings.conf.search_backend == crate::conf::SearchBackend::Sqlite3 {
if let Err(err) = crate::sqlite3::remove(env_hash) {
let envelopes = self.collection.envelopes.read().unwrap();
melib::log(
format!(
"Failed to remove envelope {} [{}] in cache: {}",
&envelopes[&env_hash].message_id_display(),
env_hash,
err.to_string()
),
melib::ERROR,
);
}
}
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) => {
debug!("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);
}
/*
context
.1
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{} watcher exited with error", &self.name)),
e.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
*/
self.watch();
return Some(Notification(
Some("Account watch failed".into()),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
));
2019-08-14 19:59:46 +00:00
}
RefreshEventKind::MailboxCreate(_new_mailbox) => {}
RefreshEventKind::MailboxDelete(_mailbox_hash) => {}
RefreshEventKind::MailboxRename {
old_mailbox_hash: _,
new_mailbox: _,
} => {}
RefreshEventKind::MailboxSubscribe(_mailbox_hash) => {}
RefreshEventKind::MailboxUnsubscribe(_mailbox_hash) => {}
2018-09-05 13:08:11 +00:00
}
}
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()?;
2020-02-28 07:16:19 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(format!("Running command {}", refresh_command)),
)))
.unwrap();
2020-03-01 15:56:58 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Fork(
crate::ForkType::Generic(child),
)))
.unwrap();
return Ok(());
}
2020-09-10 18:01:40 +00:00
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.job_executor.spawn_specialized(refresh_job)
} else {
self.job_executor.spawn_blocking(refresh_job)
};
self.insert_job(
handle.job_id,
JobRequest::Refresh {
mailbox_hash,
handle,
},
);
2020-06-28 21:18:24 +00:00
}
Ok(())
}
2020-07-05 10:22:48 +00:00
pub fn watch(&mut self) {
if self.settings.account().manual_refresh {
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.job_executor.spawn_specialized(fut)
} else {
self.job_executor.spawn_blocking(fut)
};
2020-08-25 18:12:28 +00:00
self.active_jobs
.insert(handle.job_id, JobRequest::Watch { handle });
2020-07-05 10:22:48 +00:00
}
Err(e) => {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
)))
.unwrap();
}
}
}
2017-09-28 15:06:35 +00:00
}
2018-07-11 15:58:57 +00:00
pub fn len(&self) -> usize {
self.tree.len()
2018-07-11 15:58:57 +00:00
}
2018-08-23 12:36:52 +00:00
pub fn is_empty(&self) -> bool {
self.tree.is_empty()
}
pub fn list_mailboxes(&self) -> Vec<MailboxNode> {
let mut ret = Vec::with_capacity(self.mailbox_entries.len());
fn rec(node: &MailboxNode, ret: &mut Vec<MailboxNode>) {
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) -> &Vec<MailboxHash> {
&self.mailboxes_order
2018-07-11 15:58:57 +00:00
}
pub fn name(&self) -> &str {
2018-07-11 20:12:25 +00:00
&self.name
}
2018-08-23 12:36:52 +00:00
2020-07-24 19:08:09 +00:00
pub fn hash(&self) -> AccountHash {
self.hash
}
2020-03-02 10:06:19 +00:00
pub fn load(&mut self, mailbox_hash: MailboxHash) -> result::Result<(), usize> {
if mailbox_hash == 0 {
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.job_executor.spawn_specialized(mailbox_job)
} else {
self.job_executor.spawn_blocking(mailbox_job)
};
self.insert_job(
handle.job_id,
JobRequest::Fetch {
mailbox_hash,
handle,
},
);
2020-03-02 10:06:19 +00:00
}
Err(err) => {
2020-03-02 10:06:19 +00:00
self.mailbox_entries
.entry(mailbox_hash)
.and_modify(|entry| {
entry.status = MailboxStatus::Failed(err);
2020-03-02 10:06:19 +00:00
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(mailbox_hash)))
.unwrap();
}
}
}
self.collection.new_mailbox(mailbox_hash);
Err(0)
}
_ => Err(0),
2019-08-14 20:01:57 +00:00
}
2018-08-03 10:46:08 +00:00
}
pub fn save_special(
&mut self,
bytes: &[u8],
mailbox_type: SpecialUsageMailbox,
flags: Flag,
2020-06-23 16:25:01 +00:00
) -> Result<MailboxHash> {
let mut saved_at: Option<MailboxHash> = 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(e) = self.save(bytes, *mailbox_hash, Some(flags)) {
debug!("{:?} could not save msg", e);
melib::log(
format!(
"Could not save in '{}' mailbox: {}.",
*mailbox_hash,
e.to_string()
),
melib::ERROR,
);
} else {
saved_at = Some(*mailbox_hash);
break;
}
} else {
continue;
}
}
2020-06-23 16:25:01 +00:00
if let Some(mailbox_hash) = saved_at {
Ok(mailbox_hash)
} else {
let file = crate::types::create_temp_file(bytes, None, None, false);
debug!("message saved in {}", file.path.display());
melib::log(
format!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
),
melib::INFO,
);
return Err(MeliError::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<Flag>,
) -> Result<()> {
2019-07-18 17:16:51 +00:00
if self.settings.account.read_only() {
return Err(MeliError::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.job_executor.spawn_specialized(job)
} else {
self.job_executor.spawn_blocking(job)
};
2020-09-10 18:01:40 +00:00
self.insert_job(
handle.job_id,
JobRequest::SaveMessage {
bytes: bytes.to_vec(),
mailbox_hash,
handle,
},
);
Ok(())
2019-04-10 15:57:09 +00:00
}
pub fn send(
&mut self,
message: String,
send_mail: crate::conf::composing::SendMail,
complete_in_background: bool,
) -> Result<Option<JoinHandle<Result<()>>>> {
use crate::conf::composing::SendMail;
use std::io::Write;
use std::process::{Command, Stdio};
debug!(&send_mail);
match send_mail {
SendMail::ShellCommand(ref command) => {
if command.is_empty() {
return Err(MeliError::new(
"send_mail shell command configuration value is empty",
));
}
let mut msmtp = Command::new("sh")
.args(&["-c", command])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start mailer command");
{
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
stdin
.write_all(message.as_bytes())
.expect("Failed to write to stdin");
}
let output = msmtp.wait().expect("Failed to wait on mailer");
if output.success() {
melib::log("Message sent.", melib::LoggingLevel::TRACE);
} else {
let error_message = if let Some(exit_code) = output.code() {
format!(
"Could not send e-mail using `{}`: Process exited with {}",
command, exit_code
)
} else {
format!(
"Could not send e-mail using `{}`: Process was killed by signal",
command
)
};
melib::log(&error_message, melib::LoggingLevel::ERROR);
return Err(
MeliError::new(error_message.clone()).set_summary("Message not sent.")
);
}
Ok(None)
}
#[cfg(feature = "smtp")]
SendMail::Smtp(conf) => {
let handle = self.job_executor.spawn_specialized(async move {
let mut smtp_connection =
melib::smtp::SmtpConnection::new_connection(conf).await?;
smtp_connection.mail_transaction(&message, None).await
});
if complete_in_background {
self.insert_job(handle.job_id, JobRequest::SendMessageBackground { handle });
return Ok(None);
} else {
self.insert_job(handle.job_id, JobRequest::SendMessage);
}
Ok(Some(handle))
}
}
}
pub fn send_async(
&self,
send_mail: crate::conf::composing::SendMail,
) -> impl FnOnce(Arc<String>) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> + Send {
|message: Arc<String>| -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
Box::pin(async move {
use crate::conf::composing::SendMail;
use std::io::Write;
use std::process::{Command, Stdio};
match send_mail {
SendMail::ShellCommand(ref command) => {
if command.is_empty() {
return Err(MeliError::new(
"send_mail shell command configuration value is empty",
));
}
let mut msmtp = Command::new("sh")
.args(&["-c", command])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start mailer command");
{
let stdin = msmtp.stdin.as_mut().expect("failed to open stdin");
stdin
.write_all(message.as_bytes())
.expect("Failed to write to stdin");
}
let output = msmtp.wait().expect("Failed to wait on mailer");
if output.success() {
melib::log("Message sent.", melib::LoggingLevel::TRACE);
} else {
let error_message = if let Some(exit_code) = output.code() {
format!(
"Could not send e-mail using `{}`: Process exited with {}",
command, exit_code
)
} else {
format!(
"Could not send e-mail using `{}`: Process was killed by signal",
command
)
};
melib::log(&error_message, melib::LoggingLevel::ERROR);
return Err(MeliError::new(error_message.clone())
.set_summary("Message not sent."));
}
Ok(())
}
#[cfg(feature = "smtp")]
SendMail::Smtp(conf) => {
let mut smtp_connection =
melib::smtp::SmtpConnection::new_connection(conf).await?;
smtp_connection
.mail_transaction(message.as_str(), None)
.await
}
}
})
}
}
2019-06-18 18:13:58 +00:00
pub fn contains_key(&self, h: EnvelopeHash) -> bool {
self.collection.contains_key(&h)
}
pub fn operation(&self, h: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
let operation = self.backend.read().unwrap().operation(h)?;
Ok(if self.settings.account.read_only() {
ReadOnlyOp::new(operation)
} else {
operation
})
}
pub fn mailbox_operation(
&mut self,
op: crate::command::actions::MailboxOperation,
) -> Result<()> {
use crate::command::actions::MailboxOperation;
if self.settings.account.read_only() {
return Err(MeliError::new("Account is read-only."));
}
match op {
MailboxOperation::Create(path) => {
let job = self
.backend
.write()
.unwrap()
.create_mailbox(path.to_string())?;
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(job)
} else {
self.job_executor.spawn_blocking(job)
};
self.insert_job(handle.job_id, JobRequest::CreateMailbox { path, handle });
Ok(())
}
MailboxOperation::Delete(path) => {
if self.mailbox_entries.len() == 1 {
return Err(MeliError::new("Cannot delete only mailbox."));
}
let mailbox_hash = self.mailbox_by_path(&path)?;
let job = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?;
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(job)
} else {
self.job_executor.spawn_blocking(job)
};
self.insert_job(
handle.job_id,
JobRequest::DeleteMailbox {
mailbox_hash,
handle,
},
);
Ok(())
}
MailboxOperation::Subscribe(path) => {
let mailbox_hash = self.mailbox_by_path(&path)?;
self.backend
.write()
.unwrap()
.set_mailbox_subscription(mailbox_hash, true)?;
self.mailbox_entries.entry(mailbox_hash).and_modify(|m| {
m.conf.mailbox_conf.subscribe = super::ToggleFlag::True;
let _ = m.ref_mailbox.set_is_subscribed(true);
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
None,
format!("'`{}` has been subscribed.", &path),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
)))
.expect("Could not send event on main channel");
Ok(())
}
MailboxOperation::Unsubscribe(path) => {
let mailbox_hash = self.mailbox_by_path(&path)?;
self.backend
.write()
.unwrap()
.set_mailbox_subscription(mailbox_hash, false)?;
self.mailbox_entries.entry(mailbox_hash).and_modify(|m| {
m.conf.mailbox_conf.subscribe = super::ToggleFlag::False;
let _ = m.ref_mailbox.set_is_subscribed(false);
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
None,
format!("'`{}` has been unsubscribed.", &path),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
)))
.expect("Could not send event on main channel");
Ok(())
}
MailboxOperation::Rename(_, _) => Err(MeliError::new("Not implemented.")),
MailboxOperation::SetPermissions(_) => Err(MeliError::new("Not implemented.")),
}
}
pub fn special_use_mailbox(&self, special_use: SpecialUsageMailbox) -> Option<MailboxHash> {
let ret = self
.mailbox_entries
.iter()
.find(|(_, f)| f.conf.mailbox_conf().usage == Some(special_use));
if let Some(ret) = ret.as_ref() {
Some(ret.1.ref_mailbox.hash())
} else {
None
}
}
/* Call only in Context::is_online, since only Context can launch the watcher threads if an
* account goes from offline to online. */
pub fn is_online(&mut self) -> Result<()> {
if !self.backend_capabilities.is_remote && !self.backend_capabilities.is_async {
return Ok(());
}
if self.is_online.is_err()
&& self
.is_online
.as_ref()
.unwrap_err()
.kind
.is_authentication()
{
return self.is_online.clone();
}
2020-09-16 10:17:56 +00:00
if self.is_online.is_ok() {
return Ok(());
}
if !self.active_jobs.values().any(JobRequest::is_online) {
let online_job = self.backend.read().unwrap().is_online();
if let Ok(online_job) = online_job {
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(online_job)
} else {
self.job_executor.spawn_blocking(online_job)
};
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
}
2020-02-28 07:16:19 +00:00
}
return self.is_online.clone();
}
2019-10-30 19:09:48 +00:00
pub fn search(
&self,
search_term: &str,
2020-07-05 10:22:48 +00:00
_sort: (SortField, SortOrder),
mailbox_hash: MailboxHash,
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
let query = melib::search::Query::try_from(search_term)?;
2020-07-16 20:58:46 +00:00
match self.settings.conf.search_backend {
#[cfg(feature = "sqlite3")]
crate::conf::SearchBackend::Sqlite3 => crate::sqlite3::search(&query, _sort),
crate::conf::SearchBackend::Auto | crate::conf::SearchBackend::None => {
if self.backend_capabilities.supports_search {
2020-07-16 20:58:46 +00:00
self.backend
.read()
.unwrap()
.search(query, Some(mailbox_hash))
} else {
use melib::search::QueryTrait;
2020-07-16 20:58:46 +00:00
let mut ret = SmallVec::new();
let envelopes = self.collection.envelopes.read().unwrap();
for &env_hash in self.collection.get_mailbox(mailbox_hash).iter() {
if let Some(envelope) = envelopes.get(&env_hash) {
if envelope.is_match(&query) {
ret.push(env_hash);
}
2020-07-16 20:58:46 +00:00
}
}
Ok(Box::pin(async { Ok(ret) }))
2019-10-30 19:09:48 +00:00
}
}
}
}
pub fn mailbox_by_path(&self, path: &str) -> Result<MailboxHash> {
if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
Ok(*mailbox_hash)
} else {
Err(MeliError::new("Mailbox with that path not found."))
}
}
pub fn process_event(&mut self, job_id: &JobId) -> bool {
2020-09-10 18:01:40 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::JobFinished(*job_id),
)))
.unwrap();
if let Some(mut job) = self.active_jobs.remove(job_id) {
match job {
JobRequest::Mailboxes { ref mut handle } => {
if let Ok(Some(mailboxes)) = handle.chan.try_recv() {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
self.hash,
)))
.unwrap();
if let Err(err) = mailboxes.and_then(|mailboxes| self.init(mailboxes)) {
if err.kind.is_authentication() {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: authentication error", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
self.is_online = Err(err);
return true;
}
2020-09-10 18:01:40 +00:00
let mailboxes_job = self.backend.read().unwrap().mailboxes();
if let Ok(mailboxes_job) = mailboxes_job {
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(mailboxes_job)
} else {
self.job_executor.spawn_blocking(mailboxes_job)
};
self.insert_job(handle.job_id, JobRequest::Mailboxes { handle });
2020-09-10 18:01:40 +00:00
};
}
}
}
JobRequest::Fetch {
mailbox_hash,
ref mut handle,
..
} => {
debug!("got payload in status for {}", mailbox_hash);
match handle.chan.try_recv() {
Err(_) => {
/* canceled */
return true;
}
Ok(None) => {
return true;
}
Ok(Some((None, _))) => {
debug!("finished in status for {}", mailbox_hash);
self.mailbox_entries
.entry(mailbox_hash)
.and_modify(|entry| {
entry.status = MailboxStatus::Available;
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
self.hash,
mailbox_hash,
))))
.unwrap();
return true;
}
Ok(Some((Some(Err(err)), _))) => {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not fetch mailbox", &self.name)),
err.to_string(),
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
self.mailbox_entries
.entry(mailbox_hash)
.and_modify(|entry| {
entry.status = MailboxStatus::Failed(err);
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
self.hash,
mailbox_hash,
))))
.unwrap();
return true;
}
Ok(Some((Some(Ok(payload)), rest))) => {
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(rest.into_future())
} else {
self.job_executor.spawn_blocking(rest.into_future())
};
self.insert_job(
handle.job_id,
JobRequest::Fetch {
mailbox_hash,
handle,
},
);
let envelopes = payload
.into_iter()
.map(|e| (e.hash(), e))
.collect::<HashMap<EnvelopeHash, Envelope>>();
if let Some(updated_mailboxes) =
self.collection
.merge(envelopes, mailbox_hash, self.sent_mailbox)
{
for f in updated_mailboxes {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
self.hash, f,
))))
.unwrap();
}
}
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxUpdate((
self.hash,
mailbox_hash,
))))
.unwrap();
}
}
}
JobRequest::IsOnline { ref mut handle, .. } => {
if let Ok(Some(is_online)) = handle.chan.try_recv() {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
self.hash,
)))
.unwrap();
2020-06-28 12:39:33 +00:00
if is_online.is_ok() {
if self.is_online.is_err()
&& !self
.is_online
.as_ref()
.unwrap_err()
.kind
.is_authentication()
{
self.watch();
}
self.is_online = Ok(());
2020-06-28 12:39:33 +00:00
return true;
}
self.is_online = is_online;
}
2020-09-10 18:01:40 +00:00
let online_job = self.backend.read().unwrap().is_online();
if let Ok(online_job) = online_job {
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(online_job)
} else {
self.job_executor.spawn_blocking(online_job)
};
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
2020-09-10 18:01:40 +00:00
};
}
JobRequest::Refresh { ref mut handle, .. } => {
match handle.chan.try_recv() {
Err(_) => { /* canceled */ }
Ok(None) => {}
Ok(Some(Ok(()))) => {
if self.is_online.is_err()
&& !self
.is_online
.as_ref()
.unwrap_err()
.kind
.is_authentication()
{
self.watch();
}
if !(self.is_online.is_err()
&& self
.is_online
.as_ref()
.unwrap_err()
.kind
.is_authentication())
{
self.is_online = Ok(());
self.sender
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
self.hash,
)))
.unwrap();
}
}
Ok(Some(Err(err))) => {
if !err.kind.is_authentication() {
let online_job = self.backend.read().unwrap().is_online();
if let Ok(online_job) = online_job {
let handle = if self.backend_capabilities.is_async {
self.job_executor.spawn_specialized(online_job)
} else {
self.job_executor.spawn_blocking(online_job)
};
self.insert_job(handle.job_id, JobRequest::IsOnline { handle });
};
}
self.is_online = Err(err);
self.sender
.send(ThreadEvent::UIEvent(UIEvent::AccountStatusChange(
self.hash,
)))
.unwrap();
}
2020-06-28 21:18:24 +00:00
}
}
JobRequest::SetFlags { ref mut handle, .. } => {
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not set flag", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
JobRequest::SaveMessage {
ref mut handle,
ref bytes,
..
} => {
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
melib::log(format!("Could not save message: {}", err), melib::ERROR);
let file = crate::types::create_temp_file(bytes, None, None, false);
debug!("message saved in {}", file.path.display());
melib::log(
format!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
),
melib::INFO,
);
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not save message", &self.name)),
format!(
"Message was stored in {} so that you can restore it manually.",
file.path.display()
),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
2020-09-10 18:01:40 +00:00
JobRequest::SendMessage => {}
JobRequest::SendMessageBackground { ref mut handle, .. } => {
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some("Could not send message".to_string()),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
}
}
JobRequest::CopyTo {
dest_mailbox_hash: mailbox_hash,
ref mut handle,
..
} => {
if let Ok(Some(Err(err))) = handle
.chan
2020-07-05 10:22:48 +00:00
.try_recv()
.map_err(|_: futures::channel::oneshot::Canceled| {
MeliError::new("Job was canceled")
})
.map(|r| {
r.map(|r| r.and_then(|bytes| self.save(&bytes, mailbox_hash, None)))
})
2020-07-05 10:22:48 +00:00
{
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not save message", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-07-05 10:22:48 +00:00
)))
.expect("Could not send event on main channel");
}
}
JobRequest::DeleteMessages { ref mut handle, .. } => {
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not delete message", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
JobRequest::CreateMailbox {
ref path,
ref mut handle,
..
} => {
if let Ok(Some(r)) = handle.chan.try_recv() {
match r {
Err(err) => {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!(
"{}: could not create mailbox {}",
&self.name, path
)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
}
Ok((mailbox_hash, mut mailboxes)) => {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxCreate((
self.hash,
mailbox_hash,
))))
.unwrap();
let mut new = FileMailboxConf::default();
new.mailbox_conf.subscribe = super::ToggleFlag::InternalVal(true);
new.mailbox_conf.usage = if mailboxes[&mailbox_hash].special_usage()
!= SpecialUsageMailbox::Normal
{
Some(mailboxes[&mailbox_hash].special_usage())
} else {
let tmp = SpecialUsageMailbox::detect_usage(
mailboxes[&mailbox_hash].name(),
);
if let Some(tmp) =
tmp.filter(|&v| v != SpecialUsageMailbox::Normal)
{
mailboxes.entry(mailbox_hash).and_modify(|entry| {
let _ = entry.set_special_usage(tmp);
});
}
tmp
};
/* if new mailbox has parent, we need to update its children field */
if let Some(parent_hash) = mailboxes[&mailbox_hash].parent() {
self.mailbox_entries
.entry(parent_hash)
.and_modify(|parent| {
parent.ref_mailbox =
mailboxes.remove(&parent_hash).unwrap();
});
}
2020-08-27 14:29:27 +00:00
let status = MailboxStatus::default();
self.mailbox_entries.insert(
mailbox_hash,
MailboxEntry {
name: mailboxes[&mailbox_hash].path().to_string(),
status,
conf: new,
ref_mailbox: mailboxes.remove(&mailbox_hash).unwrap(),
},
);
self.collection
.threads
.write()
.unwrap()
.insert(mailbox_hash, Threads::default());
self.collection
.mailboxes
.write()
.unwrap()
.insert(mailbox_hash, Default::default());
build_mailboxes_order(
&mut self.tree,
&self.mailbox_entries,
&mut self.mailboxes_order,
);
//Ok(format!("`{}` successfully created.", &path))
}
}
2020-06-28 21:18:24 +00:00
}
}
JobRequest::DeleteMailbox {
mailbox_hash,
ref mut handle,
..
} => {
match handle.chan.try_recv() {
Err(_) => { /* canceled */ }
Ok(None) => {}
Ok(Some(Err(err))) => {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: could not delete mailbox", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
Ok(Some(Ok(mut mailboxes))) => {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
self.hash,
mailbox_hash,
))))
.unwrap();
if let Some(pos) =
self.mailboxes_order.iter().position(|&h| h == mailbox_hash)
{
self.mailboxes_order.remove(pos);
}
if let Some(pos) = self.tree.iter().position(|n| n.hash == mailbox_hash)
{
self.tree.remove(pos);
}
if self.sent_mailbox == Some(mailbox_hash) {
self.sent_mailbox = None;
}
self.collection
.threads
.write()
.unwrap()
.remove(&mailbox_hash);
let deleted_mailbox =
self.mailbox_entries.remove(&mailbox_hash).unwrap();
/* if deleted mailbox had parent, we need to update its children field */
if let Some(parent_hash) = deleted_mailbox.ref_mailbox.parent() {
self.mailbox_entries
.entry(parent_hash)
.and_modify(|parent| {
parent.ref_mailbox =
mailboxes.remove(&parent_hash).unwrap();
});
}
self.collection
.mailboxes
.write()
.unwrap()
.remove(&mailbox_hash);
build_mailboxes_order(
&mut self.tree,
&self.mailbox_entries,
&mut self.mailboxes_order,
);
// FIXME remove from settings as well
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: mailbox deleted successfully", &self.name)),
String::new(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
}
//JobRequest::RenameMailbox,
JobRequest::Search { .. } | JobRequest::AsBytes { .. } => {}
JobRequest::SetMailboxPermissions { ref mut handle, .. } => {
match handle.chan.try_recv() {
Err(_) => { /* canceled */ }
Ok(None) => {}
Ok(Some(Err(err))) => {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!(
"{}: could not set mailbox permissions",
&self.name
)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
Ok(Some(Ok(_))) => {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!(
"{}: mailbox permissions set successfully",
&self.name
)),
String::new(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
}
JobRequest::SetMailboxSubscription { ref mut handle, .. } => {
match handle.chan.try_recv() {
Err(_) => { /* canceled */ }
Ok(None) => {}
Ok(Some(Err(err))) => {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!(
"{}: could not set mailbox subscription",
&self.name
)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
Ok(Some(Ok(_))) => {
2020-06-28 21:18:24 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!(
"{}: mailbox subscription set successfully",
&self.name
)),
String::new(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
2020-06-28 21:18:24 +00:00
)))
.expect("Could not send event on main channel");
}
}
}
JobRequest::Watch { ref mut handle } => {
2020-07-05 10:22:48 +00:00
debug!("JobRequest::Watch finished??? ");
if let Ok(Some(Err(err))) = handle.chan.try_recv() {
if err.kind.is_timeout() {
self.watch();
} else {
//TODO: relaunch watch job with ratelimit for failure
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: watch thread failed", &self.name)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
)))
.expect("Could not send event on main channel");
}
2020-08-25 18:12:28 +00:00
}
2020-07-05 10:22:48 +00:00
}
2020-07-16 19:54:50 +00:00
JobRequest::Generic {
2020-07-29 11:27:43 +00:00
ref name,
ref mut handle,
2020-08-25 12:39:43 +00:00
ref mut on_finish,
logging_level,
2020-07-16 19:54:50 +00:00
} => {
match handle.chan.try_recv() {
Ok(Some(Err(err))) => {
2020-07-16 19:54:50 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: {} failed", &self.name, name,)),
err.to_string(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Error(err.kind)),
2020-07-16 19:54:50 +00:00
)))
.expect("Could not send event on main channel");
}
Ok(Some(Ok(()))) if on_finish.is_none() => {
if logging_level <= melib::LoggingLevel::INFO {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Notification(
Some(format!("{}: {} succeeded", &self.name, name,)),
String::new(),
2020-09-13 12:23:14 +00:00
Some(crate::types::NotificationType::Info),
)))
.expect("Could not send event on main channel");
}
2020-07-16 19:54:50 +00:00
}
Err(_) => { /* canceled */ }
Ok(Some(Ok(()))) | Ok(None) => {}
2020-07-16 19:54:50 +00:00
}
2020-08-25 12:39:43 +00:00
if on_finish.is_some() {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::Callback(
on_finish.take().unwrap(),
)))
.unwrap();
}
2020-07-16 19:54:50 +00:00
}
}
true
} else {
false
}
}
2020-07-24 18:48:29 +00:00
pub fn insert_job(&mut self, job_id: JobId, job: JobRequest) {
self.active_jobs.insert(job_id, job);
self.active_job_instants
.insert(std::time::Instant::now(), job_id);
2020-09-10 18:01:40 +00:00
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::NewJob(job_id),
)))
.unwrap();
2020-07-24 18:48:29 +00:00
}
pub fn cancel_job(&mut self, job_id: JobId) -> Option<JobRequest> {
if let Some(req) = self.active_jobs.remove(&job_id) {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
StatusEvent::JobCanceled(job_id),
)))
.unwrap();
Some(req)
} else {
None
}
}
2018-08-03 10:46:08 +00:00
}
impl Index<&MailboxHash> for Account {
type Output = MailboxEntry;
fn index(&self, index: &MailboxHash) -> &MailboxEntry {
&self.mailbox_entries[index]
}
}
impl IndexMut<&MailboxHash> for Account {
fn index_mut(&mut self, index: &MailboxHash) -> &mut MailboxEntry {
self.mailbox_entries.get_mut(index).unwrap()
}
}
2018-08-03 10:46:08 +00:00
fn build_mailboxes_order(
tree: &mut Vec<MailboxNode>,
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
mailboxes_order: &mut Vec<MailboxHash>,
) {
tree.clear();
mailboxes_order.clear();
for (h, f) in mailbox_entries.iter() {
if f.ref_mailbox.parent().is_none() {
fn rec(
h: MailboxHash,
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
depth: usize,
) -> MailboxNode {
let mut node = MailboxNode {
hash: h,
children: Vec::new(),
depth,
indentation: 0,
has_sibling: false,
};
for &c in mailbox_entries[&h].ref_mailbox.children() {
if mailbox_entries.contains_key(&c) {
node.children.push(rec(c, mailbox_entries, depth + 1));
}
}
node
};
tree.push(rec(*h, &mailbox_entries, 0));
}
}
tree.sort_unstable_by(|a, b| {
if mailbox_entries[&b.hash]
.ref_mailbox
.path()
.eq_ignore_ascii_case("INBOX")
{
std::cmp::Ordering::Greater
} else if mailbox_entries[&a.hash]
.ref_mailbox
.path()
.eq_ignore_ascii_case("INBOX")
{
std::cmp::Ordering::Less
} else {
mailbox_entries[&a.hash]
.ref_mailbox
.path()
.cmp(&mailbox_entries[&b.hash].ref_mailbox.path())
}
});
let mut stack: SmallVec<[Option<&MailboxNode>; 16]> = SmallVec::new();
for n in tree.iter_mut() {
mailboxes_order.push(n.hash);
n.children.sort_unstable_by(|a, b| {
if mailbox_entries[&b.hash]
.ref_mailbox
.path()
.eq_ignore_ascii_case("INBOX")
{
std::cmp::Ordering::Greater
} else if mailbox_entries[&a.hash]
.ref_mailbox
.path()
.eq_ignore_ascii_case("INBOX")
{
std::cmp::Ordering::Less
} else {
mailbox_entries[&a.hash]
.ref_mailbox
.path()
.cmp(&mailbox_entries[&b.hash].ref_mailbox.path())
}
});
stack.extend(n.children.iter().rev().map(Some));
while let Some(Some(next)) = stack.pop() {
mailboxes_order.push(next.hash);
stack.extend(next.children.iter().rev().map(Some));
}
}
drop(stack);
for node in tree.iter_mut() {
fn rec(
node: &mut MailboxNode,
mailbox_entries: &IndexMap<MailboxHash, MailboxEntry>,
depth: usize,
mut indentation: u32,
has_sibling: bool,
) {
node.indentation = indentation;
node.has_sibling = has_sibling;
let mut iter = (0..node.children.len())
.filter(|i| {
mailbox_entries[&node.children[*i].hash]
.ref_mailbox
.is_subscribed()
})
.collect::<SmallVec<[_; 8]>>()
.into_iter()
.peekable();
if has_sibling {
indentation <<= 1;
indentation |= 1;
} else {
indentation <<= 1;
}
while let Some(i) = iter.next() {
let c = &mut node.children[i];
rec(
c,
mailbox_entries,
depth + 1,
indentation,
iter.peek() != None,
);
}
};
rec(node, &mailbox_entries, 0, 0, false);
}
}