Browse Source

ui: move Collection to Account

Each account had one mailbox per folder, which had one associated
collection. Now each Account has one Collection for all folders and each
Mailbox object holds only the hashes of each message.

Collection also gets Threads for each folder in order to mix messages
(ie from/to Sent folder).

Insert Sent emails in chronological order

if inserted unsorted, mails a, b with a happened-before b, might never
  get added.

Fix multiple insertions in ThreadTree upon insert_reply

insert_reply was creating multiple copies in threading
tags/pre-alpha-0.0
Manos Pitsidianakis 2 years ago
parent
commit
42654410e3
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 77
      melib/src/mailbox.rs
  2. 183
      melib/src/mailbox/collection.rs
  3. 817
      melib/src/mailbox/thread.rs
  4. 11
      ui/src/components/mail/accounts.rs
  5. 34
      ui/src/components/mail/compose.rs
  6. 8
      ui/src/components/mail/listing.rs
  7. 53
      ui/src/components/mail/listing/compact.rs
  8. 38
      ui/src/components/mail/listing/plain.rs
  9. 40
      ui/src/components/mail/listing/thread.rs
  10. 71
      ui/src/components/mail/view.rs
  11. 50
      ui/src/components/mail/view/thread.rs
  12. 15
      ui/src/components/utilities.rs
  13. 139
      ui/src/conf/accounts.rs
  14. 3
      ui/src/execute/actions.rs

77
melib/src/mailbox.rs

@ -41,26 +41,30 @@ pub use self::collection::*;
use std::option::Option;
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 collection: Collection,
pub envelopes: FnvHashSet<EnvelopeHash>,
pub thread_root_set: FnvHashSet<ThreadHash>,
has_sent: bool,
}
impl Mailbox {
pub fn new(folder: Folder, envelopes: Result<Vec<Envelope>>) -> Result<Mailbox> {
let mut envelopes: Vec<Envelope> = envelopes?;
envelopes.sort_by(|a, b| a.date().cmp(&b.date()));
let collection = Collection::new(envelopes, &folder);
pub fn new(
folder: Folder,
envelopes: Result<&FnvHashMap<EnvelopeHash, Envelope>>,
) -> Result<Mailbox> {
let envelopes = envelopes?;
let name = folder.name().into();
let envelopes = envelopes.keys().cloned().collect();
Ok(Mailbox {
folder,
collection,
name,
envelopes,
..Default::default()
})
}
@ -70,65 +74,20 @@ impl Mailbox {
}
pub fn is_empty(&self) -> bool {
self.collection.is_empty()
self.envelopes.is_empty()
}
pub fn len(&self) -> usize {
self.collection.len()
self.envelopes.len()
}
pub fn thread_to_mail_mut(&mut self, h: ThreadHash) -> &mut Envelope {
self.collection
.envelopes
.entry(self.collection.threads.thread_to_mail(h))
.or_default()
pub fn insert(&mut self, h: EnvelopeHash) {
self.envelopes.insert(h);
}
pub fn thread_to_mail(&self, h: ThreadHash) -> &Envelope {
&self.collection.envelopes[&self.collection.threads.thread_to_mail(h)]
}
pub fn threaded_mail(&self, h: ThreadHash) -> EnvelopeHash {
self.collection.threads.thread_to_mail(h)
}
pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) {
let thread;
{
let x = &mut self.collection.envelopes.entry(i).or_default();
thread = &self.collection.threads[&x.thread()];
}
(self.collection.envelopes.entry(i).or_default(), thread)
}
pub fn thread(&self, h: ThreadHash) -> &ThreadNode {
&self.collection.threads.thread_nodes()[&h]
}
pub fn insert_sent_folder(&mut self, _sent: &Mailbox) {
/*if !self.has_sent {
for envelope in sent.collection.envelopes.values() {
self.insert_reply(envelope);
}
self.has_sent = true;
}*/
}
pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
self.collection.rename(old_hash, new_hash);
}
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
self.collection.update_envelope(old_hash, envelope);
}
self.envelopes.remove(&old_hash);
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
let hash = envelope.hash();
self.collection.insert(envelope);
&self.collection[&hash]
self.envelopes.insert(new_hash);
}
pub fn insert_reply(&mut self, envelope: &Envelope) {
debug!("mailbox insert reply {}", self.name);
self.collection.insert_reply(envelope);
}
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
self.collection.remove(envelope_hash);
// debug!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
pub fn remove(&mut self, h: EnvelopeHash) {
self.envelopes.remove(&h);
}
}

183
melib/src/mailbox/collection.rs

@ -1,4 +1,5 @@
use super::*;
use crate::mailbox::backends::FolderHash;
use std::collections::BTreeMap;
use std::fs;
use std::io;
@ -6,22 +7,20 @@ use std::ops::{Deref, DerefMut};
use fnv::FnvHashMap;
/// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone, Deserialize, Default, Serialize)]
pub struct Collection {
#[serde(skip_serializing, skip_deserializing)]
folder: Folder,
pub envelopes: FnvHashMap<EnvelopeHash, Envelope>,
message_ids: FnvHashMap<Vec<u8>, EnvelopeHash>,
date_index: BTreeMap<UnixTimestamp, EnvelopeHash>,
subject_index: Option<BTreeMap<String, EnvelopeHash>>,
pub threads: Threads,
pub threads: FnvHashMap<FolderHash, Threads>,
sent_folder: Option<FolderHash>,
}
impl Drop for Collection {
fn drop(&mut self) {
let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", self.folder.hash()))
.unwrap();
let cache_dir: xdg::BaseDirectories =
xdg::BaseDirectories::with_profile("meli", "threads".to_string()).unwrap();
if let Ok(cached) = cache_dir.place_cache_file("threads") {
/* place result in cache directory */
let f = match fs::File::create(cached) {
@ -37,47 +36,24 @@ impl Drop for Collection {
}
impl Collection {
pub fn new(vec: Vec<Envelope>, folder: &Folder) -> Collection {
let mut envelopes: FnvHashMap<EnvelopeHash, Envelope> =
FnvHashMap::with_capacity_and_hasher(vec.len(), Default::default());
for e in vec {
envelopes.insert(e.hash(), e);
}
pub fn new(envelopes: FnvHashMap<EnvelopeHash, Envelope>) -> Collection {
let date_index = BTreeMap::new();
let subject_index = None;
let message_ids = FnvHashMap::with_capacity_and_hasher(2048, Default::default());
/* Scrap caching for now. When a cached threads file is loaded, we must remove/rehash the
* thread nodes that shouldn't exist anymore (e.g. because their file moved from /new to
* /cur, or it was deleted).
*/
let threads = Threads::new(&mut envelopes);
/*let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator;
debug!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
ret
} else {
Threads::new(&mut envelopes)
};
*/
let threads = FnvHashMap::with_capacity_and_hasher(16, Default::default());
Collection {
folder: folder.clone(),
envelopes,
date_index,
message_ids,
subject_index,
threads,
sent_folder: None,
}
}
@ -89,22 +65,34 @@ impl Collection {
self.envelopes.is_empty()
}
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
pub fn remove(&mut self, envelope_hash: EnvelopeHash, folder_hash: FolderHash) {
debug!("DEBUG: Removing {}", envelope_hash);
self.envelopes.remove(&envelope_hash);
self.threads.remove(envelope_hash, &mut self.envelopes);
self.threads
.entry(folder_hash)
.or_default()
.remove(envelope_hash, &mut self.envelopes);
}
pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) {
pub fn rename(
&mut self,
old_hash: EnvelopeHash,
new_hash: EnvelopeHash,
folder_hash: FolderHash,
) {
if !self.envelopes.contains_key(&old_hash) {
return;
}
let mut env = self.envelopes.remove(&old_hash).unwrap();
env.set_hash(new_hash);
self.message_ids
.insert(env.message_id().raw().to_vec(), new_hash);
self.envelopes.insert(new_hash, env);
{
if self
.threads
.entry(folder_hash)
.or_default()
.update_envelope(old_hash, new_hash, &self.envelopes)
.is_ok()
{
@ -114,17 +102,102 @@ impl Collection {
/* envelope is not in threads, so insert it */
let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
self.threads
.entry(folder_hash)
.or_default()
.insert(&mut (*env), &self.envelopes);
}
}
pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
pub fn merge(
&mut self,
mut envelopes: FnvHashMap<EnvelopeHash, Envelope>,
folder_hash: FolderHash,
mailbox: &mut Result<Mailbox>,
sent_folder: Option<FolderHash>,
) {
self.sent_folder = sent_folder;
envelopes.retain(|&h, e| {
if self.message_ids.contains_key(e.message_id().raw()) {
/* skip duplicates until a better way to handle them is found. */
//FIXME
if let Ok(mailbox) = mailbox.as_mut() {
mailbox.remove(h);
}
false
} else {
self.message_ids.insert(e.message_id().raw().to_vec(), h);
true
}
});
let mut threads = Threads::new(&mut envelopes);
for (h, e) in envelopes {
self.envelopes.insert(h, e);
}
for (t_fh, t) in self.threads.iter_mut() {
if self.sent_folder.map(|f| f == folder_hash).unwrap_or(false) {
let mut ordered_hash_set = threads
.hash_set
.iter()
.cloned()
.collect::<Vec<EnvelopeHash>>();
unsafe {
/* FIXME NLL
* Sorting ordered_hash_set triggers a borrow which should not happen with NLL
* probably */
let envelopes = &self.envelopes as *const FnvHashMap<EnvelopeHash, Envelope>;
ordered_hash_set.sort_by(|a, b| {
(*envelopes)[a]
.date()
.partial_cmp(&(*(envelopes))[b].date())
.unwrap()
});
}
for h in ordered_hash_set {
t.insert_reply(&mut self.envelopes, h);
}
continue;
}
if self.sent_folder.map(|f| f == *t_fh).unwrap_or(false) {
let mut ordered_hash_set =
t.hash_set.iter().cloned().collect::<Vec<EnvelopeHash>>();
unsafe {
/* FIXME NLL
* Sorting ordered_hash_set triggers a borrow which should not happen with NLL
* probably */
let envelopes = &self.envelopes as *const FnvHashMap<EnvelopeHash, Envelope>;
ordered_hash_set.sort_by(|a, b| {
(*envelopes)[a]
.date()
.partial_cmp(&(*(envelopes))[b].date())
.unwrap()
});
}
for h in ordered_hash_set {
threads.insert_reply(&mut self.envelopes, h);
}
}
}
self.threads.insert(folder_hash, threads);
}
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope, folder_hash: FolderHash) {
self.envelopes.remove(&old_hash);
let new_hash = envelope.hash();
self.message_ids
.insert(envelope.message_id().raw().to_vec(), new_hash);
self.envelopes.insert(new_hash, envelope);
if self.sent_folder.map(|f| f == folder_hash).unwrap_or(false) {
for (_, t) in self.threads.iter_mut() {
t.update_envelope(old_hash, new_hash, &self.envelopes);
}
}
{
if self
.threads
.entry(folder_hash)
.or_default()
.update_envelope(old_hash, new_hash, &self.envelopes)
.is_ok()
{
@ -134,26 +207,28 @@ impl Collection {
/* envelope is not in threads, so insert it */
let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
self.threads
.entry(folder_hash)
.or_default()
.insert(&mut (*env), &self.envelopes);
}
}
pub fn insert(&mut self, envelope: Envelope) {
pub fn insert(&mut self, envelope: Envelope, folder_hash: FolderHash) -> &Envelope {
let hash = envelope.hash();
debug!("DEBUG: Inserting hash {} in {}", hash, self.folder.name());
self.message_ids
.insert(envelope.message_id().raw().to_vec(), hash);
self.envelopes.insert(hash, envelope);
let env = self.envelopes.entry(hash).or_default() as *mut Envelope;
unsafe {
self.threads.insert(&mut (*env), &self.envelopes);
}
self.threads
.entry(folder_hash)
.or_default()
.insert_reply(&mut self.envelopes, hash);
&self.envelopes[&hash]
}
pub(crate) fn insert_reply(&mut self, _envelope: &Envelope) {
return;
/*
//self.insert(envelope);
debug!("insert_reply in collections");
self.threads.insert_reply(envelope, &mut self.envelopes);
*/
pub fn insert_reply(&mut self, env_hash: EnvelopeHash) {
for (_, t) in self.threads.iter_mut() {
t.insert_reply(&mut self.envelopes, env_hash);
}
}
}

817
melib/src/mailbox/thread.rs
File diff suppressed because it is too large
View File

11
ui/src/components/mail/accounts.rs

@ -163,12 +163,11 @@ impl AccountsPanel {
write_string_to_grid(
&format!(
"total {}",
a.iter_mailboxes().fold(0, |mut acc, m| {
acc += m
.map(|m| m.collection.values().filter(|e| !e.is_seen()).count())
.unwrap_or(0);
acc
})
a.collection
.envelopes
.values()
.filter(|e| !e.is_seen())
.count()
),
&mut self.content,
Color::Default,

34
ui/src/components/mail/compose.rs

@ -127,21 +127,14 @@ impl Composer {
* msg: index of message we reply to in thread_nodes
* context: current context
*/
pub fn edit(coordinates: (usize, usize, usize), msg: ThreadHash, context: &Context) -> Self {
let mailbox = &context.accounts[coordinates.0][coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let thread_nodes = &threads.thread_nodes();
pub fn edit(account_pos: usize, h: EnvelopeHash, context: &Context) -> Self {
let mut ret = Composer::default();
let message = &mailbox.collection[&thread_nodes[&msg].message().unwrap()];
let op = context.accounts[coordinates.0]
.backend
.operation(message.hash(), mailbox.folder.hash());
let op = context.accounts[account_pos].operation(&h);
let envelope: &Envelope = context.accounts[account_pos].get_env(&h);
ret.draft = Draft::edit(message, op);
ret.draft = Draft::edit(envelope, op);
ret.account_cursor = coordinates.0;
ret.account_cursor = account_pos;
ret
}
pub fn with_context(
@ -149,17 +142,14 @@ impl Composer {
msg: ThreadHash,
context: &Context,
) -> Self {
let mailbox = &context.accounts[coordinates.0][coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let account = &context.accounts[coordinates.0];
let mailbox = &account[coordinates.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_nodes = &threads.thread_nodes();
let mut ret = Composer::default();
let p = &thread_nodes[&msg];
let parent_message = &mailbox.collection[&p.message().unwrap()];
let mut op = context.accounts[coordinates.0]
.backend
.operation(parent_message.hash(), mailbox.folder.hash());
let parent_message = &account.collection[&p.message().unwrap()];
let mut op = account.operation(&parent_message.hash());
let parent_bytes = op.as_bytes();
ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap());
@ -168,10 +158,10 @@ impl Composer {
if p.show_subject() {
format!(
"Re: {}",
mailbox.collection[&p.message().unwrap()].subject().clone()
account.get_env(&p.message().unwrap()).subject().clone()
)
} else {
mailbox.collection[&p.message().unwrap()].subject().into()
account.get_env(&p.message().unwrap()).subject().into()
},
);

8
ui/src/components/mail/listing.rs

@ -500,11 +500,13 @@ impl Listing {
) {
match context.accounts[index].status(entries[&folder_idx].hash()) {
Ok(_) => {
let count = context.accounts[index][entries[&folder_idx].hash()]
let account = &context.accounts[index];
let count = account[entries[&folder_idx].hash()]
.as_ref()
.unwrap()
.collection
.values()
.envelopes
.iter()
.map(|h| &account.collection[&h])
.filter(|e| !e.is_seen())
.count();
let len = s.len();

53
ui/src/components/mail/listing/compact.rs

@ -189,15 +189,15 @@ impl MailboxView {
}
if old_cursor_pos == self.new_cursor_pos {
self.view.update(context);
} else {
} else if self.unfocused {
self.view = ThreadView::new(self.new_cursor_pos, None, context);
}
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let account = &context.accounts[self.cursor_pos.0];
let mailbox = account[self.cursor_pos.1].as_ref().unwrap();
self.length = mailbox.collection.threads.root_len();
let threads = &account.collection.threads[&mailbox.folder.hash()];
self.length = threads.root_len();
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
self.order.clear();
if self.length == 0 {
@ -211,11 +211,10 @@ impl MailboxView {
);
return;
}
let threads = &mailbox.collection.threads;
let mut rows = Vec::with_capacity(1024);
let mut min_width = (0, 0, 0);
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
threads.sort_by(self.sort, self.subsort, &account.collection);
for (idx, root_idx) in threads.root_iter().enumerate() {
let thread_node = &threads.thread_nodes()[&root_idx];
let i = if let Some(i) = thread_node.message() {
@ -227,7 +226,7 @@ impl MailboxView {
}
threads.thread_nodes()[&iter_ptr].message().unwrap()
};
if !mailbox.collection.contains_key(&i) {
if !context.accounts[self.cursor_pos.0].contains_key(&i) {
debug!("key = {}", i);
debug!(
"name = {} {}",
@ -238,7 +237,7 @@ impl MailboxView {
panic!();
}
let root_envelope: &Envelope = &mailbox.collection[&i];
let root_envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&i);
let strings = MailboxView::make_entry_string(
root_envelope,
@ -277,14 +276,14 @@ impl MailboxView {
}
threads.thread_nodes()[&iter_ptr].message().unwrap()
};
if !mailbox.collection.contains_key(&i) {
debug!("key = {}", i);
debug!(
"name = {} {}",
mailbox.name(),
context.accounts[self.cursor_pos.0].name()
);
debug!("{:#?}", context.accounts);
if !context.accounts[self.cursor_pos.0].contains_key(&i) {
//debug!("key = {}", i);
//debug!(
// "name = {} {}",
// mailbox.name(),
// context.accounts[self.cursor_pos.0].name()
//);
//debug!("{:#?}", context.accounts);
panic!();
}
@ -365,13 +364,12 @@ impl MailboxView {
context: &Context,
) {
if idx == self.cursor_pos.2 || grid.is_none() {
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
if mailbox.is_empty() {
if self.length == 0 {
return;
}
let threads = &mailbox.collection.threads;
let account = &context.accounts[self.cursor_pos.0];
let mailbox = account[self.cursor_pos.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_node = threads.root_set(idx);
let thread_node = &threads.thread_nodes()[&thread_node];
let i = if let Some(i) = thread_node.message() {
@ -384,7 +382,7 @@ impl MailboxView {
threads.thread_nodes()[&iter_ptr].message().unwrap()
};
let root_envelope: &Envelope = &mailbox.collection[&i];
let root_envelope: &Envelope = &account.get_env(&i);
let fg_color = if !root_envelope.is_seen() {
Color::Byte(0)
} else {
@ -671,11 +669,12 @@ impl Component for MailboxView {
Action::ToggleThreadSnooze => {
{
//FIXME NLL
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_mut()
let account = &mut context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1]
.as_ref()
.map(|m| m.folder.hash())
.unwrap();
let threads = &mut mailbox.collection.threads;
let threads = account.collection.threads.entry(folder_hash).or_default();
let thread_group = threads.thread_nodes()
[&threads.root_set(self.cursor_pos.2)]
.thread_group();

38
ui/src/components/mail/listing/plain.rs

@ -131,9 +131,8 @@ impl PlainListing {
return;
}
}
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let account = &context.accounts[self.cursor_pos.0];
let mailbox = &account[self.cursor_pos.1].as_ref().unwrap();
self.length = mailbox.len();
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
@ -158,31 +157,31 @@ impl PlainListing {
break;
}
/* Write an entire line for each envelope entry. */
self.local_collection = mailbox.collection.keys().cloned().collect();
self.local_collection = account.collection.keys().cloned().collect();
let sort = self.sort;
self.local_collection.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
let ma = &mailbox.collection[a];
let mb = &mailbox.collection[b];
let ma = &account.get_env(a);
let mb = &account.get_env(b);
mb.date().cmp(&ma.date())
}
(SortField::Date, SortOrder::Asc) => {
let ma = &mailbox.collection[a];
let mb = &mailbox.collection[b];
let ma = &account.get_env(a);
let mb = &account.get_env(b);
ma.date().cmp(&mb.date())
}
(SortField::Subject, SortOrder::Desc) => {
let ma = &mailbox.collection[a];
let mb = &mailbox.collection[b];
let ma = &account.get_env(a);
let mb = &account.get_env(b);
ma.subject().cmp(&mb.subject())
}
(SortField::Subject, SortOrder::Asc) => {
let ma = &mailbox.collection[a];
let mb = &mailbox.collection[b];
let ma = &account.get_env(a);
let mb = &account.get_env(b);
mb.subject().cmp(&ma.subject())
}
});
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
let envelope: &Envelope = &account.get_env(&self.local_collection[idx]);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
@ -230,10 +229,8 @@ impl PlainListing {
}
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
let account = &context.accounts[self.cursor_pos.0];
let envelope: &Envelope = &account.get_env(&self.local_collection[idx]);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
@ -367,11 +364,8 @@ impl Component for PlainListing {
false
} else {
let account = &mut context.accounts[self.cursor_pos.0];
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope = &mut mailbox
.collection
.entry(self.local_collection[idx])
.or_default();
let envelope: &mut Envelope =
&mut account.get_env_mut(&self.local_collection[idx]);
!envelope.is_seen()
}
};

40
ui/src/components/mail/listing/thread.rs

@ -20,7 +20,6 @@
*/
use super::*;
use std::dbg;
const MAX_COLS: usize = 500;
@ -129,11 +128,10 @@ impl ThreadListing {
return;
}
}
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref()
.unwrap();
let account = &context.accounts[self.cursor_pos.0];
let mailbox = account[self.cursor_pos.1].as_ref().unwrap();
self.length = mailbox.collection.threads.len();
self.length = account.collection.threads.len();
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
self.locations.clear();
if self.length == 0 {
@ -151,8 +149,8 @@ impl ThreadListing {
let mut indentations: Vec<bool> = Vec::with_capacity(6);
let mut thread_idx = 0; // needed for alternate thread colors
/* Draw threaded view. */
let threads = &mailbox.collection.threads;
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
let threads = &account.collection.threads[&mailbox.folder.hash()];
threads.sort_by(self.sort, self.subsort, &account.collection);
let thread_nodes: &FnvHashMap<ThreadHash, ThreadNode> = &threads.thread_nodes();
let mut iter = threads.threads_iter().peekable();
/* This is just a desugared for loop so that we can use .peek() */
@ -164,7 +162,7 @@ impl ThreadListing {
thread_idx += 1;
}
if thread_node.has_message() {
let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()];
let envelope: &Envelope = &account.get_env(&thread_node.message().unwrap());
self.locations.push(envelope.hash());
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
@ -230,7 +228,8 @@ impl ThreadListing {
return;
}
if self.locations[idx] != 0 {
let envelope: &Envelope = &mailbox.collection[&self.locations[idx]];
let envelope: &Envelope =
&context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
@ -262,7 +261,8 @@ impl ThreadListing {
}
if self.locations[idx] != 0 {
let envelope: &Envelope = &mailbox.collection[&self.locations[idx]];
let envelope: &Envelope =
&context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]);
let fg_color = if !envelope.is_seen() {
Color::Byte(0)
@ -469,26 +469,14 @@ impl Component for ThreadListing {
} else {
let account = &mut context.accounts[self.cursor_pos.0];
let (hash, is_seen) = {
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
debug!("key is {}", self.locations[dbg!(self.cursor_pos).2]);
let envelope: &Envelope =
&mailbox.collection[&self.locations[self.cursor_pos.2]];
&account.get_env(&self.locations[self.cursor_pos.2]);
(envelope.hash(), envelope.is_seen())
};
if !is_seen {
let folder_hash = {
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
mailbox.folder.hash()
};
let op = {
let backend = &account.backend;
backend.operation(hash, folder_hash)
};
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope = mailbox
.collection
.get_mut(&self.locations[self.cursor_pos.2])
.unwrap();
let op = account.operation(&hash);
let envelope: &mut Envelope =
account.get_env_mut(&self.locations[self.cursor_pos.2]);
envelope.set_seen(op).unwrap();
true
} else {

71
ui/src/components/mail/view.rs

@ -89,8 +89,7 @@ impl MailView {
) -> Self {
let account = &mut context.accounts[coordinates.0];
let (hash, is_seen) = {
let mailbox = &mut account[coordinates.1].as_mut().unwrap();
let envelope: &mut Envelope = &mut mailbox.collection.entry(coordinates.2).or_default();
let envelope: &Envelope = &account.get_env(&coordinates.2);
(envelope.hash(), envelope.is_seen())
};
if !is_seen {
@ -102,8 +101,7 @@ impl MailView {
let backend = &account.backend;
backend.operation(hash, folder_hash)
};
let mailbox = &mut account[coordinates.1].as_mut().unwrap();
let envelope: &mut Envelope = &mut mailbox.collection.entry(coordinates.2).or_default();
let envelope: &mut Envelope = &mut account.get_env_mut(&coordinates.2);
envelope.set_seen(op).unwrap();
}
MailView {
@ -292,16 +290,13 @@ impl Component for MailView {
let bottom_right = bottom_right!(area);
let y: usize = {
let accounts = &mut context.accounts;
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
if !mailbox.collection.contains_key(&self.coordinates.2) {
let account = &mut context.accounts[self.coordinates.0];
if !account.contains_key(&self.coordinates.2) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
return;
}
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
if self.mode == ViewMode::Raw {
clear_area(grid, area);
@ -383,14 +378,9 @@ impl Component for MailView {
if self.dirty {
let body = {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
let op = context.accounts[mailbox_idx.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let account = &mut context.accounts[self.coordinates.0];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
let op = account.operation(&envelope.hash());
envelope.body(op)
};
match self.mode {
@ -413,14 +403,9 @@ impl Component for MailView {
ViewMode::Subview | ViewMode::ContactSelector(_) => {}
ViewMode::Raw => {
let text = {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
let mut op = context.accounts[mailbox_idx.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let account = &mut context.accounts[self.coordinates.0];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
let mut op = account.operation(&envelope.hash());
op.as_bytes()
.map(|v| String::from_utf8_lossy(v).into_owned())
.unwrap_or_else(|e| e.to_string())
@ -507,8 +492,7 @@ impl Component for MailView {
let account = &mut context.accounts[self.coordinates.0];
let mut results = Vec::new();
{
let mailbox = &account[self.coordinates.1].as_ref().unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
for c in s.collect() {
let c = usize::from_ne_bytes({
[c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]
@ -536,11 +520,8 @@ impl Component for MailView {
}
return true;
}
let accounts = &context.accounts;
let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let account = &mut context.accounts[self.coordinates.0];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
let mut entries = Vec::new();
for (idx, env) in envelope
@ -594,15 +575,9 @@ impl Component for MailView {
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
{
let accounts = &context.accounts;
let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let op = context.accounts[self.coordinates.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let account = &mut context.accounts[self.coordinates.0];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
let op = account.operation(&envelope.hash());
if let Some(u) = envelope.body(op).attachments().get(lidx) {
match u.content_type() {
ContentType::MessageRfc822 => {
@ -690,16 +665,10 @@ impl Component for MailView {
.replies
.push_back(UIEvent::StatusEvent(StatusEvent::BufClear));
let url = {
let accounts = &context.accounts;
let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let account = &mut context.accounts[self.coordinates.0];
let envelope: &Envelope = &account.get_env(&self.coordinates.2);
let finder = LinkFinder::new();
let op = context.accounts[self.coordinates.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let op = account.operation(&envelope.hash());
let mut t = envelope.body(op).text().to_string();
let links: Vec<Link> = finder.links(&t).collect();
if let Some(u) = links.get(lidx) {

50
ui/src/components/mail/view/thread.rs

@ -146,16 +146,15 @@ impl ThreadView {
}
fn initiate(&mut self, expanded_hash: Option<ThreadHash>, context: &Context) {
/* stack to push thread messages in order in order to pop and print them later */
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_iter = threads.thread_iter(self.coordinates.2);
self.entries.clear();
for (line, (ind, thread_hash)) in thread_iter.enumerate() {
let entry = if let Some(msg_hash) = threads.thread_nodes()[&thread_hash].message() {
let seen: bool = mailbox.collection[&msg_hash].is_seen();
let seen: bool = account.get_env(&msg_hash).is_seen();
self.make_entry((ind, thread_hash, line), msg_hash, seen)
} else {
continue;
@ -180,7 +179,7 @@ impl ThreadView {
let mut highlight_reply_subjects: Vec<Option<usize>> =
Vec::with_capacity(self.entries.len());
for e in &mut self.entries {
let envelope: &Envelope = &mailbox.collection[&e.msg_hash];
let envelope: &Envelope = &context.accounts[self.coordinates.0].get_env(&e.msg_hash);
let thread_node = &threads.thread_nodes()[&e.index.1];
let string = if thread_node.show_subject() {
let subject = envelope.subject();
@ -533,10 +532,9 @@ impl ThreadView {
/* First draw the thread subject on the first row */
let y = if self.dirty {
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() {
i
@ -545,7 +543,7 @@ impl ThreadView {
.message()
.unwrap()
};
let envelope: &Envelope = &mailbox.collection[&i];
let envelope: &Envelope = account.get_env(&i);
let (x, y) = write_string_to_grid(
&envelope.subject(),
@ -613,10 +611,9 @@ impl ThreadView {
/* First draw the thread subject on the first row */
let y = {
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() {
i
@ -627,7 +624,7 @@ impl ThreadView {
}
threads.thread_nodes()[&iter_ptr].message().unwrap()
};
let envelope: &Envelope = &mailbox.collection[&i];
let envelope: &Envelope = account.get_env(&i);
let (x, y) = write_string_to_grid(
&envelope.subject(),
@ -833,10 +830,9 @@ impl Component for ThreadView {
}
UIEvent::Input(Key::Char('e')) => {
{
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref()
.unwrap();
let threads = &mailbox.collection.threads;
let account = &context.accounts[self.coordinates.0];
let mailbox = &account[self.coordinates.1].as_ref().unwrap();
let threads = &account.collection.threads[&mailbox.folder.hash()];
let thread_node =
&threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() {
@ -846,20 +842,18 @@ impl Component for ThreadView {
.message()
.unwrap()
};
let envelope: &Envelope = &mailbox.collection[&i];
let op = context.accounts[self.coordinates.0]
.backend
.operation(envelope.hash(), mailbox.folder.hash());
let envelope: &Envelope = &account.get_env(&i);
let op = account.operation(&envelope.hash());
debug!(
"sending action edit for {}, {}",
envelope.message_id(),
op.description()
);
context.replies.push_back(UIEvent::Action(Tab(Edit(
self.coordinates.0,
envelope.hash(),
))));
}
context.replies.push_back(UIEvent::Action(Tab(Edit(
self.coordinates,
self.entries[self.expanded_pos].index.1,
))));
return true;
}
UIEvent::Input(Key::Up) => {

15
ui/src/components/utilities.rs

@ -868,13 +868,18 @@ impl Component for StatusBar {
return false;
}
}
let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap();
let account = &context.accounts[*idx_a];
let m = &account[*idx_f].as_ref().unwrap();
self.status = format!(
"{} | Mailbox: {}, Messages: {}, New: {}",
self.mode,
m.folder.name(),
m.collection.len(),
m.collection.values().filter(|e| !e.is_seen()).count()
m.envelopes.len(),
m.envelopes
.iter()
.map(|h| &account.collection[&h])
.filter(|e| !e.is_seen())
.count()
);
self.dirty = true;
}
@ -1254,8 +1259,8 @@ impl Component for Tabbed {
self.children[self.cursor_pos].set_dirty();
return true;
}
UIEvent::Action(Tab(Edit(coordinates, msg))) => {
self.add_component(Box::new(Composer::edit(coordinates, msg, context)));
UIEvent::Action(Tab(Edit(account_pos, msg))) => {
self.add_component(Box::new(Composer::edit(account_pos, msg, context)));
self.cursor_pos = self.children.len() - 1;
self.children[self.cursor_pos].set_dirty();
return true;

139
ui/src/conf/accounts.rs

@ -31,9 +31,11 @@ use melib::async_workers::{Async, AsyncBuilder, AsyncStatus};
use melib::backends::FolderHash;
use melib::error::Result;
use melib::mailbox::backends::{
Backends, Folder, MailBackend, NotifyFn, RefreshEvent, RefreshEventConsumer, RefreshEventKind,
BackendOp, Backends, Folder, MailBackend, NotifyFn, RefreshEvent, RefreshEventConsumer,
RefreshEventKind,
};
use melib::mailbox::*;
use melib::thread::ThreadHash;
use melib::AddressBook;
use std::collections::VecDeque;
@ -45,7 +47,7 @@ use std::result;
use std::sync::Arc;
use types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification};
pub type Worker = Option<Async<Result<Mailbox>>>;
pub type Worker = Option<Async<(Result<FnvHashMap<EnvelopeHash, Envelope>>, Result<Mailbox>)>>;
macro_rules! mailbox {
($idx:expr, $folders:expr) => {
@ -66,7 +68,8 @@ pub struct Account {
pub(crate) folders_order: Vec<FolderHash>,
folder_names: FnvHashMap<FolderHash, String>,
tree: Vec<FolderNode>,
sent_folder: Option<usize>,
sent_folder: Option<FolderHash>,
pub(crate) collection: Collection,
pub(crate) address_book: AddressBook,
@ -161,11 +164,15 @@ impl Account {
let notify_fn = Arc::new(notify_fn);
let mut folder_names = FnvHashMap::default();
let mut sent_folder = None;
for f in ref_folders.values_mut() {
let entry = settings
.folder_confs
.entry(f.name().to_string())
.or_default();
if f.name().eq_ignore_ascii_case("sent") {
sent_folder = Some(f.hash());
}
if (f.name().eq_ignore_ascii_case("junk")
|| f.name().eq_ignore_ascii_case("spam")
|| f.name().eq_ignore_ascii_case("sent")
@ -247,7 +254,8 @@ impl Account {
folder_names,
tree,
address_book,
sent_folder: None,
sent_folder,
collection: Collection::new(Default::default()),
workers,
settings: settings.clone(),
runtime_settings: settings,
@ -271,10 +279,17 @@ impl Account {
let work = handle.work().unwrap();
work.compute();
handle.join();
let envelopes = handle.extract();
let envelopes: Result<FnvHashMap<EnvelopeHash, Envelope>> = handle.extract().map(|v| {
v.into_iter()
.map(|e| (e.hash(), e))
.collect::<FnvHashMap<EnvelopeHash, Envelope>>()
});
let hash = folder.hash();
let ret = Mailbox::new(folder, envelopes)