Browse Source

melib: remove Mailbox

Refactor Collection from melib to hold what folders have what envelopes.

Frontend accounts will now have a FolderEntry for each logical folder
and will unify many Account fields into one and eliminate a lot of
duplicate/dead code.
async
Manos Pitsidianakis 3 years ago
parent
commit
b6efb14824
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 48
      melib/src/collection.rs
  2. 8
      melib/src/lib.rs
  3. 80
      melib/src/mailbox.rs
  4. 52
      src/components/mail/listing.rs
  5. 14
      src/components/mail/listing/compact.rs
  6. 12
      src/components/mail/listing/conversations.rs
  7. 23
      src/components/mail/listing/plain.rs
  8. 22
      src/components/mail/listing/thread.rs
  9. 20
      src/components/mail/status.rs
  10. 11
      src/components/mail/view/thread.rs
  11. 452
      src/conf/accounts.rs
  12. 2
      src/state.rs

48
melib/src/collection.rs

@ -21,12 +21,13 @@
use super::*;
use crate::backends::FolderHash;
use core::ops::{Index, IndexMut};
use smallvec::SmallVec;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use fnv::FnvHashMap;
use fnv::{FnvHashMap, FnvHashSet};
pub struct EnvelopeRef<'g> {
guard: RwLockReadGuard<'g, FnvHashMap<EnvelopeHash, Envelope>>,
@ -68,6 +69,7 @@ pub struct Collection {
subject_index: Option<BTreeMap<String, EnvelopeHash>>,
pub threads: FnvHashMap<FolderHash, Threads>,
sent_folder: Option<FolderHash>,
pub mailboxes: FnvHashMap<FolderHash, FnvHashSet<EnvelopeHash>>,
}
impl Drop for Collection {
@ -101,6 +103,7 @@ impl Collection {
* /cur, or it was deleted).
*/
let threads = FnvHashMap::with_capacity_and_hasher(16, Default::default());
let mailboxes = FnvHashMap::with_capacity_and_hasher(16, Default::default());
Collection {
envelopes: Arc::new(RwLock::new(envelopes)),
@ -108,6 +111,7 @@ impl Collection {
message_ids,
subject_index,
threads,
mailboxes,
sent_folder: None,
}
}
@ -123,6 +127,9 @@ impl Collection {
pub fn remove(&mut self, envelope_hash: EnvelopeHash, folder_hash: FolderHash) {
debug!("DEBUG: Removing {}", envelope_hash);
self.envelopes.write().unwrap().remove(&envelope_hash);
self.mailboxes.entry(folder_hash).and_modify(|m| {
m.remove(&envelope_hash);
});
self.threads
.entry(folder_hash)
.or_default()
@ -144,11 +151,15 @@ impl Collection {
if !self.envelopes.write().unwrap().contains_key(&old_hash) {
return;
}
let mut env = self.envelopes.write().unwrap().remove(&old_hash).unwrap();
env.set_hash(new_hash);
let mut envelope = self.envelopes.write().unwrap().remove(&old_hash).unwrap();
self.mailboxes.entry(folder_hash).and_modify(|m| {
m.remove(&old_hash);
m.insert(new_hash);
});
envelope.set_hash(new_hash);
self.message_ids
.insert(env.message_id().raw().to_vec(), new_hash);
self.envelopes.write().unwrap().insert(new_hash, env);
.insert(envelope.message_id().raw().to_vec(), new_hash);
self.envelopes.write().unwrap().insert(new_hash, envelope);
{
if self
.threads
@ -175,7 +186,7 @@ impl Collection {
}
}
/// Merge new Mailbox to collection and update threads.
/// Merge new mailbox to collection and update threads.
/// Returns a list of already existing folders whose threads were updated
pub fn merge(
&mut self,
@ -191,16 +202,21 @@ impl Collection {
let &mut Collection {
ref mut threads,
ref mut envelopes,
ref mut mailboxes,
ref sent_folder,
..
} = self;
if !threads.contains_key(&folder_hash) {
threads.insert(folder_hash, Threads::new(new_envelopes.len()));
mailboxes.insert(folder_hash, new_envelopes.keys().cloned().collect());
for (h, e) in new_envelopes {
envelopes.write().unwrap().insert(h, e);
}
} else {
mailboxes.entry(folder_hash).and_modify(|m| {
m.extend(new_envelopes.keys().cloned());
});
threads.entry(folder_hash).and_modify(|t| {
let mut ordered_hash_set =
new_envelopes.keys().cloned().collect::<Vec<EnvelopeHash>>();
@ -291,6 +307,10 @@ impl Collection {
let old_env = self.envelopes.write().unwrap().remove(&old_hash).unwrap();
envelope.set_thread(old_env.thread());
let new_hash = envelope.hash();
self.mailboxes.entry(folder_hash).and_modify(|m| {
m.remove(&old_hash);
m.insert(new_hash);
});
self.message_ids
.insert(envelope.message_id().raw().to_vec(), new_hash);
self.envelopes.write().unwrap().insert(new_hash, envelope);
@ -328,6 +348,9 @@ impl Collection {
pub fn insert(&mut self, envelope: Envelope, folder_hash: FolderHash) {
let hash = envelope.hash();
self.mailboxes.entry(folder_hash).and_modify(|m| {
m.insert(hash);
});
self.message_ids
.insert(envelope.message_id().raw().to_vec(), hash);
self.envelopes.write().unwrap().insert(hash, envelope);
@ -365,3 +388,16 @@ impl Collection {
self.envelopes.read().unwrap().contains_key(env_hash)
}
}
impl Index<&FolderHash> for Collection {
type Output = FnvHashSet<EnvelopeHash>;
fn index(&self, index: &FolderHash) -> &FnvHashSet<EnvelopeHash> {
&self.mailboxes[index]
}
}
impl IndexMut<&FolderHash> for Collection {
fn index_mut(&mut self, index: &FolderHash) -> &mut FnvHashSet<EnvelopeHash> {
self.mailboxes.get_mut(index).unwrap()
}
}

8
melib/src/lib.rs

@ -119,7 +119,6 @@ mod collection;
pub mod conf;
pub mod email;
pub mod error;
pub mod mailbox;
pub mod thread;
pub use crate::email::*;
pub use crate::thread::*;
@ -138,11 +137,10 @@ extern crate bitflags;
extern crate fnv;
extern crate uuid;
pub use crate::conf::*;
pub use crate::mailbox::*;
pub use crate::backends::{Backends, RefreshEvent, RefreshEventConsumer, SpecialUsageMailbox};
pub use crate::email::{Envelope, Flag};
pub use crate::collection::*;
pub use crate::conf::*;
pub use crate::email::{Envelope, EnvelopeHash, Flag};
pub use crate::error::{MeliError, Result};
pub use crate::addressbook::*;

80
melib/src/mailbox.rs

@ -1,80 +0,0 @@
/*
* meli - mailbox 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/>.
*/
/*!
* Mail related code.
*
* This module handles reading emails from various backends, handling account data etc
*/
use crate::backends::Folder;
pub use crate::collection::*;
pub use crate::email::*;
use fnv::{FnvHashMap, FnvHashSet};
/// `Mailbox` represents a folder of mail.
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Mailbox {
#[serde(skip_serializing, skip_deserializing)]
pub folder: Folder,
name: String,
pub envelopes: FnvHashSet<EnvelopeHash>,
has_sent: bool,
}
impl Mailbox {
pub fn new(folder: Folder, envelopes: &FnvHashMap<EnvelopeHash, Envelope>) -> Mailbox {
let name = folder.name().into();
let envelopes = envelopes.keys().cloned().collect();
Mailbox {
folder,
name,
envelopes,
..Default::default()
}
}
pub fn merge(&mut self, envelopes: &FnvHashMap<EnvelopeHash, Envelope>) {
self.envelopes.extend(envelopes.keys().cloned());
}
pub fn name(&self) -> &str {
&self.name
}
pub fn is_empty(&self) -> bool {
self.envelopes.is_empty()
}
pub fn len(&self) -> usize {
self.envelopes.len()
}
pub fn insert(&mut self, h: EnvelopeHash) {
self.envelopes.insert(h);
}
pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
self.envelopes.remove(&old_hash);
self.envelopes.insert(new_hash);
}
pub fn remove(&mut self, h: EnvelopeHash) {
self.envelopes.remove(&h);
}
}

52
src/components/mail/listing.rs

@ -137,7 +137,7 @@ pub trait MailListingTrait: ListingTrait {
) {
let account = &mut context.accounts[self.coordinates().0];
let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
let folder_hash = account[self.coordinates().1].unwrap().folder.hash();
let folder_hash = self.coordinates().1;
for (_, h) in account.collection.threads[&folder_hash].thread_group_iter(thread_hash) {
envs_to_set.push(
account.collection.threads[&folder_hash].thread_nodes()[&h]
@ -449,7 +449,8 @@ impl Component for Listing {
.list_folders()
.into_iter()
.filter(|folder_node| {
context.accounts[*account_index].ref_folders()[&folder_node.hash]
context.accounts[*account_index][&folder_node.hash]
.ref_folder
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
@ -464,7 +465,8 @@ impl Component for Listing {
.list_folders()
.into_iter()
.filter(|folder_node| {
context.accounts[*account_index].ref_folders()[&folder_node.hash]
context.accounts[*account_index][&folder_node.hash]
.ref_folder
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
@ -550,10 +552,10 @@ impl Component for Listing {
{
/* Account might have no folders yet if it's offline */
/* Check if per-folder configuration overrides general configuration */
if let Some(index_style) =
context.accounts.get(self.cursor_pos.0).and_then(|account| {
account.folder_confs(*folder_hash).conf_override.index_style
})
if let Some(index_style) = context
.accounts
.get(self.cursor_pos.0)
.and_then(|account| account[folder_hash].conf.conf_override.index_style)
{
self.component.set_style(index_style);
} else if let Some(index_style) = context
@ -870,15 +872,20 @@ impl Component for Listing {
};
let account = &context.accounts[self.cursor_pos.0];
if let Ok(m) = account[folder_hash].as_result() {
format!(
use crate::conf::accounts::MailboxStatus;
match account[&folder_hash].status {
MailboxStatus::Available | MailboxStatus::Parsing(_, _) => format!(
"Mailbox: {}, Messages: {}, New: {}",
m.folder.name(),
m.envelopes.len(),
m.folder.count().ok().map(|(v, _)| v).unwrap_or(0),
)
} else {
account[folder_hash].to_string()
account[&folder_hash].ref_folder.name(),
account.collection[&folder_hash].len(),
account[&folder_hash]
.ref_folder
.count()
.ok()
.map(|(v, _)| v)
.unwrap_or(0),
),
MailboxStatus::Failed(_) | MailboxStatus::None => account[&folder_hash].status(),
}
}
}
@ -905,7 +912,7 @@ impl Listing {
let entries: SmallVec<[(usize, FolderHash); 16]> = a
.list_folders()
.into_iter()
.filter(|folder_node| a.ref_folders()[&folder_node.hash].is_subscribed())
.filter(|folder_node| a[&folder_node.hash].ref_folder.is_subscribed())
.map(|f| (f.depth, f.hash))
.collect::<_>();
@ -974,8 +981,11 @@ impl Listing {
debug!("BUG: invalid area in print_account");
}
// Each entry and its index in the account
let folders: FnvHashMap<FolderHash, Folder> =
context.accounts[a.index].ref_folders().clone();
let folders: FnvHashMap<FolderHash, Folder> = context.accounts[a.index]
.folder_entries
.iter()
.map(|(&hash, entry)| (hash, entry.ref_folder.clone()))
.collect();
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
@ -1160,7 +1170,9 @@ impl Listing {
.list_folders()
.into_iter()
.filter(|folder_node| {
context.accounts[self.cursor_pos.0].ref_folders()[&folder_node.hash].is_subscribed()
context.accounts[self.cursor_pos.0][&folder_node.hash]
.ref_folder
.is_subscribed()
})
.map(|f| (f.depth, f.hash))
.collect::<_>();
@ -1175,7 +1187,7 @@ impl Listing {
if let Some(index_style) = context
.accounts
.get(self.cursor_pos.0)
.and_then(|account| account.folder_confs(*folder_hash).conf_override.index_style)
.and_then(|account| account[folder_hash].conf.conf_override.index_style)
{
self.component.set_style(index_style);
} else if let Some(index_style) = context

14
src/components/mail/listing/compact.rs

@ -142,7 +142,7 @@ impl MailListingTrait for CompactListing {
ret
};
let message: String =
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
context.accounts[self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.length = 0;
@ -650,7 +650,7 @@ impl CompactListing {
hash: ThreadHash,
) -> EntryStrings {
let thread = threads.thread_ref(hash);
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
let folder = &context.accounts[self.cursor_pos.0][&self.cursor_pos.1].conf;
let mut tags = String::new();
let mut colors: SmallVec<[_; 8]> = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
@ -716,9 +716,8 @@ impl CompactListing {
fn redraw_list(&mut self, context: &Context, items: Box<dyn Iterator<Item = ThreadHash>>) {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.cursor_pos.1];
self.order.clear();
self.selection.clear();
self.length = 0;
@ -752,7 +751,7 @@ impl CompactListing {
debug!("key = {}", root_env_hash);
debug!(
"name = {} {}",
mailbox.name(),
account[&self.cursor_pos.1].name(),
context.accounts[self.cursor_pos.0].name()
);
debug!("{:#?}", context.accounts);
@ -837,7 +836,7 @@ impl CompactListing {
//debug!("key = {}", root_env_hash);
//debug!(
// "name = {} {}",
// mailbox.name(),
// account[&self.cursor_pos.1].name(),
// context.accounts[self.cursor_pos.0].name()
//);
//debug!("{:#?}", context.accounts);
@ -968,8 +967,7 @@ impl CompactListing {
}
}
if self.length == 0 && self.filter_term.is_empty() {
let mailbox = &account[self.cursor_pos.1];
let message = mailbox.to_string();
let message = format!("{} is empty", account[&self.cursor_pos.1].name());
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
write_string_to_grid(

12
src/components/mail/listing/conversations.rs

@ -128,7 +128,7 @@ impl MailListingTrait for ConversationsListing {
ret
};
let message: String =
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
context.accounts[self.cursor_pos.0][&self.cursor_pos.1].status();
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.length = 0;
@ -592,7 +592,7 @@ impl ConversationsListing {
hash: ThreadHash,
) -> EntryStrings {
let thread = threads.thread_ref(hash);
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
let folder = &context.accounts[self.cursor_pos.0][&self.cursor_pos.1].conf;
let mut tags = String::new();
let mut colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
@ -658,9 +658,8 @@ impl ConversationsListing {
fn redraw_list(&mut self, context: &Context, items: Box<dyn Iterator<Item = ThreadHash>>) {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.cursor_pos.1];
self.order.clear();
self.selection.clear();
self.length = 0;
@ -684,7 +683,7 @@ impl ConversationsListing {
debug!("key = {}", root_env_hash);
debug!(
"name = {} {}",
mailbox.name(),
account[&self.cursor_pos.1].name(),
context.accounts[self.cursor_pos.0].name()
);
debug!("{:#?}", context.accounts);
@ -852,8 +851,7 @@ impl ConversationsListing {
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
let mailbox = &account[self.cursor_pos.1];
let message = mailbox.to_string();
let message = format!("{} is empty", account[&self.cursor_pos.1].name());
self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,

23
src/components/mail/listing/plain.rs

@ -142,7 +142,7 @@ impl MailListingTrait for PlainListing {
ret
};
let message: String =
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
context.accounts[self.cursor_pos.0][&self.cursor_pos.1].status();
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.length = 0;
@ -158,9 +158,7 @@ impl MailListingTrait for PlainListing {
return;
}
}
self.local_collection = context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.unwrap()
.envelopes
self.local_collection = context.accounts[self.cursor_pos.0].collection[&self.cursor_pos.1]
.iter()
.cloned()
.collect();
@ -169,9 +167,8 @@ impl MailListingTrait for PlainListing {
.envelopes
.read()
.unwrap();
self.thread_node_hashes = context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.unwrap()
.envelopes
self.thread_node_hashes = context.accounts[self.cursor_pos.0].collection
[&self.cursor_pos.1]
.iter()
.map(|h| (*h, env_lck[h].thread()))
.collect();
@ -616,7 +613,7 @@ impl PlainListing {
}
}
fn make_entry_string(&self, e: EnvelopeRef, context: &Context) -> EntryStrings {
let folder = &context.accounts[self.cursor_pos.0].folder_confs[&self.cursor_pos.1];
let folder = &context.accounts[self.cursor_pos.0][&self.cursor_pos.1].conf;
let mut tags = String::new();
let mut colors = SmallVec::new();
let backend_lck = context.accounts[self.cursor_pos.0].backend.read().unwrap();
@ -664,7 +661,7 @@ impl PlainListing {
fn redraw_list(&mut self, context: &Context) {
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].unwrap();
let mailbox = &account[&self.cursor_pos.1];
self.order.clear();
self.selection.clear();
@ -890,8 +887,7 @@ impl PlainListing {
}
}
if self.length == 0 && self.filter_term.is_empty() {
let mailbox = &account[self.cursor_pos.1];
let message = mailbox.to_string();
let message = format!("{} is empty", account[&self.cursor_pos.1].name());
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), self.length + 1, default_cell, context);
write_string_to_grid(
@ -1139,10 +1135,7 @@ impl Component for PlainListing {
UIEvent::EnvelopeRename(ref old_hash, ref new_hash) => {
let account = &context.accounts[self.cursor_pos.0];
if !account.collection.contains_key(new_hash)
|| !account[self.cursor_pos.1]
.unwrap()
.envelopes
.contains(new_hash)
|| !account.collection[&self.cursor_pos.1].contains(new_hash)
{
return false;
}

22
src/components/mail/listing/thread.rs

@ -99,7 +99,7 @@ impl MailListingTrait for ThreadListing {
ret
};
let message: String =
context.accounts[self.cursor_pos.0][self.cursor_pos.1].to_string();
context.accounts[self.cursor_pos.0][&self.cursor_pos.1].status();
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
self.length = 0;
@ -116,9 +116,7 @@ impl MailListingTrait for ThreadListing {
}
}
let account = &context.accounts[self.cursor_pos.0];
let mailbox = account[self.cursor_pos.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.cursor_pos.1];
self.length = threads.len();
self.locations.clear();
let default_cell = {
@ -129,7 +127,7 @@ impl MailListingTrait for ThreadListing {
ret
};
if self.length == 0 {
let message = format!("Folder `{}` is empty.", mailbox.folder.name());
let message = format!("Folder `{}` is empty.", account[&self.cursor_pos.1].name());
self.content = CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,
@ -348,12 +346,7 @@ impl ListingTrait for ThreadListing {
}
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
let mailbox = if context.accounts[self.cursor_pos.0][self.cursor_pos.1].is_available() {
context.accounts[self.cursor_pos.0][self.cursor_pos.1].unwrap()
} else {
return;
};
if mailbox.is_empty() || mailbox.len() <= idx {
if context.accounts[self.cursor_pos.0].collection[&self.cursor_pos.1].is_empty() {
return;
}
@ -414,12 +407,7 @@ impl ThreadListing {
}
fn highlight_line_self(&mut self, idx: usize, context: &Context) {
let mailbox = if context.accounts[self.cursor_pos.0][self.cursor_pos.1].is_available() {
context.accounts[self.cursor_pos.0][self.cursor_pos.1].unwrap()
} else {
return;
};
if mailbox.is_empty() {
if context.accounts[self.cursor_pos.0].collection[&self.cursor_pos.1].is_empty() {
return;
}
if self.locations[idx] != 0 {

20
src/components/mail/status.rs

@ -307,10 +307,14 @@ impl StatusPanel {
self.content[(2, h + y + 1)].set_ch(' ');
}
}
let count = a.ref_folders.values().fold((0, 0), |acc, f| {
let count = f.count().unwrap_or((0, 0));
(acc.0 + count.0, acc.1 + count.1)
});
let count = a
.folder_entries
.values()
.map(|entry| &entry.ref_folder)
.fold((0, 0), |acc, f| {
let count = f.count().unwrap_or((0, 0));
(acc.0 + count.0, acc.1 + count.1)
});
let (mut column_width, _) = write_string_to_grid(
&format!("Messages total {}, unseen {}", count.1, count.0),
&mut self.content,
@ -357,8 +361,9 @@ impl StatusPanel {
None,
);
for (i, f) in a
.ref_folders
.folder_entries
.values()
.map(|entry| &entry.ref_folder)
.filter(|f| f.special_usage() != SpecialUsageMailbox::Normal)
.enumerate()
{
@ -474,8 +479,9 @@ impl Component for AccountStatus {
None,
);
for f in a
.ref_folders
.folder_entries
.values()
.map(|entry| &entry.ref_folder)
.filter(|f| f.special_usage() != SpecialUsageMailbox::Normal)
{
line += 1;
@ -501,7 +507,7 @@ impl Component for AccountStatus {
);
line += 2;
for folder_node in a.list_folders() {
let f: &Folder = &a.ref_folders()[&folder_node.hash];
let f: &Folder = &a[&folder_node.hash].ref_folder;
if f.is_subscribed() {
write_string_to_grid(
f.path(),

11
src/components/mail/view/thread.rs

@ -69,7 +69,7 @@ pub struct ThreadView {
impl ThreadView {
const DESCRIPTION: &'static str = "thread view";
/*
* coordinates: (account index, mailbox index, root set thread_node index)
* coordinates: (account index, folder_hash, root set thread_node index)
* expanded_hash: optional position of expanded entry when we render the threadview. Default
* expanded message is the last one.
* context: current context
@ -163,8 +163,7 @@ impl ThreadView {
fn initiate(&mut self, expanded_hash: Option<ThreadNodeHash>, context: &Context) {
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.coordinates.1];
if !threads.groups.contains_key(&self.thread_group) {
return;
@ -649,8 +648,7 @@ impl ThreadView {
/* First draw the thread subject on the first row */
let y = if self.dirty {
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.coordinates.1];
let thread_root = threads
.thread_group_iter(self.thread_group)
.next()
@ -752,8 +750,7 @@ impl ThreadView {
/* First draw the thread subject on the first row */
let y = {
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let threads = &account.collection.threads[&self.coordinates.1];
let thread_root = threads
.thread_group_iter(self.thread_group)
.next()

452
src/conf/accounts.rs

@ -30,11 +30,12 @@ use melib::backends::{
BackendOp, Backends, Folder, FolderHash, MailBackend, NotifyFn, ReadOnlyOp, RefreshEvent,
RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
};
use melib::email::*;
use melib::error::{MeliError, Result};
use melib::mailbox::*;
use melib::text_processing::GlobMatch;
use melib::thread::{SortField, SortOrder, ThreadNode, ThreadNodeHash, Threads};
use melib::AddressBook;
use melib::Collection;
use smallvec::SmallVec;
use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification};
@ -50,97 +51,61 @@ use std::sync::{Arc, RwLock};
pub type Worker = Option<Async<Result<Vec<Envelope>>>>;
macro_rules! mailbox {
($idx:expr, $folders:expr) => {
$folders.get_mut(&$idx).unwrap().unwrap_mut()
};
}
#[derive(Serialize, Debug)]
pub enum MailboxEntry {
Available(Mailbox),
pub enum MailboxStatus {
Available,
Failed(MeliError),
/// first argument is done work, and second is total work
Parsing(Mailbox, usize, usize),
Parsing(usize, usize),
None,
}
impl Default for MailboxEntry {
impl Default for MailboxStatus {
fn default() -> Self {
MailboxEntry::Parsing(Mailbox::default(), 0, 0)
MailboxStatus::Parsing(0, 0)
}
}
impl std::fmt::Display for MailboxEntry {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
MailboxEntry::Available(ref m) => m.name().to_string(),
MailboxEntry::Failed(ref e) => e.to_string(),
MailboxEntry::None => "Not subscribed, is this a bug?".to_string(),
MailboxEntry::Parsing(_, done, total) => {
format!("Parsing messages. [{}/{}]", done, total)
}
}
)
}
}
impl MailboxEntry {
impl MailboxStatus {
pub fn is_available(&self) -> bool {
if let MailboxEntry::Available(_) = self {
if let MailboxStatus::Available = self {
true
} else {
false
}
}
pub fn is_parsing(&self) -> bool {
if let MailboxEntry::Parsing(_, _, _) = self {
if let MailboxStatus::Parsing(_, _) = self {
true
} else {
false
}
}
}
pub fn as_result(&self) -> Result<&Mailbox> {
match self {
MailboxEntry::Available(ref m) => Ok(m),
MailboxEntry::Parsing(ref m, _, _) => Ok(m),
MailboxEntry::Failed(ref e) => Err(MeliError::new(format!(
"Mailbox is not available: {}",
e.to_string()
))),
MailboxEntry::None => Err(MeliError::new("Mailbox is not subscribed.")),
}
}
pub fn as_mut_result(&mut self) -> Result<&mut Mailbox> {
match self {
MailboxEntry::Available(ref mut m) => Ok(m),
MailboxEntry::Parsing(ref mut m, _, _) => Ok(m),
MailboxEntry::Failed(ref e) => Err(MeliError::new(format!(
"Mailbox is not available: {}",
e.to_string()
))),
MailboxEntry::None => Err(MeliError::new("Mailbox is not subscribed.")),
}
}
#[derive(Debug)]
pub struct FolderEntry {
pub status: MailboxStatus,
pub name: String,
pub ref_folder: Folder,
pub conf: FileFolderConf,
pub worker: Worker,
}
pub fn unwrap_mut(&mut self) -> &mut Mailbox {
match self {
MailboxEntry::Available(ref mut m) => m,
MailboxEntry::Parsing(ref mut m, _, _) => m,
e => panic!(format!("mailbox is not available! {:#}", e)),
impl FolderEntry {
pub fn status(&self) -> String {
match self.status {
MailboxStatus::Available => self.name().to_string(),
MailboxStatus::Failed(ref e) => e.to_string(),
MailboxStatus::None => "Not subscribed, is this a bug?".to_string(),
MailboxStatus::Parsing(done, total) => {
format!("Parsing messages. [{}/{}]", done, total)
}
}
}
pub fn unwrap(&self) -> &Mailbox {
match self {
MailboxEntry::Available(ref m) => m,
MailboxEntry::Parsing(ref m, _, _) => m,
e => panic!(format!("mailbox is not available! {:#}", e)),
}
pub fn name(&self) -> &str {
&self.name
}
}
@ -149,20 +114,13 @@ pub struct Account {
pub index: usize,
name: String,
pub is_online: bool,
pub(crate) folders: FnvHashMap<FolderHash, MailboxEntry>,
pub(crate) ref_folders: FnvHashMap<FolderHash, Folder>,
pub(crate) folder_confs: FnvHashMap<FolderHash, FileFolderConf>,
pub(crate) folder_entries: FnvHashMap<FolderHash, FolderEntry>,
pub(crate) folders_order: Vec<FolderHash>,
pub(crate) folder_names: FnvHashMap<FolderHash, String>,
tree: Vec<FolderNode>,
sent_folder: Option<FolderHash>,
pub(crate) collection: Collection,
pub(crate) address_book: AddressBook,
pub(crate) workers: FnvHashMap<FolderHash, Worker>,
pub(crate) work_context: WorkContext,
pub(crate) settings: AccountConf,
pub(crate) runtime_settings: AccountConf,
pub(crate) backend: Arc<RwLock<Box<dyn MailBackend>>>,
@ -210,7 +168,7 @@ impl Drop for Account {
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::serialize_into(writer, &self.folders) {
if let Err(err) = bincode::serialize_into(writer, &self.collection) {
eprintln!("{}", err);
};
};
@ -218,26 +176,6 @@ impl Drop for Account {
}
}
pub struct MailboxIterator<'a> {
folders_order: &'a [FolderHash],
folders: &'a FnvHashMap<FolderHash, MailboxEntry>,
pos: usize,
}
impl<'a> Iterator for MailboxIterator<'a> {
type Item = &'a MailboxEntry;
fn next(&mut self) -> Option<&'a MailboxEntry> {
if self.pos == self.folders.len() {
return None;
}
let fh = &self.folders_order[self.pos];
self.pos += 1;
Some(&self.folders[&fh])
}
}
#[derive(Serialize, Debug, Clone, Default)]
pub struct FolderNode {
pub hash: FolderHash,
@ -295,16 +233,12 @@ impl Account {
index,
name,
is_online: false,
folders: Default::default(),
ref_folders: Default::default(),
folder_confs: Default::default(),
folder_entries: Default::default(),
folders_order: Default::default(),
folder_names: Default::default(),
tree: Default::default(),
address_book,
sent_folder: Default::default(),
collection: Default::default(),
workers: Default::default(),
work_context,
runtime_settings: settings.clone(),
settings,
@ -325,12 +259,9 @@ impl Account {
return;
}
};
let mut folders: FnvHashMap<FolderHash, MailboxEntry> =
let mut folder_entries: FnvHashMap<FolderHash, FolderEntry> =
FnvHashMap::with_capacity_and_hasher(ref_folders.len(), Default::default());
let mut folders_order: Vec<FolderHash> = Vec::with_capacity(ref_folders.len());
let mut workers: FnvHashMap<FolderHash, Worker> = FnvHashMap::default();
let mut folder_names = FnvHashMap::default();
let mut folder_confs = FnvHashMap::default();
let mut sent_folder = None;
for f in ref_folders.values_mut() {
@ -355,7 +286,16 @@ impl Account {
}
_ => {}
}
folder_confs.insert(f.hash(), conf.clone());
folder_entries.insert(
f.hash(),
FolderEntry {
ref_folder: f.clone(),
name: f.path().to_string(),
status: MailboxStatus::None,
conf: conf.clone(),
worker: None,
},
);
} else {
let mut new = FileFolderConf::default();
new.folder_conf.usage = if f.special_usage() != SpecialUsageMailbox::Normal {
@ -368,9 +308,17 @@ impl Account {
tmp
};
folder_confs.insert(f.hash(), new);
folder_entries.insert(
f.hash(),
FolderEntry {
ref_folder: f.clone(),
name: f.path().to_string(),
status: MailboxStatus::None,
conf: new,
worker: None,
},
);
}
folder_names.insert(f.hash(), f.path().to_string());
}
let mut tree: Vec<FolderNode> = Vec::new();
@ -378,36 +326,27 @@ impl Account {
for (h, f) in ref_folders.iter() {
if !f.is_subscribed() {
/* Skip unsubscribed folder */
folders.insert(*h, MailboxEntry::None);
workers.insert(*h, None);
continue;
}
folders.insert(
*h,
MailboxEntry::Parsing(Mailbox::new(f.clone(), &FnvHashMap::default()), 0, 0),
);
workers.insert(
*h,
Account::new_worker(
folder_entries.entry(*h).and_modify(|entry| {
entry.status = MailboxStatus::Parsing(0, 0);
entry.worker = Account::new_worker(
f.clone(),
&mut self.backend,
&self.work_context,
self.notify_fn.clone(),
),
);
);
});
collection.mailboxes.insert(*h, Default::default());
collection.threads.insert(*h, Threads::default());
}
build_folders_order(&mut tree, &ref_folders, &mut folders_order);
self.folders = folders;
self.ref_folders = ref_folders;
self.folder_confs = folder_confs;
build_folders_order(&mut tree, &folder_entries, &mut folders_order);
self.folders_order = folders_order;
self.folder_names = folder_names;
self.folder_entries = folder_entries;
self.tree = tree;
self.sent_folder = sent_folder;
self.collection = collection;
self.workers = workers;
}
fn new_worker(
@ -483,7 +422,7 @@ impl Account {
Some(w)
}
pub fn reload(&mut self, event: RefreshEvent, folder_hash: FolderHash) -> Option<UIEvent> {
if !self.folders[&folder_hash].is_available() {
if !self.folder_entries[&folder_hash].status.is_available() {
self.event_queue.push_back((folder_hash, event));
return None;
}
@ -493,7 +432,6 @@ impl Account {
//let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap();
match kind {
RefreshEventKind::Update(old_hash, envelope) => {
mailbox!(&folder_hash, self.folders).rename(old_hash, envelope.hash());
#[cfg(feature = "sqlite3")]
{
if let Err(err) = crate::sqlite3::remove(old_hash).and_then(|_| {
@ -514,7 +452,6 @@ impl Account {
}
RefreshEventKind::Rename(old_hash, new_hash) => {
debug!("rename {} to {}", old_hash, new_hash);
mailbox!(&folder_hash, self.folders).rename(old_hash, new_hash);
self.collection.rename(old_hash, new_hash, folder_hash);
#[cfg(feature = "sqlite3")]
{
@ -538,13 +475,10 @@ impl Account {
RefreshEventKind::Create(envelope) => {
let env_hash = envelope.hash();
if self.collection.contains_key(&env_hash)
&& mailbox!(&folder_hash, self.folders)
.envelopes
.contains(&env_hash)
&& self.collection[&folder_hash].contains(&env_hash)
{
return None;
}
mailbox!(&folder_hash, self.folders).insert(env_hash);
let (is_seen, is_draft) =
{ (envelope.is_seen(), envelope.flags().contains(Flag::DRAFT)) };
let (subject, from) = {
@ -578,10 +512,12 @@ impl Account {
self.collection.insert_reply(env_hash);
}
let ref_folders: FnvHashMap<FolderHash, Folder> =
self.backend.read().unwrap().folders().unwrap();
let folder_conf = &self.folder_confs[&folder_hash];
if folder_conf.folder_conf().ignore.is_true() {
if self.folder_entries[&folder_hash]
.conf
.folder_conf
.ignore
.is_true()
{
return Some(UIEvent::MailboxUpdate((self.index, folder_hash)));
}
@ -606,13 +542,12 @@ impl Account {
"{}\n{} {}",
subject,
self.name,
ref_folders[&folder_hash].name(),
self.folder_entries[&folder_hash].name()
),
Some(crate::types::NotificationType::NewMail),
));
}
RefreshEventKind::Remove(envelope_hash) => {
mailbox!(&folder_hash, self.folders).remove(envelope_hash);
#[cfg(feature = "sqlite3")]
{
let envelopes = self.collection.envelopes.read();
@ -633,15 +568,15 @@ impl Account {
return Some(EnvelopeRemove(envelope_hash));
}
RefreshEventKind::Rescan => {
let ref_folders: FnvHashMap<FolderHash, Folder> =
self.backend.read().unwrap().folders().unwrap();
let handle = Account::new_worker(
ref_folders[&folder_hash].clone(),
self.folder_entries[&folder_hash].ref_folder.clone(),
&mut self.backend,
&self.work_context,
self.notify_fn.clone(),
);
self.workers.insert(folder_hash, handle);
self.folder_entries.entry(folder_hash).and_modify(|entry| {
entry.worker = handle;
});
}
RefreshEventKind::Failure(e) => {
debug!("RefreshEvent Failure: {}", e.to_string());
@ -717,18 +652,14 @@ impl Account {
}
pub fn len(&self) -> usize {
self.folders.len()
self.tree.len()
}
pub fn is_empty(&self) -> bool {
self.folders.is_empty()
}
pub fn ref_folders(&self) -> &FnvHashMap<FolderHash, Folder> {
&self.ref_folders
self.tree.is_empty()
}
pub fn list_folders(&self) -> Vec<FolderNode> {
let mut ret = Vec::with_capacity(self.folders.len());
let mut ret = Vec::with_capacity(self.folder_entries.len());
fn rec(node: &FolderNode, ret: &mut Vec<FolderNode>) {
ret.push(node.clone());
for c in node.children.iter() {
@ -747,14 +678,12 @@ impl Account {
pub fn name(&self) -> &str {
&self.name
}
pub fn workers(&mut self) -> &mut FnvHashMap<FolderHash, Worker> {
&mut self.workers
}
fn load_mailbox(&mut self, folder_hash: FolderHash, payload: Result<Vec<Envelope>>) {
if payload.is_err() {
self.folders
.insert(folder_hash, MailboxEntry::Failed(payload.unwrap_err()));
self.folder_entries.entry(folder_hash).and_modify(|entry| {
entry.status = MailboxStatus::Failed(payload.unwrap_err());
});
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(folder_hash)))
.unwrap();
@ -765,20 +694,14 @@ impl Account {
.into_iter()
.map(|e| (e.hash(), e))
.collect::<FnvHashMap<EnvelopeHash, Envelope>>();
match self.folders.entry(folder_hash).or_default() {
MailboxEntry::Failed(_) | MailboxEntry::None => {}
MailboxEntry::Parsing(ref mut m, _, _) | MailboxEntry::Available(ref mut m) => {
m.merge(&envelopes);
if let Some(updated_folders) =
self.collection
.merge(envelopes, folder_hash, self.sent_folder)
{
for f in updated_folders {
self.sender
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(f)))
.unwrap();
}
}
if let Some(updated_folders) =
self.collection