save Account to disk

closes #114
embed
Manos Pitsidianakis 2019-05-14 21:47:47 +03:00
parent 4582bcd5ae
commit 22d868f499
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
15 changed files with 315 additions and 235 deletions

View File

@ -19,7 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
#[derive(Debug, Default, Clone)] #[derive(Debug, Serialize, Default, Clone)]
pub struct AccountSettings { pub struct AccountSettings {
pub name: String, pub name: String,
pub root_folder: String, pub root_folder: String,

View File

@ -35,7 +35,7 @@ use nom;
pub type Result<T> = result::Result<T, MeliError>; pub type Result<T> = result::Result<T, MeliError>;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MeliError { pub struct MeliError {
details: String, details: String,
} }

View File

@ -31,6 +31,8 @@ pub use self::email::*;
pub mod backends; pub mod backends;
use self::backends::Folder; use self::backends::Folder;
use crate::error::Result; use crate::error::Result;
use crate::mailbox::thread::ThreadHash;
pub mod thread; pub mod thread;
pub use self::thread::{SortField, SortOrder, ThreadNode, Threads}; pub use self::thread::{SortField, SortOrder, ThreadNode, Threads};
@ -40,8 +42,9 @@ pub use self::collection::*;
use std::option::Option; use std::option::Option;
/// `Mailbox` represents a folder of mail. /// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone, Default)] #[derive(Debug, Deserialize, Serialize, Clone, Default)]
pub struct Mailbox { pub struct Mailbox {
#[serde(skip_serializing, skip_deserializing)]
pub folder: Folder, pub folder: Folder,
name: String, name: String,
pub collection: Collection, pub collection: Collection,
@ -72,28 +75,28 @@ impl Mailbox {
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.collection.len() self.collection.len()
} }
pub fn thread_to_mail_mut(&mut self, i: usize) -> &mut Envelope { pub fn thread_to_mail_mut(&mut self, h: ThreadHash) -> &mut Envelope {
self.collection self.collection
.envelopes .envelopes
.entry(self.collection.threads.thread_to_mail(i)) .entry(self.collection.threads.thread_to_mail(h))
.or_default() .or_default()
} }
pub fn thread_to_mail(&self, i: usize) -> &Envelope { pub fn thread_to_mail(&self, h: ThreadHash) -> &Envelope {
&self.collection.envelopes[&self.collection.threads.thread_to_mail(i)] &self.collection.envelopes[&self.collection.threads.thread_to_mail(h)]
} }
pub fn threaded_mail(&self, i: usize) -> EnvelopeHash { pub fn threaded_mail(&self, h: ThreadHash) -> EnvelopeHash {
self.collection.threads.thread_to_mail(i) self.collection.threads.thread_to_mail(h)
} }
pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) { pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) {
let thread; let thread;
{ {
let x = &mut self.collection.envelopes.entry(i).or_default(); let x = &mut self.collection.envelopes.entry(i).or_default();
thread = &self.collection.threads[x.thread()]; thread = &self.collection.threads[&x.thread()];
} }
(self.collection.envelopes.entry(i).or_default(), thread) (self.collection.envelopes.entry(i).or_default(), thread)
} }
pub fn thread(&self, i: usize) -> &ThreadNode { pub fn thread(&self, h: ThreadHash) -> &ThreadNode {
&self.collection.threads.thread_nodes()[i] &self.collection.threads.thread_nodes()[&h]
} }
pub fn insert_sent_folder(&mut self, _sent: &Mailbox) { pub fn insert_sent_folder(&mut self, _sent: &Mailbox) {

View File

@ -7,8 +7,9 @@ use std::ops::{Deref, DerefMut};
use fnv::FnvHashMap; use fnv::FnvHashMap;
/// `Mailbox` represents a folder of mail. /// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Deserialize, Default, Serialize)]
pub struct Collection { pub struct Collection {
#[serde(skip_serializing, skip_deserializing)]
folder: Folder, folder: Folder,
pub envelopes: FnvHashMap<EnvelopeHash, Envelope>, pub envelopes: FnvHashMap<EnvelopeHash, Envelope>,
date_index: BTreeMap<UnixTimestamp, EnvelopeHash>, date_index: BTreeMap<UnixTimestamp, EnvelopeHash>,

View File

@ -34,6 +34,7 @@ use parser::BytesExt;
use super::backends::BackendOp; use super::backends::BackendOp;
use crate::error::{MeliError, Result}; use crate::error::{MeliError, Result};
use crate::mailbox::thread::ThreadHash;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -320,7 +321,7 @@ pub struct Envelope {
references: Option<References>, references: Option<References>,
timestamp: UnixTimestamp, timestamp: UnixTimestamp,
thread: usize, thread: ThreadHash,
hash: EnvelopeHash, hash: EnvelopeHash,
@ -355,7 +356,7 @@ impl Envelope {
timestamp: 0, timestamp: 0,
thread: 0, thread: ThreadHash::null(),
hash, hash,
flags: Flag::default(), flags: Flag::default(),
@ -731,10 +732,10 @@ impl Envelope {
None => Vec::new(), None => Vec::new(),
} }
} }
pub fn thread(&self) -> usize { pub fn thread(&self) -> ThreadHash {
self.thread self.thread
} }
pub fn set_thread(&mut self, new_val: usize) { pub fn set_thread(&mut self, new_val: ThreadHash) {
self.thread = new_val; self.thread = new_val;
} }
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) { pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) {

View File

@ -34,6 +34,7 @@
use crate::mailbox::email::parser::BytesExt; use crate::mailbox::email::parser::BytesExt;
use crate::mailbox::email::*; use crate::mailbox::email::*;
use uuid::Uuid;
use fnv::{FnvHashMap, FnvHashSet}; use fnv::{FnvHashMap, FnvHashSet};
use std::cell::{Ref, RefCell}; use std::cell::{Ref, RefCell};
@ -48,46 +49,71 @@ use std::str::FromStr;
type Envelopes = FnvHashMap<EnvelopeHash, Envelope>; type Envelopes = FnvHashMap<EnvelopeHash, Envelope>;
#[derive(PartialEq, Hash, Eq, Debug, Copy, Clone, Serialize, Deserialize, Default)]
pub struct ThreadHash(Uuid);
impl ThreadHash {
fn new() -> Self {
ThreadHash(Uuid::new_v4())
}
pub fn null() -> Self {
ThreadHash(Uuid::nil())
}
}
/* Helper macros to avoid repeating ourselves */ /* Helper macros to avoid repeating ourselves */
fn rec_change_root_parent(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) { fn rec_change_root_parent(
b[idx].thread_group = new_root; b: &mut FnvHashMap<ThreadHash, ThreadNode>,
if let Some(p) = b[idx].parent { idx: ThreadHash,
new_root: ThreadHash,
) {
let entry = b.entry(idx).or_default();
entry.thread_group = new_root;
if let Some(p) = entry.parent {
rec_change_children(b, p, new_root); rec_change_children(b, p, new_root);
rec_change_root_parent(b, p, new_root); rec_change_root_parent(b, p, new_root);
} }
} }
fn rec_change_children(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) { fn rec_change_children(
b[idx].thread_group = new_root; b: &mut FnvHashMap<ThreadHash, ThreadNode>,
idx: ThreadHash,
new_root: ThreadHash,
) {
let entry = b.entry(idx).or_default();
entry.thread_group = new_root;
for c in b[idx].children.clone() { for c in entry.children.clone() {
rec_change_children(b, c, new_root); rec_change_children(b, c, new_root);
} }
} }
macro_rules! remove_from_parent { macro_rules! remove_from_parent {
($buf:expr, $idx:expr) => { ($buf:expr, $idx:expr) => {{
if let Some(p) = $buf[$idx].parent { let entry = $buf.entry($idx).or_default();
if let Some(pos) = $buf[p].children.iter().position(|c| *c == $idx) { if let Some(p) = entry.parent {
$buf[p].children.remove(pos); if let Some(pos) = $buf[&p].children.iter().position(|c| *c == $idx) {
$buf.entry(p).and_modify(|e| {
e.children.remove(pos);
});
} }
rec_change_root_parent($buf, p, p); rec_change_root_parent($buf, p, p);
} }
$buf[$idx].parent = None; $buf.entry($idx).and_modify(|e| e.parent = None);
rec_change_children($buf, $idx, $idx); rec_change_children($buf, $idx, $idx);
$buf[$idx].thread_group = $idx; $buf.entry($idx).and_modify(|e| e.thread_group = $idx);
}; }};
} }
macro_rules! make { macro_rules! make {
(($p:expr)parent of($c:expr), $buf:expr) => { (($p:expr)parent of($c:expr), $buf:expr) => {
remove_from_parent!($buf, $c); remove_from_parent!($buf, $c);
if !($buf[$p]).children.contains(&$c) { if !($buf[&$p]).children.contains(&$c) {
$buf[$p].children.push($c); $buf.entry($p).and_modify(|e| e.children.push($c));
} else { } else {
panic!(); panic!();
} }
$buf[$c].parent = Some($p); $buf.entry($c).and_modify(|e| e.parent = Some($p));
union($buf, $c, $p); union($buf, $c, $p);
}; };
} }
@ -201,7 +227,7 @@ impl FromStr for SortOrder {
#[derive(Clone, Deserialize, Serialize)] #[derive(Clone, Deserialize, Serialize)]
struct ThreadTree { struct ThreadTree {
id: usize, id: ThreadHash,
children: Vec<ThreadTree>, children: Vec<ThreadTree>,
} }
@ -212,7 +238,7 @@ impl fmt::Debug for ThreadTree {
} }
impl ThreadTree { impl ThreadTree {
fn new(id: usize) -> Self { fn new(id: ThreadHash) -> Self {
ThreadTree { ThreadTree {
id, id,
children: Vec::new(), children: Vec::new(),
@ -241,8 +267,8 @@ pub struct ThreadsIterator<'a> {
tree: Ref<'a, Vec<ThreadTree>>, tree: Ref<'a, Vec<ThreadTree>>,
} }
impl<'a> Iterator for ThreadsIterator<'a> { impl<'a> Iterator for ThreadsIterator<'a> {
type Item = (usize, usize, bool); type Item = (usize, ThreadHash, bool);
fn next(&mut self) -> Option<(usize, usize, bool)> { fn next(&mut self) -> Option<(usize, ThreadHash, bool)> {
{ {
let mut tree = &(*self.tree); let mut tree = &(*self.tree);
for i in &self.stack { for i in &self.stack {
@ -293,8 +319,8 @@ pub struct ThreadIterator<'a> {
tree: Ref<'a, Vec<ThreadTree>>, tree: Ref<'a, Vec<ThreadTree>>,
} }
impl<'a> Iterator for ThreadIterator<'a> { impl<'a> Iterator for ThreadIterator<'a> {
type Item = (usize, usize); type Item = (usize, ThreadHash);
fn next(&mut self) -> Option<(usize, usize)> { fn next(&mut self) -> Option<(usize, ThreadHash)> {
{ {
let mut tree = &(*self.tree); let mut tree = &(*self.tree);
for i in &self.stack { for i in &self.stack {
@ -324,8 +350,8 @@ impl<'a> Iterator for ThreadIterator<'a> {
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ThreadNode { pub struct ThreadNode {
message: Option<EnvelopeHash>, message: Option<EnvelopeHash>,
parent: Option<usize>, parent: Option<ThreadHash>,
children: Vec<usize>, children: Vec<ThreadHash>,
date: UnixTimestamp, date: UnixTimestamp,
indentation: usize, indentation: usize,
show_subject: bool, show_subject: bool,
@ -334,7 +360,7 @@ pub struct ThreadNode {
has_unseen: bool, has_unseen: bool,
/* Union/Find set fields */ /* Union/Find set fields */
thread_group: usize, thread_group: ThreadHash,
rank: i32, rank: i32,
} }
@ -350,13 +376,19 @@ impl Default for ThreadNode {
len: 0, len: 0,
has_unseen: false, has_unseen: false,
thread_group: 0, thread_group: ThreadHash::default(),
rank: 0, rank: 0,
} }
} }
} }
impl ThreadNode { impl ThreadNode {
fn new(thread_group: ThreadHash) -> Self {
ThreadNode {
thread_group,
..Default::default()
}
}
pub fn show_subject(&self) -> bool { pub fn show_subject(&self) -> bool {
self.show_subject self.show_subject
} }
@ -381,7 +413,7 @@ impl ThreadNode {
self.message.is_some() self.message.is_some()
} }
pub fn parent(&self) -> Option<usize> { pub fn parent(&self) -> Option<ThreadHash> {
self.parent self.parent
} }
@ -389,7 +421,7 @@ impl ThreadNode {
self.parent.is_some() self.parent.is_some()
} }
pub fn children(&self) -> &[usize] { pub fn children(&self) -> &[ThreadHash] {
&self.children &self.children
} }
@ -400,11 +432,11 @@ impl ThreadNode {
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Threads { pub struct Threads {
thread_nodes: Vec<ThreadNode>, thread_nodes: FnvHashMap<ThreadHash, ThreadNode>,
root_set: RefCell<Vec<usize>>, root_set: RefCell<Vec<ThreadHash>>,
tree: RefCell<Vec<ThreadTree>>, tree: RefCell<Vec<ThreadTree>>,
message_ids: FnvHashMap<Vec<u8>, usize>, message_ids: FnvHashMap<Vec<u8>, ThreadHash>,
pub hash_set: FnvHashSet<EnvelopeHash>, pub hash_set: FnvHashSet<EnvelopeHash>,
sort: RefCell<(SortField, SortOrder)>, sort: RefCell<(SortField, SortOrder)>,
subsort: RefCell<(SortField, SortOrder)>, subsort: RefCell<(SortField, SortOrder)>,
@ -425,8 +457,8 @@ pub struct RootIterator<'a> {
} }
impl<'a> Iterator for RootIterator<'a> { impl<'a> Iterator for RootIterator<'a> {
type Item = usize; type Item = ThreadHash;
fn next(&mut self) -> Option<usize> { fn next(&mut self) -> Option<ThreadHash> {
{ {
if self.pos == self.root_tree.len() { if self.pos == self.root_tree.len() {
return None; return None;
@ -436,15 +468,16 @@ impl<'a> Iterator for RootIterator<'a> {
} }
} }
} }
fn find(buf: &mut Vec<ThreadNode>, i: usize) -> usize { fn find(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
if buf[i].thread_group == i { if buf[&h].thread_group == h {
return i; return h;
} }
let p = buf[i].thread_group; let p = buf[&h].thread_group;
buf[i].thread_group = find(buf, p); let new_group = find(buf, p);
buf[i].thread_group buf.entry(h).and_modify(|e| e.thread_group = new_group);
new_group
} }
fn union(buf: &mut Vec<ThreadNode>, x: usize, y: usize) -> usize { fn union(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, x: ThreadHash, y: ThreadHash) -> ThreadHash {
let mut x_root = find(buf, x); let mut x_root = find(buf, x);
let mut y_root = find(buf, y); let mut y_root = find(buf, y);
@ -453,24 +486,26 @@ fn union(buf: &mut Vec<ThreadNode>, x: usize, y: usize) -> usize {
return x_root; return x_root;
} }
if buf[x_root].rank < buf[y_root].rank { if buf[&x_root].rank < buf[&y_root].rank {
mem::swap(&mut x_root, &mut y_root); mem::swap(&mut x_root, &mut y_root);
} }
// x and y are not in same set, so we merge them // x and y are not in same set, so we merge them
// //
buf[y_root].thread_group = x_root; buf.entry(y_root).and_modify(|e| e.thread_group = x_root);
if buf[x_root].rank == buf[y_root].rank { if buf[&x_root].rank == buf[&y_root].rank {
buf[x_root].rank += 1; buf.entry(x_root).and_modify(|e| {
e.rank += 1;
});
} }
x_root x_root
} }
impl Threads { impl Threads {
fn find(&mut self, i: usize) -> usize { fn find(&mut self, i: ThreadHash) -> ThreadHash {
find(&mut self.thread_nodes, i) find(&mut self.thread_nodes, i)
} }
fn union(&mut self, x: usize, y: usize) -> usize { fn union(&mut self, x: ThreadHash, y: ThreadHash) -> ThreadHash {
let mut x_root = self.find(x); let mut x_root = self.find(x);
let mut y_root = self.find(y); let mut y_root = self.find(y);
@ -479,47 +514,51 @@ impl Threads {
return x_root; return x_root;
} }
if self.thread_nodes[x_root].rank < self.thread_nodes[y_root].rank { if self.thread_nodes[&x_root].rank < self.thread_nodes[&y_root].rank {
mem::swap(&mut x_root, &mut y_root); mem::swap(&mut x_root, &mut y_root);
} }
// x and y are not in same set, so we merge them // x and y are not in same set, so we merge them
// //
self.thread_nodes[y_root].thread_group = x_root; self.thread_nodes
if self.thread_nodes[x_root].rank == self.thread_nodes[y_root].rank { .entry(y_root)
self.thread_nodes[x_root].rank += 1; .and_modify(|e| e.thread_group = x_root);
if self.thread_nodes[&x_root].rank == self.thread_nodes[&y_root].rank {
self.thread_nodes.entry(x_root).and_modify(|e| e.rank += 1);
} }
x_root x_root
} }
fn prune_empty_nodes(&mut self, root_set: &mut Vec<usize>) { fn prune_empty_nodes(&mut self, root_set: &mut Vec<ThreadHash>) {
fn prune( fn prune(
thread_nodes: &mut Vec<ThreadNode>, thread_nodes: &mut FnvHashMap<ThreadHash, ThreadNode>,
idx: usize, idx: ThreadHash,
root_set: &mut Vec<usize>, root_set: &mut Vec<ThreadHash>,
) -> bool { ) -> bool {
/* "If it is an empty container with no children, nuke it." */ /* "If it is an empty container with no children, nuke it." */
if !thread_nodes[idx].has_message() && thread_nodes[idx].children.is_empty() { if !thread_nodes[&idx].has_message() && thread_nodes[&idx].children.is_empty() {
remove_from_parent!(thread_nodes, idx); remove_from_parent!(thread_nodes, idx);
return true; return true;
} }
if !thread_nodes[idx].has_message() && !thread_nodes[idx].has_parent() { if !thread_nodes[&idx].has_message() && !thread_nodes[&idx].has_parent() {
if thread_nodes[idx].children.len() == 1 { if thread_nodes[&idx].children.len() == 1 {
/* "Do not promote the children if doing so would promote them to the root set /* "Do not promote the children if doing so would promote them to the root set
* -- unless there is only one child, in which case, do." */ * -- unless there is only one child, in which case, do." */
let child = thread_nodes[idx].children[0]; let child = thread_nodes[&idx].children[0];
root_set.push(child); root_set.push(child);
remove_from_parent!(thread_nodes, child); remove_from_parent!(thread_nodes, child);
return true; // Pruned return true; // Pruned
} }
} else if let Some(p) = thread_nodes[idx].parent { } else if let Some(p) = thread_nodes[&idx].parent {
if !thread_nodes[idx].has_message() { if !thread_nodes[&idx].has_message() {
let orphans = thread_nodes[idx].children.clone(); let orphans = thread_nodes[&idx].children.clone();
for c in orphans { for c in orphans {
make!((p) parent of (c), thread_nodes); make!((p) parent of (c), thread_nodes);
} }
remove_from_parent!(thread_nodes, idx); remove_from_parent!(thread_nodes, idx);
thread_nodes[idx].children.clear(); thread_nodes.entry(idx).and_modify(|e| {
e.children.clear();
});
return true; // Pruned return true; // Pruned
} }
} }
@ -528,15 +567,15 @@ impl Threads {
*/ */
let mut c_idx = 0; let mut c_idx = 0;
loop { loop {
if c_idx == thread_nodes[idx].children.len() { if c_idx == thread_nodes[&idx].children.len() {
break; break;
} }
let c = thread_nodes[idx].children[c_idx]; let c = thread_nodes[&idx].children[c_idx];
if !prune(thread_nodes, c, root_set) { if !prune(thread_nodes, c, root_set) {
c_idx += 1; c_idx += 1;
} }
} }
!thread_nodes[idx].has_message() && thread_nodes[idx].children.is_empty() !thread_nodes[&idx].has_message() && thread_nodes[&idx].children.is_empty()
} }
let mut idx = 0; let mut idx = 0;
@ -556,10 +595,12 @@ impl Threads {
/* To reconstruct thread information from the mails we need: */ /* To reconstruct thread information from the mails we need: */
/* a vector to hold thread members */ /* a vector to hold thread members */
let thread_nodes: Vec<ThreadNode> = let thread_nodes: FnvHashMap<ThreadHash, ThreadNode> = FnvHashMap::with_capacity_and_hasher(
Vec::with_capacity((collection.len() as f64 * 1.2) as usize); (collection.len() as f64 * 1.2) as usize,
Default::default(),
);
/* A hash table of Message IDs */ /* A hash table of Message IDs */
let message_ids: FnvHashMap<Vec<u8>, usize> = let message_ids: FnvHashMap<Vec<u8>, ThreadHash> =
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
let hash_set: FnvHashSet<EnvelopeHash> = let hash_set: FnvHashSet<EnvelopeHash> =
FnvHashSet::with_capacity_and_hasher(collection.len(), Default::default()); FnvHashSet::with_capacity_and_hasher(collection.len(), Default::default());
@ -616,11 +657,11 @@ impl Threads {
fn create_root_set(&mut self, collection: &Envelopes) { fn create_root_set(&mut self, collection: &Envelopes) {
/* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that /* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that
* have no parents. These are the root messages of each thread */ * have no parents. These are the root messages of each thread */
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len()); let mut root_set: Vec<ThreadHash> = Vec::with_capacity(collection.len());
/* Find the root set */ /* Find the root set */
for v in self.message_ids.values() { for v in self.message_ids.values() {
if self.thread_nodes[*v].parent.is_none() { if self.thread_nodes[v].parent.is_none() {
root_set.push(*v); root_set.push(*v);
} }
} }
@ -635,22 +676,22 @@ impl Threads {
* messages which don't have References headers at all still get threaded (to the extent * messages which don't have References headers at all still get threaded (to the extent
* possible, at least.)" * possible, at least.)"
*/ */
let mut subject_table: FnvHashMap<Vec<u8>, (bool, usize)> = let mut subject_table: FnvHashMap<Vec<u8>, (bool, ThreadHash)> =
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
for &r in root_set.iter() { for &r in root_set.iter() {
/* "Find the subject of that sub-tree": */ /* "Find the subject of that sub-tree": */
let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[r].message.is_some() { let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[&r].message.is_some() {
/* "If there is a message in the Container, the subject is the subject of that /* "If there is a message in the Container, the subject is the subject of that
* message. " */ * message. " */
let msg_idx = self.thread_nodes[r].message.unwrap(); let msg_idx = self.thread_nodes[&r].message.unwrap();
let envelope = &collection[&msg_idx]; let envelope = &collection[&msg_idx];
(envelope.subject(), !envelope.references().is_empty()) (envelope.subject(), !envelope.references().is_empty())
} else { } else {
/* "If there is no message in the Container, then the Container will have at least /* "If there is no message in the Container, then the Container will have at least
* one child Container, and that Container will have a message. Use the subject of * one child Container, and that Container will have a message. Use the subject of
* that message instead." */ * that message instead." */
let msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]] let msg_idx = self.thread_nodes[&self.thread_nodes[&r].children[0]]
.message .message
.unwrap(); .unwrap();
let envelope = &collection[&msg_idx]; let envelope = &collection[&msg_idx];
@ -677,7 +718,7 @@ impl Threads {
* "The container in the table has a ``Re:'' version of this subject, and this * "The container in the table has a ``Re:'' version of this subject, and this
* container has a non-``Re:'' version of this subject. The non-re version is the * container has a non-``Re:'' version of this subject. The non-re version is the
* more interesting of the two." */ * more interesting of the two." */
if (!self.thread_nodes[id].has_message() && self.thread_nodes[r].has_message()) if (!self.thread_nodes[&id].has_message() && self.thread_nodes[&r].has_message())
|| (other_is_re && !is_re) || (other_is_re && !is_re)
{ {
mem::replace( mem::replace(
@ -697,12 +738,12 @@ impl Threads {
let r = root_set[i]; let r = root_set[i];
/* "Find the subject of this Container (as above.)" */ /* "Find the subject of this Container (as above.)" */
let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[r].message.is_some() { let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[&r].message.is_some() {
let msg_idx = self.thread_nodes[r].message.unwrap(); let msg_idx = self.thread_nodes[&r].message.unwrap();
let envelope = &collection[&msg_idx]; let envelope = &collection[&msg_idx];
(envelope.subject(), !envelope.references().is_empty()) (envelope.subject(), !envelope.references().is_empty())
} else { } else {
let msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]] let msg_idx = self.thread_nodes[&self.thread_nodes[&r].children[0]]
.message .message
.unwrap(); .unwrap();
let envelope = &collection[&msg_idx]; let envelope = &collection[&msg_idx];
@ -718,7 +759,7 @@ impl Threads {
let (other_is_re, other_idx) = subject_table[subject]; let (other_is_re, other_idx) = subject_table[subject];
/* "If it is null, or if it is this container, continue." */ /* "If it is null, or if it is this container, continue." */
if !self.thread_nodes[other_idx].has_message() || other_idx == r { if !self.thread_nodes[&other_idx].has_message() || other_idx == r {
continue; continue;
} }
@ -729,8 +770,9 @@ impl Threads {
* "If both are dummies, append one's children to the other, and remove the now-empty * "If both are dummies, append one's children to the other, and remove the now-empty
* container." * container."
*/ */
if !self.thread_nodes[r].has_message() && !self.thread_nodes[other_idx].has_message() { if !self.thread_nodes[&r].has_message() && !self.thread_nodes[&other_idx].has_message()
let children = self.thread_nodes[r].children.clone(); {
let children = self.thread_nodes[&r].children.clone();
for c in children { for c in children {
make!((other_idx) parent of (c), &mut self.thread_nodes); make!((other_idx) parent of (c), &mut self.thread_nodes);
} }
@ -740,16 +782,16 @@ impl Threads {
* of the empty, and a sibling of the other ``real'' messages with the same subject * of the empty, and a sibling of the other ``real'' messages with the same subject
* (the empty's children.)" * (the empty's children.)"
*/ */
} else if self.thread_nodes[r].has_message() } else if self.thread_nodes[&r].has_message()
&& !self.thread_nodes[other_idx].has_message() && !self.thread_nodes[&other_idx].has_message()
{ {
make!((other_idx) parent of (r), &mut self.thread_nodes); make!((other_idx) parent of (r), &mut self.thread_nodes);
if !root_set.contains(&other_idx) { if !root_set.contains(&other_idx) {
root_set.push(other_idx); root_set.push(other_idx);
} }
roots_to_remove.push(i); roots_to_remove.push(i);
} else if !self.thread_nodes[r].has_message() } else if !self.thread_nodes[&r].has_message()
&& self.thread_nodes[other_idx].has_message() && self.thread_nodes[&other_idx].has_message()
{ {
make!((r) parent of (other_idx), &mut self.thread_nodes); make!((r) parent of (other_idx), &mut self.thread_nodes);
if let Some(pos) = root_set.iter().position(|&i| i == other_idx) { if let Some(pos) = root_set.iter().position(|&i| i == other_idx) {
@ -759,7 +801,7 @@ impl Threads {
* "If that container is a non-empty, and that message's subject does not begin with ``Re:'', but this * "If that container is a non-empty, and that message's subject does not begin with ``Re:'', but this
* message's subject does, then make this be a child of the other." * message's subject does, then make this be a child of the other."
*/ */
} else if self.thread_nodes[other_idx].has_message() && !other_is_re && is_re { } else if self.thread_nodes[&other_idx].has_message() && !other_is_re && is_re {
make!((other_idx) parent of (r), &mut self.thread_nodes); make!((other_idx) parent of (r), &mut self.thread_nodes);
roots_to_remove.push(i); roots_to_remove.push(i);
@ -769,7 +811,7 @@ impl Threads {
* without will be in the hash table, regardless of the order in which they were * without will be in the hash table, regardless of the order in which they were
* seen.)" * seen.)"
*/ */
} else if self.thread_nodes[other_idx].has_message() && other_is_re && !is_re { } else if self.thread_nodes[&other_idx].has_message() && other_is_re && !is_re {
make!((r) parent of (other_idx), &mut self.thread_nodes); make!((r) parent of (other_idx), &mut self.thread_nodes);
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) { if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
roots_to_remove.push(pos); roots_to_remove.push(pos);
@ -780,9 +822,8 @@ impl Threads {
* hierarchical relationship which might not be true." * hierarchical relationship which might not be true."
*/ */
} else { } else {
self.thread_nodes.push(Default::default()); let new_id = ThreadHash::new();
let new_id = self.thread_nodes.len() - 1; self.thread_nodes.insert(new_id, ThreadNode::new(new_id));
self.thread_nodes[new_id].thread_group = new_id;
make!((new_id) parent of (r), &mut self.thread_nodes); make!((new_id) parent of (r), &mut self.thread_nodes);
make!((new_id) parent of (other_idx), &mut self.thread_nodes); make!((new_id) parent of (other_idx), &mut self.thread_nodes);
root_set[i] = new_id; root_set[i] = new_id;
@ -827,20 +868,19 @@ impl Threads {
* - hash_set * - hash_set
* - message fields in thread_nodes * - message fields in thread_nodes
*/ */
let idx = if let Some((idx, node)) = self let thread_hash = if let Some((key, node)) = self
.thread_nodes .thread_nodes
.iter_mut() .iter_mut()
.enumerate()
.find(|(_, n)| n.message.map(|n| n == old_hash).unwrap_or(false)) .find(|(_, n)| n.message.map(|n| n == old_hash).unwrap_or(false))
{ {
node.message = Some(new_hash); node.message = Some(new_hash);
idx *key
} else { } else {
return Err(()); return Err(());
}; };
self.hash_set.remove(&old_hash); self.hash_set.remove(&old_hash);
self.hash_set.insert(new_hash); self.hash_set.insert(new_hash);
self.rebuild_thread(idx, collection); self.rebuild_thread(thread_hash, collection);
Ok(()) Ok(())
} }
@ -856,15 +896,15 @@ impl Threads {
// debug!("DEBUG: {} in threads is idx= {}", envelope_hash, pos); // debug!("DEBUG: {} in threads is idx= {}", envelope_hash, pos);
//} //}
let t_id: usize; let t_id: ThreadHash;
{ {
if let Some(pos) = self if let Some((pos, n)) = self
.thread_nodes .thread_nodes
.iter() .iter_mut()
.position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false)) .find(|(_, n)| n.message.map(|n| n == envelope_hash).unwrap_or(false))
{ {
t_id = pos; t_id = *pos;
self.thread_nodes[pos].message = None; n.message = None;
} else { } else {
/* else it was deleted during a thread_rebuild or others */ /* else it was deleted during a thread_rebuild or others */
return; return;
@ -874,7 +914,7 @@ impl Threads {
let mut node_idx = t_id; let mut node_idx = t_id;
/* Trace path back to root ThreadNode */ /* Trace path back to root ThreadNode */
while let Some(p) = &self.thread_nodes[node_idx].parent { while let Some(p) = &self.thread_nodes[&node_idx].parent {
node_idx = *p; node_idx = *p;
} }
{ {
@ -895,7 +935,7 @@ impl Threads {
} }
} }
let mut root_set: Vec<usize> = self.tree.borrow().iter().map(|t| t.id).collect(); let mut root_set: Vec<ThreadHash> = self.tree.borrow().iter().map(|t| t.id).collect();
self.prune_empty_nodes(&mut root_set); self.prune_empty_nodes(&mut root_set);
self.tree.borrow_mut().retain(|t| root_set.contains(&t.id)); self.tree.borrow_mut().retain(|t| root_set.contains(&t.id));
} }
@ -922,7 +962,7 @@ impl Threads {
} }
self.create_root_set(collection); self.create_root_set(collection);
let mut root_set: Vec<usize> = self.tree.borrow().iter().map(|t| t.id).collect(); let mut root_set: Vec<ThreadHash> = self.tree.borrow().iter().map(|t| t.id).collect();
self.prune_empty_nodes(&mut root_set); self.prune_empty_nodes(&mut root_set);
let tree = self.tree.get_mut(); let tree = self.tree.get_mut();
tree.retain(|t| root_set.contains(&t.id)); tree.retain(|t| root_set.contains(&t.id));
@ -966,11 +1006,11 @@ impl Threads {
} }
/* Update thread tree information on envelope insertion */ /* Update thread tree information on envelope insertion */
fn rebuild_thread(&mut self, id: usize, collection: &Envelopes) { fn rebuild_thread(&mut self, id: ThreadHash, collection: &Envelopes) {
let mut node_idx = id; let mut node_idx = id;
let mut stack = Vec::with_capacity(32); let mut stack = Vec::with_capacity(32);
let no_parent: bool = if let Some(node) = self.thread_nodes.get(node_idx) { let no_parent: bool = if let Some(node) = self.thread_nodes.get(&node_idx) {
node.parent.is_none() node.parent.is_none()
} else { } else {
false false
@ -987,7 +1027,7 @@ impl Threads {
} }
/* Trace path back to root ThreadNode */ /* Trace path back to root ThreadNode */
while let Some(p) = &self.thread_nodes[node_idx].parent { while let Some(p) = &self.thread_nodes[&node_idx].parent {
node_idx = *p; node_idx = *p;
stack.push(node_idx); stack.push(node_idx);
} }
@ -1053,18 +1093,18 @@ impl Threads {
for t in tree.iter_mut() { for t in tree.iter_mut() {
t.children.sort_by(|a, b| match subsort { t.children.sort_by(|a, b| match subsort {
(SortField::Date, SortOrder::Desc) => { (SortField::Date, SortOrder::Desc) => {
let a = &self.thread_nodes[a.id]; let a = &self.thread_nodes[&a.id];
let b = &self.thread_nodes[b.id]; let b = &self.thread_nodes[&b.id];
b.date.cmp(&a.date) b.date.cmp(&a.date)
} }
(SortField::Date, SortOrder::Asc) => { (SortField::Date, SortOrder::Asc) => {
let a = &self.thread_nodes[a.id]; let a = &self.thread_nodes[&a.id];
let b = &self.thread_nodes[b.id]; let b = &self.thread_nodes[&b.id];
a.date.cmp(&b.date) a.date.cmp(&b.date)
} }
(SortField::Subject, SortOrder::Desc) => { (SortField::Subject, SortOrder::Desc) => {
let a = &self.thread_nodes[a.id].message(); let a = &self.thread_nodes[&a.id].message();
let b = &self.thread_nodes[b.id].message(); let b = &self.thread_nodes[&b.id].message();
if a.is_none() || b.is_none() { if a.is_none() || b.is_none() {
return Ordering::Equal; return Ordering::Equal;
@ -1074,8 +1114,8 @@ impl Threads {
ma.subject().cmp(&mb.subject()) ma.subject().cmp(&mb.subject())
} }
(SortField::Subject, SortOrder::Asc) => { (SortField::Subject, SortOrder::Asc) => {
let a = &self.thread_nodes[a.id].message(); let a = &self.thread_nodes[&a.id].message();
let b = &self.thread_nodes[b.id].message(); let b = &self.thread_nodes[&b.id].message();
if a.is_none() || b.is_none() { if a.is_none() || b.is_none() {
return Ordering::Equal; return Ordering::Equal;
@ -1092,18 +1132,18 @@ impl Threads {
let tree = &mut self.tree.borrow_mut(); let tree = &mut self.tree.borrow_mut();
tree.sort_by(|a, b| match sort { tree.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => { (SortField::Date, SortOrder::Desc) => {
let a = &self.thread_nodes[a.id]; let a = &self.thread_nodes[&a.id];
let b = &self.thread_nodes[b.id]; let b = &self.thread_nodes[&b.id];
b.date.cmp(&a.date) b.date.cmp(&a.date)
} }
(SortField::Date, SortOrder::Asc) => { (SortField::Date, SortOrder::Asc) => {
let a = &self.thread_nodes[a.id]; let a = &self.thread_nodes[&a.id];
let b = &self.thread_nodes[b.id]; let b = &self.thread_nodes[&b.id];
a.date.cmp(&b.date) a.date.cmp(&b.date)
} }
(SortField::Subject, SortOrder::Desc) => { (SortField::Subject, SortOrder::Desc) => {
let a = &self.thread_nodes[a.id].message(); let a = &self.thread_nodes[&a.id].message();
let b = &self.thread_nodes[b.id].message(); let b = &self.thread_nodes[&b.id].message();
if a.is_none() || b.is_none() { if a.is_none() || b.is_none() {
return Ordering::Equal; return Ordering::Equal;
@ -1113,8 +1153,8 @@ impl Threads {
ma.subject().cmp(&mb.subject()) ma.subject().cmp(&mb.subject())
} }
(SortField::Subject, SortOrder::Asc) => { (SortField::Subject, SortOrder::Asc) => {
let a = &self.thread_nodes[a.id].message(); let a = &self.thread_nodes[&a.id].message();
let b = &self.thread_nodes[b.id].message(); let b = &self.thread_nodes[&b.id].message();
if a.is_none() || b.is_none() { if a.is_none() || b.is_none() {
return Ordering::Equal; return Ordering::Equal;
@ -1142,12 +1182,12 @@ impl Threads {
} }
} }
pub fn thread_to_mail(&self, i: usize) -> EnvelopeHash { pub fn thread_to_mail(&self, i: ThreadHash) -> EnvelopeHash {
let thread = &self.thread_nodes[i]; let thread = &self.thread_nodes[&i];
thread.message().unwrap() thread.message().unwrap()
} }
pub fn thread_nodes(&self) -> &Vec<ThreadNode> { pub fn thread_nodes(&self) -> &FnvHashMap<ThreadHash, ThreadNode> {
&self.thread_nodes &self.thread_nodes
} }
@ -1159,7 +1199,7 @@ impl Threads {
self.tree.borrow().len() self.tree.borrow().len()
} }
pub fn root_set(&self, idx: usize) -> usize { pub fn root_set(&self, idx: usize) -> ThreadHash {
self.tree.borrow()[idx].id self.tree.borrow()[idx].id
} }
@ -1170,15 +1210,15 @@ impl Threads {
} }
} }
pub fn has_sibling(&self, i: usize) -> bool { pub fn has_sibling(&self, h: ThreadHash) -> bool {
if let Some(parent) = self[i].parent { if let Some(parent) = self[&h].parent {
let children = &self[parent].children; let children = &self[&parent].children;
if children.is_empty() { if children.is_empty() {
return false; return false;
} }
let pos = children let pos = children
.iter() .iter()
.position(|&x| x == i) .position(|&x| x == h)
.expect("Did not find node in parent!"); .expect("Did not find node in parent!");
pos != children.len() - 1 pos != children.len() - 1
} else { } else {
@ -1187,7 +1227,7 @@ impl Threads {
} }
fn link_envelope(&mut self, envelope: &mut Envelope) { fn link_envelope(&mut self, envelope: &mut Envelope) {
let t_idx: usize = { let t_idx: ThreadHash = {
let m_id = envelope.message_id().raw(); let m_id = envelope.message_id().raw();
/* t_idx: The index of this message's ThreadNode in thread_nodes /* t_idx: The index of this message's ThreadNode in thread_nodes
@ -1203,28 +1243,31 @@ impl Threads {
/* the already existing ThreadNote should be empty, since we're /* the already existing ThreadNote should be empty, since we're
* seeing this message for the first time. otherwise it's a * seeing this message for the first time. otherwise it's a
* duplicate. */ * duplicate. */
if self.thread_nodes[node_idx].message.is_some() { if self.thread_nodes[&node_idx].message.is_some() {
return; return;
} }
node_idx node_idx
} else { } else {
/* Create a new ThreadNode object holding this message */ /* Create a new ThreadNode object holding this message */
self.thread_nodes.push(ThreadNode { /* The new thread node's set is just itself */
let new_id = ThreadHash::new();
let node = ThreadNode {
message: Some(envelope.hash()), message: Some(envelope.hash()),
date: envelope.date(), date: envelope.date(),
thread_group: new_id,
..Default::default() ..Default::default()
}); };
/* The new thread node's set is just itself */ self.thread_nodes.insert(new_id, node);
let new_id = self.thread_nodes.len() - 1;
self.thread_nodes[new_id].thread_group = new_id;
self.message_ids.insert(m_id.to_vec(), new_id); self.message_ids.insert(m_id.to_vec(), new_id);
new_id new_id
} }
}; };
self.thread_nodes[t_idx].date = envelope.date(); self.thread_nodes.entry(t_idx).and_modify(|e| {
self.thread_nodes[t_idx].message = Some(envelope.hash()); e.date = envelope.date();
self.thread_nodes[t_idx].has_unseen |= !envelope.is_seen(); e.message = Some(envelope.hash());
e.has_unseen |= !envelope.is_seen();
});
envelope.set_thread(t_idx); envelope.set_thread(t_idx);
self.hash_set.insert(envelope.hash()); self.hash_set.insert(envelope.hash());
@ -1247,17 +1290,20 @@ impl Threads {
self.message_ids[r_id] self.message_ids[r_id]
} else { } else {
/* Create a new ThreadNode object holding this reference */ /* Create a new ThreadNode object holding this reference */
self.thread_nodes.push(ThreadNode { let new_id = ThreadHash::new();
..Default::default() self.thread_nodes.insert(
}); new_id,
let new_id = self.thread_nodes.len() - 1; ThreadNode {
self.thread_nodes[new_id].thread_group = new_id; thread_group: new_id,
..Default::default()
},
);
self.message_ids.insert(r_id.to_vec(), new_id); self.message_ids.insert(r_id.to_vec(), new_id);
new_id new_id
}; };
/* If they are already linked, don't change the existing links. */ /* If they are already linked, don't change the existing links. */
if self.thread_nodes[ref_ptr].has_parent() if self.thread_nodes[&ref_ptr].has_parent()
&& self.thread_nodes[ref_ptr].parent.unwrap() != parent_id && self.thread_nodes[&ref_ptr].parent.unwrap() != parent_id
{ {
ref_ptr = parent_id; ref_ptr = parent_id;
continue; continue;
@ -1283,10 +1329,10 @@ impl Threads {
} }
} }
impl Index<usize> for Threads { impl Index<&ThreadHash> for Threads {
type Output = ThreadNode; type Output = ThreadNode;
fn index(&self, index: usize) -> &ThreadNode { fn index(&self, index: &ThreadHash) -> &ThreadNode {
self.thread_nodes self.thread_nodes
.get(index) .get(index)
.expect("thread index out of bounds") .expect("thread index out of bounds")
@ -1295,20 +1341,20 @@ impl Index<usize> for Threads {
fn node_build( fn node_build(
tree: &mut ThreadTree, tree: &mut ThreadTree,
idx: usize, idx: ThreadHash,
thread_nodes: &mut Vec<ThreadNode>, thread_nodes: &mut FnvHashMap<ThreadHash, ThreadNode>,
indentation: usize, indentation: usize,
collection: &Envelopes, collection: &Envelopes,
) { ) {
if let Some(hash) = thread_nodes[idx].message { if let Some(hash) = thread_nodes[&idx].message {
if !collection.contains_key(&hash) { if !collection.contains_key(&hash) {
/* invalidate node */ /* invalidate node */
// thread_nodes[idx].message = None; // thread_nodes[&idx].message = None;
} else if let Some(parent_id) = thread_nodes[idx].parent { } else if let Some(parent_id) = thread_nodes[&idx].parent {
if let Some(parent_hash) = thread_nodes[parent_id].message { if let Some(parent_hash) = thread_nodes[&parent_id].message {
if !collection.contains_key(&parent_hash) { if !collection.contains_key(&parent_hash) {
/* invalidate node */ /* invalidate node */
// thread_nodes[parent_id].message = None; // thread_nodes[&parent_id].message = None;
} else { } else {
/* decide if the subject should be shown in the UI. /* decide if the subject should be shown in the UI.
* If parent subject is Foobar and reply is `Re: Foobar` * If parent subject is Foobar and reply is `Re: Foobar`
@ -1321,15 +1367,19 @@ fn node_build(
let mut parent_subject = parent_subject.to_mut().as_bytes(); let mut parent_subject = parent_subject.to_mut().as_bytes();
parent_subject.strip_prefixes(); parent_subject.strip_prefixes();
if subject == parent_subject { if subject == parent_subject {
thread_nodes[idx].show_subject = false; thread_nodes.entry(idx).and_modify(|e| {
e.show_subject = false;
});
} }
} }
} }
} }
} }
let indentation = if thread_nodes[idx].has_message() { let indentation = if thread_nodes[&idx].has_message() {
thread_nodes[idx].indentation = indentation; thread_nodes
.entry(idx)
.and_modify(|e| e.indentation = indentation);
indentation + 1 indentation + 1
} else if indentation > 0 { } else if indentation > 0 {
indentation indentation
@ -1337,28 +1387,35 @@ fn node_build(
indentation + 1 indentation + 1
}; };
let mut has_unseen = if let Some(msg) = thread_nodes[idx].message { let mut has_unseen = if let Some(msg) = thread_nodes[&idx].message {
!collection[&msg].is_seen() !collection[&msg].is_seen()
} else { } else {
false false
}; };
let mut child_vec: Vec<ThreadTree> = Vec::new(); let mut child_vec: Vec<ThreadTree> = Vec::new();
thread_nodes[idx].len = thread_nodes[idx].children.len(); thread_nodes
.entry(idx)
.and_modify(|e| e.len = e.children.len());
/* No child/parent relationship is mutated at any point and no nodes are added or removed. Only /* No child/parent relationship is mutated at any point and no nodes are added or removed. Only
* each node's fields change, so the following is safe. * each node's fields change, so the following is safe.
*/ */
let children = &thread_nodes[idx].children as *const Vec<usize>; let children = &thread_nodes[&idx].children as *const Vec<ThreadHash>;
for &c in unsafe { &(*children) } { for &c in unsafe { &(*children) } {
let mut new_tree = ThreadTree::new(c); let mut new_tree = ThreadTree::new(c);
node_build(&mut new_tree, c, thread_nodes, indentation, collection); node_build(&mut new_tree, c, thread_nodes, indentation, collection);
thread_nodes[idx].len += thread_nodes[c].len; let _c = (thread_nodes[&c].len, thread_nodes[&c].date);
thread_nodes[idx].date = cmp::max(thread_nodes[idx].date, thread_nodes[c].date); thread_nodes.entry(idx).and_modify(|e| {
e.len += _c.0;
e.date = cmp::max(e.date, _c.1);
});
has_unseen |= thread_nodes[c].has_unseen; has_unseen |= thread_nodes[&c].has_unseen;
child_vec.push(new_tree); child_vec.push(new_tree);
} }
tree.children = child_vec; tree.children = child_vec;
thread_nodes[idx].has_unseen = has_unseen; thread_nodes.entry(idx).and_modify(|e| {
e.has_unseen = has_unseen;
});
} }

View File

@ -24,6 +24,7 @@
use super::*; use super::*;
use melib::backends::Folder; use melib::backends::Folder;
use melib::backends::FolderHash; use melib::backends::FolderHash;
use melib::thread::ThreadHash;
pub mod listing; pub mod listing;
pub use listing::*; pub use listing::*;

View File

@ -127,14 +127,14 @@ impl Composer {
* msg: index of message we reply to in thread_nodes * msg: index of message we reply to in thread_nodes
* context: current context * context: current context
*/ */
pub fn edit(coordinates: (usize, usize, usize), msg: usize, context: &Context) -> Self { pub fn edit(coordinates: (usize, usize, usize), msg: ThreadHash, context: &Context) -> Self {
let mailbox = &context.accounts[coordinates.0][coordinates.1] let mailbox = &context.accounts[coordinates.0][coordinates.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_nodes = &threads.thread_nodes(); let thread_nodes = &threads.thread_nodes();
let mut ret = Composer::default(); let mut ret = Composer::default();
let message = &mailbox.collection[&thread_nodes[msg].message().unwrap()]; let message = &mailbox.collection[&thread_nodes[&msg].message().unwrap()];
let op = context.accounts[coordinates.0] let op = context.accounts[coordinates.0]
.backend .backend
.operation(message.hash(), mailbox.folder.hash()); .operation(message.hash(), mailbox.folder.hash());
@ -144,14 +144,18 @@ impl Composer {
ret.account_cursor = coordinates.0; ret.account_cursor = coordinates.0;
ret ret
} }
pub fn with_context(coordinates: (usize, usize, usize), msg: usize, context: &Context) -> Self { pub fn with_context(
coordinates: (usize, usize, usize),
msg: ThreadHash,
context: &Context,
) -> Self {
let mailbox = &context.accounts[coordinates.0][coordinates.1] let mailbox = &context.accounts[coordinates.0][coordinates.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_nodes = &threads.thread_nodes(); let thread_nodes = &threads.thread_nodes();
let mut ret = Composer::default(); let mut ret = Composer::default();
let p = &thread_nodes[msg]; let p = &thread_nodes[&msg];
let parent_message = &mailbox.collection[&p.message().unwrap()]; let parent_message = &mailbox.collection[&p.message().unwrap()];
let mut op = context.accounts[coordinates.0] let mut op = context.accounts[coordinates.0]
.backend .backend

View File

@ -207,15 +207,15 @@ impl MailboxView {
threads.sort_by(self.sort, self.subsort, &mailbox.collection); threads.sort_by(self.sort, self.subsort, &mailbox.collection);
for (idx, root_idx) in threads.root_iter().enumerate() { for (idx, root_idx) in threads.root_iter().enumerate() {
let thread_node = &threads.thread_nodes()[root_idx]; let thread_node = &threads.thread_nodes()[&root_idx];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
let mut iter_ptr = thread_node.children()[0]; let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[iter_ptr].message().is_none() { while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[iter_ptr].children()[0]; iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
} }
threads.thread_nodes()[iter_ptr].message().unwrap() threads.thread_nodes()[&iter_ptr].message().unwrap()
}; };
if !mailbox.collection.contains_key(&i) { if !mailbox.collection.contains_key(&i) {
debug!("key = {}", i); debug!("key = {}", i);
@ -251,15 +251,15 @@ impl MailboxView {
}; };
for ((idx, root_idx), strings) in threads.root_iter().enumerate().zip(rows) { for ((idx, root_idx), strings) in threads.root_iter().enumerate().zip(rows) {
let thread_node = &threads.thread_nodes()[root_idx]; let thread_node = &threads.thread_nodes()[&root_idx];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
let mut iter_ptr = thread_node.children()[0]; let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[iter_ptr].message().is_none() { while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[iter_ptr].children()[0]; iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
} }
threads.thread_nodes()[iter_ptr].message().unwrap() threads.thread_nodes()[&iter_ptr].message().unwrap()
}; };
if !mailbox.collection.contains_key(&i) { if !mailbox.collection.contains_key(&i) {
debug!("key = {}", i); debug!("key = {}", i);
@ -354,15 +354,15 @@ impl MailboxView {
} }
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_node = threads.root_set(idx); let thread_node = threads.root_set(idx);
let thread_node = &threads.thread_nodes()[thread_node]; let thread_node = &threads.thread_nodes()[&thread_node];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
let mut iter_ptr = thread_node.children()[0]; let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[iter_ptr].message().is_none() { while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[iter_ptr].children()[0]; iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
} }
threads.thread_nodes()[iter_ptr].message().unwrap() threads.thread_nodes()[&iter_ptr].message().unwrap()
}; };
let root_envelope: &Envelope = &mailbox.collection[&i]; let root_envelope: &Envelope = &mailbox.collection[&i];

View File

@ -151,12 +151,12 @@ impl ThreadListing {
/* Draw threaded view. */ /* Draw threaded view. */
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
threads.sort_by(self.sort, self.subsort, &mailbox.collection); threads.sort_by(self.sort, self.subsort, &mailbox.collection);
let thread_nodes: &Vec<ThreadNode> = &threads.thread_nodes(); let thread_nodes: &FnvHashMap<ThreadHash, ThreadNode> = &threads.thread_nodes();
let mut iter = threads.threads_iter().peekable(); let mut iter = threads.threads_iter().peekable();
/* This is just a desugared for loop so that we can use .peek() */ /* This is just a desugared for loop so that we can use .peek() */
let mut idx = 0; let mut idx = 0;
while let Some((indentation, i, has_sibling)) = iter.next() { while let Some((indentation, thread_hash, has_sibling)) = iter.next() {
let thread_node = &thread_nodes[i]; let thread_node = &thread_nodes[&thread_hash];
if indentation == 0 { if indentation == 0 {
thread_idx += 1; thread_idx += 1;
@ -181,7 +181,7 @@ impl ThreadListing {
envelope, envelope,
idx, idx,
indentation, indentation,
i, thread_hash,
threads, threads,
&indentations, &indentations,
has_sibling, has_sibling,
@ -362,13 +362,13 @@ impl ThreadListing {
envelope: &Envelope, envelope: &Envelope,
idx: usize, idx: usize,
indent: usize, indent: usize,
node_idx: usize, node_idx: ThreadHash,
threads: &Threads, threads: &Threads,
indentations: &[bool], indentations: &[bool],
has_sibling: bool, has_sibling: bool,
//op: Box<BackendOp>, //op: Box<BackendOp>,
) -> String { ) -> String {
let thread_node = &threads[node_idx]; let thread_node = &threads[&node_idx];
let has_parent = thread_node.has_parent(); let has_parent = thread_node.has_parent();
let show_subject = thread_node.show_subject(); let show_subject = thread_node.show_subject();

View File

@ -24,7 +24,7 @@ use std::cmp;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ThreadEntry { struct ThreadEntry {
index: (usize, usize, usize), index: (usize, ThreadHash, usize),
/// (indentation, thread_node index, line number in listing) /// (indentation, thread_node index, line number in listing)
indentation: usize, indentation: usize,
msg_hash: EnvelopeHash, msg_hash: EnvelopeHash,
@ -57,13 +57,13 @@ impl ThreadView {
const DESCRIPTION: &'static str = "thread view"; const DESCRIPTION: &'static str = "thread view";
/* /*
* coordinates: (account index, mailbox index, root set thread_node index) * coordinates: (account index, mailbox index, root set thread_node index)
* expanded_idx: optional position of expanded entry when we render the threadview. Default * expanded_hash: optional position of expanded entry when we render the threadview. Default
* expanded message is the last one. * expanded message is the last one.
* context: current context * context: current context
*/ */
pub fn new( pub fn new(
coordinates: (usize, usize, usize), coordinates: (usize, usize, usize),
expanded_idx: Option<usize>, expanded_hash: Option<ThreadHash>,
context: &Context, context: &Context,
) -> Self { ) -> Self {
let mut view = ThreadView { let mut view = ThreadView {
@ -79,7 +79,7 @@ impl ThreadView {
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
..Default::default() ..Default::default()
}; };
view.initiate(expanded_idx, context); view.initiate(expanded_hash, context);
view.new_cursor_pos = view.new_expanded_pos; view.new_cursor_pos = view.new_expanded_pos;
view view
} }
@ -102,8 +102,8 @@ impl ThreadView {
None None
}; };
let expanded_pos = self.expanded_pos; let expanded_hash = old_expanded_entry.as_ref().map(|e| e.index.1);
self.initiate(Some(expanded_pos), context); self.initiate(expanded_hash, context);
let mut old_cursor = 0; let mut old_cursor = 0;
let mut new_cursor = 0; let mut new_cursor = 0;
@ -144,7 +144,7 @@ impl ThreadView {
} }
self.set_dirty(); self.set_dirty();
} }
fn initiate(&mut self, expanded_idx: Option<usize>, context: &Context) { 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 */ /* 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] let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
.as_ref() .as_ref()
@ -153,23 +153,23 @@ impl ThreadView {
let thread_iter = threads.thread_iter(self.coordinates.2); let thread_iter = threads.thread_iter(self.coordinates.2);
self.entries.clear(); self.entries.clear();
for (line, (ind, idx)) in thread_iter.enumerate() { for (line, (ind, thread_hash)) in thread_iter.enumerate() {
let entry = if let Some(msg_hash) = threads.thread_nodes()[idx].message() { 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 = mailbox.collection[&msg_hash].is_seen();
self.make_entry((ind, idx, line), msg_hash, seen) self.make_entry((ind, thread_hash, line), msg_hash, seen)
} else { } else {
continue; continue;
}; };
self.entries.push(entry); self.entries.push(entry);
match expanded_idx { match expanded_hash {
Some(expanded_idx) if expanded_idx == idx => { Some(expanded_hash) if expanded_hash == thread_hash => {
self.new_expanded_pos = self.entries.len().saturating_sub(1); self.new_expanded_pos = self.entries.len().saturating_sub(1);
self.expanded_pos = self.new_expanded_pos + 1; self.expanded_pos = self.new_expanded_pos + 1;
} }
_ => {} _ => {}
} }
} }
if expanded_idx.is_none() { if expanded_hash.is_none() {
self.new_expanded_pos = self.entries.len().saturating_sub(1); self.new_expanded_pos = self.entries.len().saturating_sub(1);
self.expanded_pos = self.new_expanded_pos + 1; self.expanded_pos = self.new_expanded_pos + 1;
} }
@ -181,7 +181,7 @@ impl ThreadView {
Vec::with_capacity(self.entries.len()); Vec::with_capacity(self.entries.len());
for e in &mut self.entries { for e in &mut self.entries {
let envelope: &Envelope = &mailbox.collection[&e.msg_hash]; let envelope: &Envelope = &mailbox.collection[&e.msg_hash];
let thread_node = &threads.thread_nodes()[e.index.1]; let thread_node = &threads.thread_nodes()[&e.index.1];
let string = if thread_node.show_subject() { let string = if thread_node.show_subject() {
let subject = envelope.subject(); let subject = envelope.subject();
highlight_reply_subjects.push(Some(subject.grapheme_width())); highlight_reply_subjects.push(Some(subject.grapheme_width()));
@ -324,7 +324,7 @@ impl ThreadView {
fn make_entry( fn make_entry(
&mut self, &mut self,
i: (usize, usize, usize), i: (usize, ThreadHash, usize),
msg_hash: EnvelopeHash, msg_hash: EnvelopeHash,
seen: bool, seen: bool,
) -> ThreadEntry { ) -> ThreadEntry {
@ -535,11 +535,11 @@ impl ThreadView {
.as_ref() .as_ref()
.unwrap(); .unwrap();
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_node = &threads.thread_nodes()[threads.root_set(self.coordinates.2)]; let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
threads.thread_nodes()[thread_node.children()[0]] threads.thread_nodes()[&thread_node.children()[0]]
.message() .message()
.unwrap() .unwrap()
}; };
@ -615,15 +615,15 @@ impl ThreadView {
.as_ref() .as_ref()
.unwrap(); .unwrap();
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_node = &threads.thread_nodes()[threads.root_set(self.coordinates.2)]; let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
let mut iter_ptr = thread_node.children()[0]; let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[iter_ptr].message().is_none() { while threads.thread_nodes()[&iter_ptr].message().is_none() {
iter_ptr = threads.thread_nodes()[iter_ptr].children()[0]; iter_ptr = threads.thread_nodes()[&iter_ptr].children()[0];
} }
threads.thread_nodes()[iter_ptr].message().unwrap() threads.thread_nodes()[&iter_ptr].message().unwrap()
}; };
let envelope: &Envelope = &mailbox.collection[&i]; let envelope: &Envelope = &mailbox.collection[&i];
@ -835,11 +835,12 @@ impl Component for ThreadView {
.as_ref() .as_ref()
.unwrap(); .unwrap();
let threads = &mailbox.collection.threads; let threads = &mailbox.collection.threads;
let thread_node = &threads.thread_nodes()[threads.root_set(self.coordinates.2)]; let thread_node =
&threads.thread_nodes()[&threads.root_set(self.coordinates.2)];
let i = if let Some(i) = thread_node.message() { let i = if let Some(i) = thread_node.message() {
i i
} else { } else {
threads.thread_nodes()[thread_node.children()[0]] threads.thread_nodes()[&thread_node.children()[0]]
.message() .message()
.unwrap() .unwrap()
}; };
@ -890,8 +891,8 @@ impl Component for ThreadView {
} }
UIEvent::Input(Key::Ctrl('r')) => { UIEvent::Input(Key::Ctrl('r')) => {
self.reversed = !self.reversed; self.reversed = !self.reversed;
let expanded_pos = self.expanded_pos; let expanded_hash = self.entries[self.expanded_pos].index.1;
self.initiate(Some(expanded_pos), context); self.initiate(Some(expanded_hash), context);
self.initiated = false; self.initiated = false;
self.dirty = true; self.dirty = true;
return true; return true;

View File

@ -92,6 +92,17 @@ impl Drop for Account {
let writer = io::BufWriter::new(f); let writer = io::BufWriter::new(f);
serde_json::to_writer(writer, &self.address_book).unwrap(); serde_json::to_writer(writer, &self.address_book).unwrap();
}; };
if let Ok(data) = data_dir.place_data_file("mailbox") {
/* place result in cache directory */
let f = match fs::File::create(data) {
Ok(f) => f,
Err(e) => {
panic!("{}", e);
}
};
let writer = io::BufWriter::new(f);
bincode::serialize_into(writer, &self.folders).unwrap();
};
} }
} }
@ -125,7 +136,7 @@ impl<'a> Iterator for MailboxIterator<'a> {
} }
} }
#[derive(Debug, Default)] #[derive(Serialize, Debug, Default)]
struct FolderNode { struct FolderNode {
hash: FolderHash, hash: FolderHash,
kids: Vec<FolderNode>, kids: Vec<FolderNode>,

View File

@ -20,7 +20,7 @@
*/ */
/// Settings for the mailer function. /// Settings for the mailer function.
#[derive(Debug, Deserialize, Clone, Default)] #[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct MailerSettings { pub struct MailerSettings {
/// A command to pipe new emails to /// A command to pipe new emails to
/// Required /// Required

View File

@ -21,7 +21,7 @@
use super::default_vals::*; use super::default_vals::*;
/// Settings for the pager function. /// Settings for the pager function.
#[derive(Debug, Deserialize, Clone, Default)] #[derive(Debug, Deserialize, Clone, Default, Serialize)]
pub struct PagerSettings { pub struct PagerSettings {
/// Number of context lines when going to next page. /// Number of context lines when going to next page.
/// Default: 0 /// Default: 0

View File

@ -25,6 +25,7 @@
use components::Component; use components::Component;
pub use melib::mailbox::{SortField, SortOrder}; pub use melib::mailbox::{SortField, SortOrder};
use melib::thread::ThreadHash;
extern crate uuid; extern crate uuid;
use uuid::Uuid; use uuid::Uuid;
@ -40,9 +41,9 @@ pub enum ListingAction {
pub enum TabAction { pub enum TabAction {
TabOpen(Option<Box<Component>>), TabOpen(Option<Box<Component>>),
NewDraft(usize), NewDraft(usize),
Reply((usize, usize, usize), usize), // thread coordinates (account, mailbox, root_set idx) and message idx Reply((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash
Close, Close,
Edit((usize, usize, usize), usize), // thread coordinates (account, mailbox, root_set idx) and message idx Edit((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash
Kill(Uuid), Kill(Uuid),
} }