You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2363 lines
98 KiB

4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
2 years ago
  1. /*
  2. * meli - accounts module.
  3. *
  4. * Copyright 2017 Manos Pitsidianakis
  5. *
  6. * This file is part of meli.
  7. *
  8. * meli is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * meli is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with meli. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. /*!
  22. * Account management from user configuration.
  23. */
  24. use super::{AccountConf, FileMailboxConf};
  25. use crate::jobs::{JobExecutor, JobId, JoinHandle};
  26. use indexmap::IndexMap;
  27. use melib::backends::*;
  28. use melib::email::*;
  29. use melib::error::{ErrorKind, MeliError, Result};
  30. use melib::text_processing::GlobMatch;
  31. use melib::thread::{SortField, SortOrder, Threads};
  32. use melib::AddressBook;
  33. use melib::Collection;
  34. use smallvec::SmallVec;
  35. use std::collections::BTreeMap;
  36. use std::collections::{HashMap, HashSet};
  37. use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification};
  38. use crate::{StatusEvent, ThreadEvent};
  39. use crossbeam::channel::Sender;
  40. use futures::{
  41. future::FutureExt,
  42. stream::{Stream, StreamExt},
  43. };
  44. use std::borrow::Cow;
  45. use std::collections::VecDeque;
  46. use std::convert::TryFrom;
  47. use std::fs;
  48. use std::future::Future;
  49. use std::io;
  50. use std::ops::{Index, IndexMut};
  51. use std::os::unix::fs::PermissionsExt;
  52. use std::pin::Pin;
  53. use std::result;
  54. use std::sync::{Arc, RwLock};
  55. #[macro_export]
  56. macro_rules! try_recv_timeout {
  57. ($oneshot:expr) => {{
  58. const _3_MS: std::time::Duration = std::time::Duration::from_millis(95);
  59. let now = std::time::Instant::now();
  60. let mut res = Ok(None);
  61. while now + _3_MS >= std::time::Instant::now() {
  62. res = $oneshot.try_recv().map_err(|_| MeliError::new("canceled"));
  63. if res.as_ref().map(|r| r.is_some()).unwrap_or(false) || res.is_err() {
  64. break;
  65. }
  66. }
  67. res
  68. }};
  69. }
  70. #[derive(Debug)]
  71. pub enum MailboxStatus {
  72. Available,
  73. Failed(MeliError),
  74. /// first argument is done work, and second is total work
  75. Parsing(usize, usize),
  76. None,
  77. }
  78. impl Default for MailboxStatus {
  79. fn default() -> Self {
  80. MailboxStatus::None
  81. }
  82. }
  83. impl MailboxStatus {
  84. pub fn is_available(&self) -> bool {
  85. matches!(self, MailboxStatus::Available)
  86. }
  87. pub fn is_parsing(&self) -> bool {
  88. matches!(self, MailboxStatus::Parsing(_, _))
  89. }
  90. }
  91. #[derive(Debug)]
  92. pub struct MailboxEntry {
  93. pub status: MailboxStatus,
  94. pub name: String,
  95. pub ref_mailbox: Mailbox,
  96. pub conf: FileMailboxConf,
  97. }
  98. impl MailboxEntry {
  99. pub fn status(&self) -> String {
  100. match self.status {
  101. MailboxStatus::Available => format!(
  102. "{} [{} messages]",
  103. self.name(),
  104. self.ref_mailbox.count().ok().unwrap_or((0, 0)).1
  105. ),
  106. MailboxStatus::Failed(ref e) => e.to_string(),
  107. MailboxStatus::None => "Retrieving mailbox.".to_string(),
  108. MailboxStatus::Parsing(done, total) => {
  109. format!("Parsing messages. [{}/{}]", done, total)
  110. }
  111. }
  112. }
  113. pub fn name(&self) -> &str {
  114. if let Some(name) = self.conf.mailbox_conf.alias.as_ref() {
  115. name
  116. } else {
  117. self.ref_mailbox.name()
  118. }
  119. }
  120. }
  121. #[derive(Debug)]
  122. pub struct Account {
  123. name: String,
  124. hash: AccountHash,
  125. pub is_online: Result<()>,
  126. pub(crate) mailbox_entries: IndexMap<MailboxHash, MailboxEntry>,
  127. pub(crate) mailboxes_order: Vec<MailboxHash>,
  128. tree: Vec<MailboxNode>,
  129. sent_mailbox: Option<MailboxHash>,
  130. pub(crate) collection: Collection,
  131. pub(crate) address_book: AddressBook,
  132. pub(crate) settings: AccountConf,
  133. pub(crate) backend: Arc<RwLock<Box<dyn MailBackend>>>,
  134. pub job_executor: Arc<JobExecutor>,
  135. pub active_jobs: HashMap<JobId, JobRequest>,
  136. pub active_job_instants: BTreeMap<std::time::Instant, JobId>,
  137. sender: Sender<ThreadEvent>,
  138. event_queue: VecDeque<(MailboxHash, RefreshEvent)>,
  139. pub backend_capabilities: MailBackendCapabilities,
  140. }
  141. pub enum JobRequest {
  142. Mailboxes {
  143. handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
  144. },
  145. Fetch {
  146. mailbox_hash: MailboxHash,
  147. #[allow(clippy::type_complexity)]
  148. handle: JoinHandle<(
  149. Option<Result<Vec<Envelope>>>,
  150. Pin<Box<dyn Stream<Item = Result<Vec<Envelope>>> + Send + 'static>>,
  151. )>,
  152. },
  153. Generic {
  154. name: Cow<'static, str>,
  155. logging_level: melib::LoggingLevel,
  156. handle: JoinHandle<Result<()>>,
  157. on_finish: Option<crate::types::CallbackFn>,
  158. },
  159. IsOnline {
  160. handle: JoinHandle<Result<()>>,
  161. },
  162. Refresh {
  163. mailbox_hash: MailboxHash,
  164. handle: JoinHandle<Result<()>>,
  165. },
  166. SetFlags {
  167. env_hashes: EnvelopeHashBatch,
  168. handle: JoinHandle<Result<()>>,
  169. },
  170. SaveMessage {
  171. bytes: Vec<u8>,
  172. mailbox_hash: MailboxHash,
  173. handle: JoinHandle<Result<()>>,
  174. },
  175. SendMessage,
  176. SendMessageBackground {
  177. handle: JoinHandle<Result<()>>,
  178. },
  179. CopyTo {
  180. dest_mailbox_hash: MailboxHash,
  181. handle: JoinHandle<Result<Vec<u8>>>,
  182. },
  183. DeleteMessages {
  184. env_hashes: EnvelopeHashBatch,
  185. handle: JoinHandle<Result<()>>,
  186. },
  187. CreateMailbox {
  188. path: String,
  189. handle: JoinHandle<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
  190. },
  191. DeleteMailbox {
  192. mailbox_hash: MailboxHash,
  193. handle: JoinHandle<Result<HashMap<MailboxHash, Mailbox>>>,
  194. },
  195. //RenameMailbox,
  196. Search {
  197. handle: JoinHandle<Result<()>>,
  198. },
  199. AsBytes {
  200. handle: JoinHandle<Result<()>>,
  201. },
  202. SetMailboxPermissions {
  203. mailbox_hash: MailboxHash,
  204. handle: JoinHandle<Result<()>>,
  205. },
  206. SetMailboxSubscription {
  207. mailbox_hash: MailboxHash,
  208. handle: JoinHandle<Result<()>>,
  209. },
  210. Watch {
  211. handle: JoinHandle<Result<()>>,
  212. },
  213. }
  214. impl Drop for JobRequest {
  215. fn drop(&mut self) {
  216. match self {
  217. JobRequest::Generic { handle, .. } |
  218. JobRequest::IsOnline { handle, .. } |
  219. JobRequest::Refresh { handle, .. } |
  220. JobRequest::SetFlags { handle, .. } |
  221. JobRequest::SaveMessage { handle, .. } |
  222. //JobRequest::RenameMailbox,
  223. JobRequest::Search { handle, .. } |
  224. JobRequest::AsBytes { handle, .. } |
  225. JobRequest::SetMailboxPermissions { handle, .. } |
  226. JobRequest::SetMailboxSubscription { handle, .. } |
  227. JobRequest::Watch { handle, .. } |
  228. JobRequest::SendMessageBackground { handle, .. } => {
  229. handle.cancel();
  230. }
  231. JobRequest::DeleteMessages { handle, .. } => {
  232. handle.cancel();
  233. }
  234. JobRequest::CreateMailbox { handle, .. } => {
  235. handle.cancel();
  236. }
  237. JobRequest::DeleteMailbox { handle, .. } => {
  238. handle.cancel();
  239. }
  240. JobRequest::Fetch { handle, .. } => {
  241. handle.cancel();
  242. }
  243. JobRequest::Mailboxes { handle, .. } => {
  244. handle.cancel();
  245. }
  246. JobRequest::CopyTo { handle, .. } => { handle.cancel(); }
  247. JobRequest::SendMessage => {}
  248. }
  249. }
  250. }
  251. impl core::fmt::Debug for JobRequest {
  252. fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
  253. match self {
  254. JobRequest::Generic { name, .. } => write!(f, "JobRequest::Generic({})", name),
  255. JobRequest::Mailboxes { .. } => write!(f, "JobRequest::Mailboxes"),
  256. JobRequest::Fetch { mailbox_hash, .. } => {
  257. write!(f, "JobRequest::Fetch({})", mailbox_hash)
  258. }
  259. JobRequest::IsOnline { .. } => write!(f, "JobRequest::IsOnline"),
  260. JobRequest::Refresh { .. } => write!(f, "JobRequest::Refresh"),
  261. JobRequest::SetFlags { .. } => write!(f, "JobRequest::SetFlags"),
  262. JobRequest::SaveMessage { .. } => write!(f, "JobRequest::SaveMessage"),
  263. JobRequest::CopyTo { .. } => write!(f, "JobRequest::CopyTo"),
  264. JobRequest::DeleteMessages { .. } => write!(f, "JobRequest::DeleteMessages"),
  265. JobRequest::CreateMailbox { .. } => write!(f, "JobRequest::CreateMailbox"),
  266. JobRequest::DeleteMailbox { mailbox_hash, .. } => {
  267. write!(f, "JobRequest::DeleteMailbox({})", mailbox_hash)
  268. }
  269. //JobRequest::RenameMailbox,
  270. JobRequest::Search { .. } => write!(f, "JobRequest::Search"),
  271. JobRequest::AsBytes { .. } => write!(f, "JobRequest::AsBytes"),
  272. JobRequest::SetMailboxPermissions { .. } => {
  273. write!(f, "JobRequest::SetMailboxPermissions")
  274. }
  275. JobRequest::SetMailboxSubscription { .. } => {
  276. write!(f, "JobRequest::SetMailboxSubscription")
  277. }
  278. JobRequest::Watch { .. } => write!(f, "JobRequest::Watch"),
  279. JobRequest::SendMessage => write!(f, "JobRequest::SendMessage"),
  280. JobRequest::SendMessageBackground { .. } => {
  281. write!(f, "JobRequest::SendMessageBackground")
  282. }
  283. }
  284. }
  285. }
  286. impl core::fmt::Display for JobRequest {
  287. fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
  288. match self {
  289. JobRequest::Generic { name, .. } => write!(f, "{}", name),
  290. JobRequest::Mailboxes { .. } => write!(f, "Get mailbox list"),
  291. JobRequest::Fetch { .. } => write!(f, "Mailbox fetch"),
  292. JobRequest::IsOnline { .. } => write!(f, "Online status check"),
  293. JobRequest::Refresh { .. } => write!(f, "Refresh mailbox"),
  294. JobRequest::SetFlags { env_hashes, .. } => write!(
  295. f,
  296. "Set flags for {} message{}",
  297. env_hashes.len(),
  298. if env_hashes.len() == 1 { "" } else { "s" }
  299. ),
  300. JobRequest::SaveMessage { .. } => write!(f, "Save message"),
  301. JobRequest::CopyTo { .. } => write!(f, "Copy message."),
  302. JobRequest::DeleteMessages { env_hashes, .. } => write!(
  303. f,
  304. "Delete {} message{}",
  305. env_hashes.len(),
  306. if env_hashes.len() == 1 { "" } else { "s" }
  307. ),
  308. JobRequest::CreateMailbox { path, .. } => write!(f, "Create mailbox {}", path),
  309. JobRequest::DeleteMailbox { .. } => write!(f, "Delete mailbox"),
  310. //JobRequest::RenameMailbox,
  311. JobRequest::Search { .. } => write!(f, "Search"),
  312. JobRequest::AsBytes { .. } => write!(f, "Message body fetch"),
  313. JobRequest::SetMailboxPermissions { .. } => write!(f, "Set mailbox permissions"),
  314. JobRequest::SetMailboxSubscription { .. } => write!(f, "Set mailbox subscription"),
  315. JobRequest::Watch { .. } => write!(f, "Background watch"),
  316. JobRequest::SendMessageBackground { .. } | JobRequest::SendMessage => {
  317. write!(f, "Sending message")
  318. }
  319. }
  320. }
  321. }
  322. impl JobRequest {
  323. pub fn is_watch(&self) -> bool {
  324. matches!(self, JobRequest::Watch { .. })
  325. }
  326. pub fn is_fetch(&self, mailbox_hash: MailboxHash) -> bool {
  327. matches!(self, JobRequest::Fetch {
  328. mailbox_hash: h, ..
  329. } if *h == mailbox_hash)
  330. }
  331. pub fn is_online(&self) -> bool {
  332. matches!(self, JobRequest::IsOnline { .. })
  333. }
  334. }
  335. impl Drop for Account {
  336. fn drop(&mut self) {
  337. if let Ok(data_dir) = xdg::BaseDirectories::with_profile("meli", &self.name) {
  338. if let Ok(data) = data_dir.place_data_file("addressbook") {
  339. /* place result in cache directory */
  340. let f = match fs::File::create(data) {
  341. Ok(f) => f,
  342. Err(e) => {
  343. eprintln!("{}", e);
  344. return;
  345. }
  346. };
  347. let metadata = f.metadata().unwrap();
  348. let mut permissions = metadata.permissions();
  349. permissions.set_mode(0o600); // Read/write for owner only.
  350. f.set_permissions(permissions).unwrap();
  351. let writer = io::BufWriter::new(f);
  352. if let Err(err) = serde_json::to_writer(writer, &self.address_book) {
  353. eprintln!("{}", err);
  354. };
  355. };
  356. /*
  357. if let Ok(data) = data_dir.place_data_file("mailbox") {
  358. /* place result in cache directory */
  359. let f = match fs::File::create(data) {
  360. Ok(f) => f,
  361. Err(e) => {
  362. eprintln!("{}", e);
  363. return;
  364. }
  365. };
  366. let metadata = f.metadata().unwrap();
  367. let mut permissions = metadata.permissions();
  368. permissions.set_mode(0o600); // Read/write for owner only.
  369. f.set_permissions(permissions).unwrap();
  370. let writer = io::BufWriter::new(f);
  371. if let Err(err) = bincode::Options::serialize_into(
  372. bincode::config::DefaultOptions::new(),
  373. writer,
  374. &self.collection,
  375. ) {
  376. eprintln!("{}", err);
  377. };
  378. };
  379. */
  380. }
  381. }
  382. }
  383. #[derive(Serialize, Debug, Clone, Default)]
  384. pub struct MailboxNode {
  385. pub hash: MailboxHash,
  386. pub depth: usize,
  387. pub indentation: u32,
  388. pub has_sibling: bool,
  389. pub children: Vec<MailboxNode>,
  390. }
  391. impl Account {
  392. pub fn new(
  393. hash: AccountHash,
  394. name: String,
  395. mut settings: AccountConf,
  396. map: &Backends,
  397. job_executor: Arc<JobExecutor>,
  398. sender: Sender<ThreadEvent>,
  399. event_consumer: BackendEventConsumer,
  400. ) -> Result<Self> {
  401. let s = settings.clone();
  402. let backend = map.get(settings.account().format())(
  403. settings.account(),
  404. Box::new(move |path: &str| {
  405. s.account.subscribed_mailboxes.is_empty()
  406. || (s.mailbox_confs.contains_key(path)
  407. && s.mailbox_confs[path].mailbox_conf().subscribe.is_true())
  408. || s.account
  409. .subscribed_mailboxes
  410. .iter()
  411. .any(|m| path.matches_glob(m))
  412. }),
  413. event_consumer,
  414. )?;
  415. let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
  416. let mut address_book = AddressBook::with_account(settings.account());
  417. if let Ok(data) = data_dir.place_data_file("addressbook") {
  418. if data.exists() {
  419. let reader = io::BufReader::new(fs::File::open(data).unwrap());
  420. let result: result::Result<AddressBook, _> = serde_json::from_reader(reader);
  421. if let Ok(data_t) = result {
  422. for (id, c) in data_t.cards {
  423. if !address_book.card_exists(id) && !c.external_resource() {
  424. address_book.add_card(c);
  425. }
  426. }
  427. }
  428. }
  429. };
  430. if settings.conf.search_backend == crate::conf::SearchBackend::Auto {
  431. if backend.capabilities().supports_search {
  432. settings.conf.search_backend = crate::conf::SearchBackend::None;
  433. } else {
  434. #[cfg(feature = "sqlite3")]
  435. {
  436. settings.conf.search_backend = crate::conf::SearchBackend::Sqlite3;
  437. }
  438. #[cfg(not(feature = "sqlite3"))]
  439. {
  440. settings.conf.search_backend = crate::conf::SearchBackend::None;
  441. }
  442. }
  443. }
  444. let mut active_jobs = HashMap::default();
  445. let mut active_job_instants = BTreeMap::default();
  446. if let Ok(mailboxes_job) = backend.mailboxes() {
  447. if let Ok(online_job) = backend.is_online() {
  448. let handle = if backend.capabilities().is_async {
  449. job_executor.spawn_specialized(online_job.then(|_| mailboxes_job))
  450. } else {
  451. job_executor.spawn_blocking(online_job.then(|_| mailboxes_job))
  452. };
  453. let job_id = handle.job_id;
  454. active_jobs.insert(job_id, JobRequest::Mailboxes { handle });
  455. active_job_instants.insert(std::time::Instant::now(), job_id);
  456. sender
  457. .send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
  458. StatusEvent::NewJob(job_id),
  459. )))
  460. .unwrap();
  461. }
  462. }
  463. Ok(Account {
  464. hash,
  465. name,
  466. is_online: if !backend.capabilities().is_remote {
  467. Ok(())
  468. } else {
  469. Err(MeliError::new("Attempting connection."))
  470. },
  471. mailbox_entries: Default::default(),
  472. mailboxes_order: Default::default(),
  473. tree: Default::default(),
  474. address_book,
  475. sent_mailbox: Default::default(),
  476. collection: backend.collection(),
  477. settings,
  478. sender,
  479. job_executor,
  480. active_jobs,
  481. active_job_instants,
  482. event_queue: VecDeque::with_capacity(8),
  483. backend_capabilities: backend.capabilities(),
  484. backend: Arc::new(RwLock::new(backend)),
  485. })
  486. }
  487. fn init(&mut self, mut ref_mailboxes: HashMap<MailboxHash, Mailbox>) -> Result<()> {
  488. self.backend_capabilities = self.backend.read().unwrap().capabilities();
  489. let mut mailbox_entries: IndexMap<MailboxHash, MailboxEntry> =
  490. IndexMap::with_capacity_and_hasher(ref_mailboxes.len(), Default::default());
  491. let mut mailboxes_order: Vec<MailboxHash> = Vec::with_capacity(ref_mailboxes.len());
  492. let mut sent_mailbox = None;
  493. /* Keep track of which mailbox config values we encounter in the actual mailboxes returned
  494. * by the backend. For each of the actual mailboxes, delete the key from the hash set. If
  495. * any are left, they are misconfigurations (eg misspelling) and a warning is shown to the
  496. * user */
  497. let mut mailbox_conf_hash_set = self
  498. .settings
  499. .mailbox_confs
  500. .keys()
  501. .cloned()
  502. .collect::<HashSet<String>>();
  503. for f in ref_mailboxes.values_mut() {
  504. if let Some(conf) = self.settings.mailbox_confs.get_mut(f.path()) {
  505. mailbox_conf_hash_set.remove(f.path());
  506. conf.mailbox_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
  507. Some(f.special_usage())
  508. } else {
  509. let tmp = SpecialUsageMailbox::detect_usage(f.name());
  510. if let Some(tmp) = tmp.filter(|&v| v != SpecialUsageMailbox::Normal) {
  511. let _ = f.set_special_usage(tmp);
  512. }
  513. tmp
  514. };
  515. match conf.mailbox_conf.usage {
  516. Some(SpecialUsageMailbox::Sent) => {
  517. sent_mailbox = Some(f.hash());
  518. }
  519. None => {
  520. if f.special_usage() == SpecialUsageMailbox::Sent {
  521. sent_mailbox = Some(f.hash());
  522. }
  523. }
  524. _ => {}
  525. }
  526. mailbox_entries.insert(
  527. f.hash(),
  528. MailboxEntry {
  529. ref_mailbox: f.clone(),
  530. name: f.path().to_string(),
  531. status: MailboxStatus::None,
  532. conf: conf.clone(),
  533. },
  534. );
  535. } else {
  536. let mut new = FileMailboxConf::default();
  537. new.mailbox_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
  538. Some(f.special_usage())
  539. } else {
  540. let tmp = SpecialUsageMailbox::detect_usage(f.name());
  541. if let Some(tmp) = tmp.filter(|&v| v != SpecialUsageMailbox::Normal) {
  542. let _ = f.set_special_usage(tmp);
  543. }
  544. tmp
  545. };
  546. if new.mailbox_conf.usage == Some(SpecialUsageMailbox::Sent) {
  547. sent_mailbox = Some(f.hash());
  548. }
  549. mailbox_entries.insert(
  550. f.hash(),
  551. MailboxEntry {
  552. ref_mailbox: f.clone(),
  553. name: f.path().to_string(),
  554. status: MailboxStatus::None,
  555. conf: new,
  556. },
  557. );
  558. }
  559. }
  560. for missing_mailbox in &mailbox_conf_hash_set {
  561. melib::log(
  562. format!(
  563. "Account `{}` mailbox `{}` configured but not present in account's mailboxes. Is it misspelled?",
  564. &self.name, missing_mailbox,
  565. ),
  566. melib::WARN,
  567. );
  568. self.sender
  569. .send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
  570. StatusEvent::DisplayMessage(format!(
  571. "Account `{}` mailbox `{}` configured but not present in account's mailboxes. Is it misspelled?",
  572. &self.name, missing_mailbox,
  573. )),
  574. )))
  575. .unwrap();
  576. }
  577. if !mailbox_conf_hash_set.is_empty() {
  578. let mut mailbox_comma_sep_list_string = mailbox_entries
  579. .values()
  580. .map(|e| e.name.as_str())
  581. .fold(String::new(), |mut acc, el| {
  582. acc.push('`');
  583. acc.push_str(el);
  584. acc.push('`');
  585. acc.push_str(", ");
  586. acc
  587. });
  588. mailbox_comma_sep_list_string.drain(mailbox_comma_sep_list_string.len() - 2..);
  589. melib::log(
  590. format!(
  591. "Account `{}` has the following mailboxes: [{}]",
  592. &self.name, mailbox_comma_sep_list_string,
  593. ),
  594. melib::WARN,
  595. );
  596. }
  597. let mut tree: Vec<MailboxNode> = Vec::new();
  598. for (h, f) in ref_mailboxes.iter() {
  599. if !f.is_subscribed() {
  600. /* Skip unsubscribed mailbox */
  601. continue;
  602. }
  603. mailbox_entries.entry(*h).and_modify(|entry| {
  604. if entry.conf.mailbox_conf.autoload
  605. || (entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Inbox
  606. || entry.ref_mailbox.special_usage() == SpecialUsageMailbox::Sent)
  607. {
  608. let total = entry.ref_mailbox.count().ok().unwrap_or((0, 0)).1;
  609. entry.status = MailboxStatus::Parsing(0, total);
  610. if let Ok(mailbox_job) = self.backend.write().unwrap().fetch(*h) {
  611. let mailbox_job = mailbox_job.into_future();
  612. let handle = if self.backend_capabilities.is_async {
  613. self.job_executor.spawn_specialized(mailbox_job)
  614. } else {
  615. self.job_executor.spawn_blocking(mailbox_job)
  616. };
  617. let job_id = handle.job_id;
  618. self.sender
  619. .send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
  620. StatusEvent::NewJob(