WIP
parent
3e9d137310
commit
6003bdd28c
|
@ -111,7 +111,7 @@ impl<'a> BackendOp for MaildirOp {
|
||||||
'R' => flag |= Flag::REPLIED,
|
'R' => flag |= Flag::REPLIED,
|
||||||
'S' => flag |= Flag::SEEN,
|
'S' => flag |= Flag::SEEN,
|
||||||
'T' => flag |= Flag::TRASHED,
|
'T' => flag |= Flag::TRASHED,
|
||||||
_ => panic!(),
|
_ => eprintln!("DEBUG: in fetch_flags, path is {}", path),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
use super::*;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
extern crate fnv;
|
||||||
|
use self::fnv::FnvHashMap;
|
||||||
|
|
||||||
|
/// `Mailbox` represents a folder of mail.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Collection {
|
||||||
|
pub envelopes: FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
|
date_index: BTreeMap<UnixTimestamp, EnvelopeHash>,
|
||||||
|
subject_index: Option<BTreeMap<String, EnvelopeHash>>,
|
||||||
|
pub threads: Threads,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Collection {
|
||||||
|
pub fn new(vec: Vec<Envelope>) -> 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);
|
||||||
|
}
|
||||||
|
let date_index = BTreeMap::new();
|
||||||
|
let subject_index = None;
|
||||||
|
|
||||||
|
let threads = Threads::new(&mut envelopes); // sent_folder);
|
||||||
|
Collection {
|
||||||
|
envelopes,
|
||||||
|
date_index,
|
||||||
|
subject_index,
|
||||||
|
threads,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.envelopes.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.envelopes.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Collection {
|
||||||
|
type Target = FnvHashMap<EnvelopeHash, Envelope>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &FnvHashMap<EnvelopeHash, Envelope> {
|
||||||
|
&self.envelopes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Collection {
|
||||||
|
fn deref_mut(&mut self) -> &mut FnvHashMap<EnvelopeHash, Envelope> {
|
||||||
|
&mut self.envelopes
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,10 @@ pub mod backends;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use mailbox::backends::{folder_default, Folder};
|
use mailbox::backends::{folder_default, Folder};
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub use mailbox::thread::{build_threads, Container, SortField, SortOrder, Threads};
|
pub use mailbox::thread::{SortField, SortOrder, ThreadNode, Threads};
|
||||||
|
|
||||||
|
mod collection;
|
||||||
|
pub use self::collection::*;
|
||||||
|
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
|
@ -40,8 +43,7 @@ use std::option::Option;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Mailbox {
|
pub struct Mailbox {
|
||||||
pub folder: Folder,
|
pub folder: Folder,
|
||||||
pub collection: Vec<Envelope>,
|
pub collection: Collection,
|
||||||
pub threads: Threads,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Mailbox {
|
impl Clone for Mailbox {
|
||||||
|
@ -49,7 +51,6 @@ impl Clone for Mailbox {
|
||||||
Mailbox {
|
Mailbox {
|
||||||
folder: self.folder.clone(),
|
folder: self.folder.clone(),
|
||||||
collection: self.collection.clone(),
|
collection: self.collection.clone(),
|
||||||
threads: self.threads.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,8 +58,7 @@ impl Default for Mailbox {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Mailbox {
|
Mailbox {
|
||||||
folder: folder_default(),
|
folder: folder_default(),
|
||||||
collection: Vec::default(),
|
collection: Collection::default(),
|
||||||
threads: Threads::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,15 +67,14 @@ impl Mailbox {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
folder: &Folder,
|
folder: &Folder,
|
||||||
sent_folder: &Option<Result<Mailbox>>,
|
sent_folder: &Option<Result<Mailbox>>,
|
||||||
collection: Result<Vec<Envelope>>,
|
envelopes: Result<Vec<Envelope>>,
|
||||||
) -> Result<Mailbox> {
|
) -> Result<Mailbox> {
|
||||||
let mut collection: Vec<Envelope> = collection?;
|
let mut envelopes: Vec<Envelope> = envelopes?;
|
||||||
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
envelopes.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||||
let threads = build_threads(&mut collection, sent_folder);
|
let collection = Collection::new(envelopes);
|
||||||
Ok(Mailbox {
|
Ok(Mailbox {
|
||||||
folder: (*folder).clone(),
|
folder: (*folder).clone(),
|
||||||
collection,
|
collection,
|
||||||
threads,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
|
@ -84,41 +83,36 @@ impl Mailbox {
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.collection.len()
|
self.collection.len()
|
||||||
}
|
}
|
||||||
pub fn threaded_mail(&self, i: usize) -> usize {
|
pub fn threaded_mail(&self, i: usize) -> EnvelopeHash {
|
||||||
self.threads.thread_to_mail(i)
|
self.collection.threads.thread_to_mail(i)
|
||||||
}
|
}
|
||||||
pub fn mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) {
|
||||||
let x = &mut self.collection.as_mut_slice()[i];
|
let thread;
|
||||||
let thread = self.threads[x.thread()];
|
{
|
||||||
(x, 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, i: usize) -> &Container {
|
pub fn thread(&self, i: usize) -> &ThreadNode {
|
||||||
&self.threads[i]
|
&self.collection.threads.thread_nodes()[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
|
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
|
||||||
if let Some(i) = self.collection.iter().position(|e| e.hash() == old_hash) {
|
self.collection.remove(&old_hash);
|
||||||
self.collection[i] = envelope;
|
self.collection.insert(envelope.hash(), envelope);
|
||||||
} else {
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
|
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
|
||||||
self.collection.push(envelope);
|
let hash = envelope.hash();
|
||||||
|
self.collection.insert(hash, envelope);
|
||||||
// TODO: Update threads.
|
// TODO: Update threads.
|
||||||
eprintln!("Inserted envelope");
|
eprintln!("Inserted envelope");
|
||||||
&self.collection[self.collection.len() - 1]
|
&self.collection[&hash]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
|
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
|
||||||
if let Some(i) = self
|
self.collection.remove(&envelope_hash);
|
||||||
.collection
|
|
||||||
.iter()
|
|
||||||
.position(|e| e.hash() == envelope_hash)
|
|
||||||
{
|
|
||||||
self.collection.remove(i);
|
|
||||||
}
|
|
||||||
// eprintln!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
|
// eprintln!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,15 +23,11 @@
|
||||||
* Threading algorithm
|
* Threading algorithm
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use error::Result;
|
|
||||||
use mailbox::email::*;
|
use mailbox::email::*;
|
||||||
use mailbox::Mailbox;
|
|
||||||
|
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
use self::fnv::FnvHashMap;
|
use self::fnv::FnvHashMap;
|
||||||
use std::borrow::Cow;
|
use std::cell::RefCell;
|
||||||
use std::cell::{Ref, RefCell};
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::ops::Index;
|
use std::ops::Index;
|
||||||
use std::result::Result as StdResult;
|
use std::result::Result as StdResult;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -82,244 +78,175 @@ impl FromStr for SortOrder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `Container` struct is needed to describe the thread tree forest during creation of threads.
|
#[derive(Clone, Debug)]
|
||||||
/// Because of Rust's memory model, we store indexes of Envelopes inside a collection instead of
|
pub struct ThreadNode {
|
||||||
/// references and every reference is passed through the `Container` owner (a `Vec<Container>`).
|
message: Option<EnvelopeHash>,
|
||||||
//
|
|
||||||
/// `message` refers to a `Envelope` entry in a `Vec`. If it's empty, the `Container` is
|
|
||||||
/// nonexistent in our `Mailbox` but we know it exists (for example we have a copy
|
|
||||||
/// of a reply to a mail but we don't have its copy.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct Container {
|
|
||||||
id: usize,
|
|
||||||
message: Option<usize>,
|
|
||||||
parent: Option<usize>,
|
parent: Option<usize>,
|
||||||
first_child: Option<usize>,
|
children: Vec<usize>,
|
||||||
next_sibling: Option<usize>,
|
|
||||||
date: UnixTimestamp,
|
date: UnixTimestamp,
|
||||||
indentation: usize,
|
indentation: usize,
|
||||||
show_subject: bool,
|
show_subject: bool,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Container {
|
|
||||||
fn default() -> Container {
|
|
||||||
Container {
|
|
||||||
id: 0,
|
|
||||||
message: None,
|
|
||||||
parent: None,
|
|
||||||
first_child: None,
|
|
||||||
next_sibling: None,
|
|
||||||
date: UnixTimestamp::default(),
|
|
||||||
indentation: 0,
|
|
||||||
show_subject: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct ContainerTree {
|
|
||||||
id: usize,
|
|
||||||
children: Option<Vec<ContainerTree>>,
|
|
||||||
len: usize,
|
len: usize,
|
||||||
has_unseen: bool,
|
has_unseen: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContainerTree {
|
impl Default for ThreadNode {
|
||||||
fn new(id: usize) -> Self {
|
fn default() -> ThreadNode {
|
||||||
ContainerTree {
|
ThreadNode {
|
||||||
id,
|
message: None,
|
||||||
children: None,
|
parent: None,
|
||||||
len: 1,
|
children: Vec::new(),
|
||||||
|
date: UnixTimestamp::default(),
|
||||||
|
indentation: 0,
|
||||||
|
show_subject: true,
|
||||||
|
|
||||||
|
len: 0,
|
||||||
has_unseen: false,
|
has_unseen: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ThreadNode {
|
||||||
|
pub fn show_subject(&self) -> bool {
|
||||||
|
self.show_subject
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_unseen(&self) -> bool {
|
||||||
|
self.has_unseen
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_descendant(&self, thread_nodes: &[ThreadNode], other: &ThreadNode) -> bool {
|
||||||
|
if self == other {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for v in &self.children {
|
||||||
|
if thread_nodes[*v].is_descendant(thread_nodes, other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pub fn message(&self) -> Option<EnvelopeHash> {
|
||||||
|
self.message
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_message(&self) -> bool {
|
||||||
|
self.message.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent(&self) -> Option<usize> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_parent(&self) -> bool {
|
||||||
|
self.parent.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self) -> &[usize] {
|
||||||
|
&self.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct Threads {
|
pub struct Threads {
|
||||||
containers: Vec<Container>,
|
thread_nodes: Vec<ThreadNode>,
|
||||||
threaded_collection: Vec<usize>,
|
|
||||||
root_set: Vec<usize>,
|
root_set: Vec<usize>,
|
||||||
tree: RefCell<Vec<ContainerTree>>,
|
|
||||||
|
message_ids: FnvHashMap<String, EnvelopeHash>,
|
||||||
sort: RefCell<(SortField, SortOrder)>,
|
sort: RefCell<(SortField, SortOrder)>,
|
||||||
subsort: RefCell<(SortField, SortOrder)>,
|
subsort: RefCell<(SortField, SortOrder)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThreadIterator<'a> {
|
impl PartialEq for ThreadNode {
|
||||||
pos: usize,
|
fn eq(&self, other: &ThreadNode) -> bool {
|
||||||
stack: Vec<usize>,
|
match (self.message, other.message) {
|
||||||
tree: Ref<'a, Vec<ContainerTree>>,
|
(Some(s), Some(o)) => s == o,
|
||||||
}
|
_ => false,
|
||||||
impl<'a> Iterator for ThreadIterator<'a> {
|
|
||||||
type Item = usize;
|
|
||||||
fn next(&mut self) -> Option<usize> {
|
|
||||||
{
|
|
||||||
let mut tree = &(*self.tree);
|
|
||||||
for i in &self.stack {
|
|
||||||
tree = tree[*i].children.as_ref().unwrap();
|
|
||||||
}
|
|
||||||
if self.pos == tree.len() {
|
|
||||||
if self.stack.is_empty() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.pos = self.stack.pop().unwrap() + 1;
|
|
||||||
} else {
|
|
||||||
debug_assert!(self.pos < tree.len());
|
|
||||||
let ret = tree[self.pos].id;
|
|
||||||
if tree[self.pos].children.is_some() {
|
|
||||||
self.stack.push(self.pos);
|
|
||||||
self.pos = 0;
|
|
||||||
return Some(ret);
|
|
||||||
}
|
|
||||||
self.pos += 1;
|
|
||||||
return Some(ret);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> IntoIterator for &'a Threads {
|
|
||||||
type Item = usize;
|
|
||||||
type IntoIter = ThreadIterator<'a>;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
ThreadIterator {
|
|
||||||
pos: 0,
|
|
||||||
stack: Vec::new(),
|
|
||||||
tree: self.tree.borrow(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RootIterator<'a> {
|
|
||||||
pos: usize,
|
|
||||||
tree: Ref<'a, Vec<ContainerTree>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for RootIterator<'a> {
|
|
||||||
type Item = (usize, usize, bool);
|
|
||||||
fn next(&mut self) -> Option<(usize, usize, bool)> {
|
|
||||||
if self.pos == self.tree.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let node = &self.tree[self.pos];
|
|
||||||
self.pos += 1;
|
|
||||||
Some((node.id, node.len, node.has_unseen))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Threads {
|
impl Threads {
|
||||||
pub fn root_len(&self) -> usize {
|
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
||||||
self.tree.borrow().len()
|
/* To reconstruct thread information from the mails we need: */
|
||||||
}
|
|
||||||
pub fn root_set(&self) -> &Vec<usize> {
|
|
||||||
&self.root_set
|
|
||||||
}
|
|
||||||
pub fn root_set_iter(&self) -> RootIterator {
|
|
||||||
RootIterator {
|
|
||||||
pos: 0,
|
|
||||||
tree: self.tree.borrow(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn thread_to_mail(&self, i: usize) -> usize {
|
|
||||||
let thread = self.containers[self.threaded_collection[i]];
|
|
||||||
thread.message().unwrap()
|
|
||||||
}
|
|
||||||
pub fn threaded_collection(&self) -> &Vec<usize> {
|
|
||||||
&self.threaded_collection
|
|
||||||
}
|
|
||||||
pub fn containers(&self) -> &Vec<Container> {
|
|
||||||
&self.containers
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner_subsort_by(&self, subsort: (SortField, SortOrder), collection: &[Envelope]) {
|
/* a vector to hold thread members */
|
||||||
let tree = &mut self.tree.borrow_mut();
|
let mut thread_nodes: Vec<ThreadNode> =
|
||||||
let containers = &self.containers;
|
Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
||||||
for mut t in tree.iter_mut() {
|
/* A hash table of Message IDs */
|
||||||
if let Some(ref mut c) = t.children {
|
let mut message_ids: FnvHashMap<String, usize> =
|
||||||
c.sort_by(|a, b| match subsort {
|
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
/* Add each message to message_ids and threads, and link them together according to the
|
||||||
let a = containers[a.id];
|
* References / In-Reply-To headers */
|
||||||
let b = containers[b.id];
|
link_threads(&mut thread_nodes, &mut message_ids, collection);
|
||||||
b.date.cmp(&a.date)
|
|
||||||
}
|
|
||||||
(SortField::Date, SortOrder::Asc) => {
|
|
||||||
let a = containers[a.id];
|
|
||||||
let b = containers[b.id];
|
|
||||||
a.date.cmp(&b.date)
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Desc) => {
|
|
||||||
let a = containers[a.id].message();
|
|
||||||
let b = containers[b.id].message();
|
|
||||||
|
|
||||||
if a.is_none() || b.is_none() {
|
/* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that have
|
||||||
return Ordering::Equal;
|
* no parents. These are the root messages of each thread */
|
||||||
}
|
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
||||||
let ma = &collection[a.unwrap()];
|
'root_set: for v in message_ids.values() {
|
||||||
let mb = &collection[b.unwrap()];
|
if thread_nodes[*v].parent.is_none() {
|
||||||
ma.subject().cmp(&mb.subject())
|
if !thread_nodes[*v].has_message() && thread_nodes[*v].children.len() == 1 {
|
||||||
}
|
/* Do not promote the children if doing so would promote them to the root set
|
||||||
(SortField::Subject, SortOrder::Asc) => {
|
* -- unless there is only one child, in which case, do. */
|
||||||
let a = containers[a.id].message();
|
root_set.push(*v);
|
||||||
let b = containers[b.id].message();
|
|
||||||
|
|
||||||
if a.is_none() || b.is_none() {
|
continue 'root_set;
|
||||||
return Ordering::Equal;
|
}
|
||||||
}
|
root_set.push(*v);
|
||||||
let ma = &collection[a.unwrap()];
|
|
||||||
let mb = &collection[b.unwrap()];
|
|
||||||
mb.subject().cmp(&ma.subject())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
root_set.sort_by(|a, b| thread_nodes[*b].date.cmp(&thread_nodes[*a].date));
|
||||||
|
|
||||||
|
let mut t = Threads {
|
||||||
|
thread_nodes,
|
||||||
|
root_set,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
t.build_collection(&collection);
|
||||||
|
t
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &[Envelope]) {
|
fn build_collection(&mut self, collection: &FnvHashMap<EnvelopeHash, Envelope>) {
|
||||||
let tree = &mut self.tree.borrow_mut();
|
for i in &self.root_set {
|
||||||
let containers = &self.containers;
|
node_build(
|
||||||
tree.sort_by(|a, b| match sort {
|
*i,
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
&mut self.thread_nodes,
|
||||||
let a = containers[a.id];
|
0, /* indentation */
|
||||||
let b = containers[b.id];
|
*i, /* root_subject_idx */
|
||||||
b.date.cmp(&a.date)
|
collection,
|
||||||
}
|
);
|
||||||
(SortField::Date, SortOrder::Asc) => {
|
}
|
||||||
let a = containers[a.id];
|
self.inner_sort_by(*self.sort.borrow(), collection);
|
||||||
let b = containers[b.id];
|
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
||||||
a.date.cmp(&b.date)
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Desc) => {
|
|
||||||
let a = containers[a.id].message();
|
|
||||||
let b = containers[b.id].message();
|
|
||||||
|
|
||||||
if a.is_none() || b.is_none() {
|
|
||||||
return Ordering::Equal;
|
|
||||||
}
|
|
||||||
let ma = &collection[a.unwrap()];
|
|
||||||
let mb = &collection[b.unwrap()];
|
|
||||||
ma.subject().cmp(&mb.subject())
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Asc) => {
|
|
||||||
let a = containers[a.id].message();
|
|
||||||
let b = containers[b.id].message();
|
|
||||||
|
|
||||||
if a.is_none() || b.is_none() {
|
|
||||||
return Ordering::Equal;
|
|
||||||
}
|
|
||||||
let ma = &collection[a.unwrap()];
|
|
||||||
let mb = &collection[b.unwrap()];
|
|
||||||
mb.subject().cmp(&ma.subject())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inner_subsort_by(
|
||||||
|
&self,
|
||||||
|
subsort: (SortField, SortOrder),
|
||||||
|
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner_sort_by(
|
||||||
|
&self,
|
||||||
|
sort: (SortField, SortOrder),
|
||||||
|
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sort_by(
|
pub fn sort_by(
|
||||||
&self,
|
&self,
|
||||||
sort: (SortField, SortOrder),
|
sort: (SortField, SortOrder),
|
||||||
subsort: (SortField, SortOrder),
|
subsort: (SortField, SortOrder),
|
||||||
collection: &[Envelope],
|
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
) {
|
) {
|
||||||
|
/*
|
||||||
if *self.sort.borrow() != sort {
|
if *self.sort.borrow() != sort {
|
||||||
self.inner_sort_by(sort, collection);
|
self.inner_sort_by(sort, collection);
|
||||||
*self.sort.borrow_mut() = sort;
|
*self.sort.borrow_mut() = sort;
|
||||||
|
@ -328,422 +255,193 @@ impl Threads {
|
||||||
self.inner_subsort_by(subsort, collection);
|
self.inner_subsort_by(subsort, collection);
|
||||||
*self.subsort.borrow_mut() = subsort;
|
*self.subsort.borrow_mut() = subsort;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_collection(&mut self, collection: &[Envelope]) {
|
pub fn thread_to_mail(&self, i: usize) -> EnvelopeHash {
|
||||||
fn build_threaded(
|
let thread = &self.thread_nodes[self.root_set[i]];
|
||||||
tree: &mut ContainerTree,
|
thread.message().unwrap()
|
||||||
containers: &mut Vec<Container>,
|
}
|
||||||
indentation: usize,
|
|
||||||
threaded: &mut Vec<usize>,
|
pub fn thread_nodes(&self) -> &Vec<ThreadNode> {
|
||||||
i: usize,
|
&self.thread_nodes
|
||||||
root_subject_idx: usize,
|
}
|
||||||
collection: &[Envelope],
|
|
||||||
) {
|
pub fn root_set(&self) -> &Vec<usize> {
|
||||||
let thread = containers[i];
|
&self.root_set
|
||||||
if let Some(msg_idx) = containers[root_subject_idx].message() {
|
}
|
||||||
let root_subject = collection[msg_idx].subject();
|
|
||||||
/* If the Container has no Message, but does have children, remove this container but
|
pub fn has_sibling(&self, i: usize) -> bool {
|
||||||
* promote its children to this level (that is, splice them in to the current child
|
if let Some(parent) = self[i].parent {
|
||||||
* list.) */
|
self[parent].children.len() > 1
|
||||||
if indentation > 0 && thread.has_message() {
|
} else {
|
||||||
let subject = collection[thread.message().unwrap()].subject();
|
false
|
||||||
tree.has_unseen = !collection[thread.message().unwrap()].is_seen();
|
}
|
||||||
if subject == root_subject
|
}
|
||||||
|| subject.starts_with("Re: ")
|
}
|
||||||
&& subject.as_ref().ends_with(root_subject.as_ref())
|
|
||||||
{
|
fn link_threads(
|
||||||
containers[i].set_show_subject(false);
|
thread_nodes: &mut Vec<ThreadNode>,
|
||||||
|
message_ids: &mut FnvHashMap<String, usize>,
|
||||||
|
collection: &mut FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
|
) {
|
||||||
|
for (k, v) in collection.iter_mut() {
|
||||||
|
let m_id = v.message_id_raw().to_string();
|
||||||
|
|
||||||
|
/* The index of this message's ThreadNode in thread_nodes */
|
||||||
|
|
||||||
|
let t_idx: usize = if message_ids.get(&m_id).is_some() {
|
||||||
|
let node_idx = message_ids[&m_id];
|
||||||
|
/* the already existing ThreadNote should be empty, since we're
|
||||||
|
* seeing this message for the first time. otherwise it's a
|
||||||
|
* duplicate. */
|
||||||
|
if thread_nodes[node_idx].message.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
thread_nodes[node_idx].date = v.date();
|
||||||
|
thread_nodes[node_idx].message = Some(*k);
|
||||||
|
v.set_thread(node_idx);
|
||||||
|
|
||||||
|
node_idx
|
||||||
|
} else {
|
||||||
|
/* Create a new ThreadNode object holding this message */
|
||||||
|
thread_nodes.push(ThreadNode {
|
||||||
|
message: Some(*k),
|
||||||
|
date: v.date(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
v.set_thread(thread_nodes.len() - 1);
|
||||||
|
message_ids.insert(m_id, thread_nodes.len() - 1);
|
||||||
|
thread_nodes.len() - 1
|
||||||
|
};
|
||||||
|
|
||||||
|
/* For each element in the message's References field:
|
||||||
|
*
|
||||||
|
* Find a ThreadNode object for the given Message-ID:
|
||||||
|
* If there's one in message_ids use that;
|
||||||
|
* Otherwise, make (and index) one with a null Message
|
||||||
|
*
|
||||||
|
* Link the References field's ThreadNode together in the order implied
|
||||||
|
* by the References header.
|
||||||
|
* If they are already linked, don't change the existing links.
|
||||||
|
* Do not add a link if adding that link would introduce a loop: that
|
||||||
|
* is, before asserting A->B, search down the children of B to see if A
|
||||||
|
* is reachable, and also search down the children of A to see if B is
|
||||||
|
* reachable. If either is already reachable as a child of the other,
|
||||||
|
* don't add the link.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* The index of the reference we are currently examining, start from current message */
|
||||||
|
let mut ref_ptr = t_idx;
|
||||||
|
|
||||||
|
for &refn in v.references().iter().rev() {
|
||||||
|
let r_id = String::from_utf8_lossy(refn.raw()).into();
|
||||||
|
let parent_id = if message_ids.contains_key(&r_id) {
|
||||||
|
let parent_id = message_ids[&r_id];
|
||||||
|
if !(thread_nodes[parent_id].is_descendant(thread_nodes, &thread_nodes[ref_ptr])
|
||||||
|
|| thread_nodes[ref_ptr].is_descendant(thread_nodes, &thread_nodes[parent_id]))
|
||||||
|
{
|
||||||
|
thread_nodes[ref_ptr].parent = Some(parent_id);
|
||||||
|
if thread_nodes[parent_id].children.is_empty() {
|
||||||
|
thread_nodes[parent_id].children.push(ref_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (left, right) = thread_nodes.split_at_mut(parent_id);
|
||||||
|
let (parent, right) = right.split_first_mut().unwrap();
|
||||||
|
for &c in &parent.children {
|
||||||
|
if c > parent_id {
|
||||||
|
right[c - parent_id - 1].parent = Some(parent_id);
|
||||||
|
} else {
|
||||||
|
left[c].parent = Some(parent_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
parent_id
|
||||||
if thread.has_parent() && !containers[thread.parent().unwrap()].has_message() {
|
|
||||||
containers[i].parent = None;
|
|
||||||
}
|
|
||||||
let indentation = if thread.has_message() {
|
|
||||||
containers[i].set_indentation(indentation);
|
|
||||||
if !threaded.contains(&i) {
|
|
||||||
threaded.push(i);
|
|
||||||
}
|
|
||||||
indentation + 1
|
|
||||||
} else if indentation > 0 {
|
|
||||||
indentation
|
|
||||||
} else {
|
} else {
|
||||||
indentation + 1
|
/* Create a new ThreadNode object holding this reference */
|
||||||
|
thread_nodes.push(ThreadNode {
|
||||||
|
message: None,
|
||||||
|
children: vec![ref_ptr; 1],
|
||||||
|
date: v.date(),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
if thread_nodes[ref_ptr].parent.is_none() {
|
||||||
|
thread_nodes[ref_ptr].parent = Some(thread_nodes.len() - 1);
|
||||||
|
}
|
||||||
|
/* Can't avoid copy here since we have different lifetimes */
|
||||||
|
message_ids.insert(r_id, thread_nodes.len() - 1);
|
||||||
|
thread_nodes.len() - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
if thread.has_children() {
|
/* Update thread's date */
|
||||||
let mut child_vec = Vec::new();
|
|
||||||
|
|
||||||
let mut fc = thread.first_child().unwrap();
|
let mut parent_iter = parent_id;
|
||||||
|
'date: loop {
|
||||||
loop {
|
let p: &mut ThreadNode = &mut thread_nodes[parent_iter];
|
||||||
let mut new_child_tree = ContainerTree::new(fc);
|
if p.date < v.date() {
|
||||||
build_threaded(
|
p.date = v.date();
|
||||||
&mut new_child_tree,
|
}
|
||||||
containers,
|
if let Some(p) = p.parent {
|
||||||
indentation,
|
parent_iter = p;
|
||||||
threaded,
|
} else {
|
||||||
fc,
|
break 'date;
|
||||||
i,
|
|
||||||
collection,
|
|
||||||
);
|
|
||||||
tree.has_unseen |= new_child_tree.has_unseen;
|
|
||||||
child_vec.push(new_child_tree);
|
|
||||||
let thread_ = containers[fc];
|
|
||||||
if !thread_.has_sibling() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fc = thread_.next_sibling().unwrap();
|
|
||||||
}
|
}
|
||||||
tree.len = child_vec.iter().map(|c| c.len).sum();
|
|
||||||
tree.children = Some(child_vec);
|
|
||||||
}
|
}
|
||||||
|
ref_ptr = parent_id;
|
||||||
}
|
}
|
||||||
let mut tree = Vec::new();
|
|
||||||
for i in &self.root_set {
|
|
||||||
let mut tree_node = ContainerTree::new(*i);
|
|
||||||
build_threaded(
|
|
||||||
&mut tree_node,
|
|
||||||
&mut self.containers,
|
|
||||||
0,
|
|
||||||
&mut self.threaded_collection,
|
|
||||||
*i,
|
|
||||||
*i,
|
|
||||||
collection,
|
|
||||||
);
|
|
||||||
tree.push(tree_node);
|
|
||||||
}
|
|
||||||
self.tree.replace(tree);
|
|
||||||
self.inner_sort_by(*self.sort.borrow(), collection);
|
|
||||||
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<usize> for Threads {
|
impl Index<usize> for Threads {
|
||||||
type Output = Container;
|
type Output = ThreadNode;
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &Container {
|
fn index(&self, index: usize) -> &ThreadNode {
|
||||||
self.containers
|
self.thread_nodes
|
||||||
.get(index)
|
.get(index)
|
||||||
.expect("thread index out of bounds")
|
.expect("thread index out of bounds")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn node_build(
|
||||||
impl Container {
|
idx: usize,
|
||||||
pub fn date(&self) -> UnixTimestamp {
|
thread_nodes: &mut Vec<ThreadNode>,
|
||||||
self.date
|
indentation: usize,
|
||||||
}
|
root_subject_idx: usize,
|
||||||
pub fn message(&self) -> Option<usize> {
|
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
self.message
|
) {
|
||||||
}
|
if let Some(msg_idx) = thread_nodes[root_subject_idx].message().as_ref() {
|
||||||
pub fn parent(&self) -> Option<usize> {
|
let root_subject = collection[msg_idx].subject();
|
||||||
self.parent
|
/* If the ThreadNode has no Message, but does have children, remove this container but
|
||||||
}
|
* promote its children to this level (that is, splice them in to the current child
|
||||||
pub fn has_parent(&self) -> bool {
|
* list.) */
|
||||||
self.parent.is_some()
|
if indentation > 0 && thread_nodes[idx].has_message() {
|
||||||
}
|
let subject = collection[thread_nodes[idx].message().as_ref().unwrap()].subject();
|
||||||
pub fn first_child(&self) -> Option<usize> {
|
thread_nodes[idx].has_unseen =
|
||||||
self.first_child
|
!collection[thread_nodes[idx].message().as_ref().unwrap()].is_seen();
|
||||||
}
|
if subject == root_subject
|
||||||
pub fn next_sibling(&self) -> Option<usize> {
|
|| subject.starts_with("Re: ") && subject.as_ref().ends_with(root_subject.as_ref())
|
||||||
self.next_sibling
|
|
||||||
}
|
|
||||||
pub fn has_children(&self) -> bool {
|
|
||||||
self.first_child.is_some()
|
|
||||||
}
|
|
||||||
pub fn has_sibling(&self) -> bool {
|
|
||||||
self.next_sibling.is_some()
|
|
||||||
}
|
|
||||||
pub fn has_message(&self) -> bool {
|
|
||||||
self.message.is_some()
|
|
||||||
}
|
|
||||||
fn set_indentation(&mut self, i: usize) {
|
|
||||||
self.indentation = i;
|
|
||||||
}
|
|
||||||
pub fn indentation(&self) -> usize {
|
|
||||||
self.indentation
|
|
||||||
}
|
|
||||||
fn is_descendant(&self, threads: &[Container], other: &Container) -> bool {
|
|
||||||
if self == other {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(v) = self.first_child {
|
|
||||||
if threads[v].is_descendant(threads, other) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(v) = self.next_sibling {
|
|
||||||
if threads[v].is_descendant(threads, other) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
false
|
|
||||||
}
|
|
||||||
fn set_show_subject(&mut self, set: bool) -> () {
|
|
||||||
self.show_subject = set;
|
|
||||||
}
|
|
||||||
pub fn show_subject(&self) -> bool {
|
|
||||||
self.show_subject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Container {
|
|
||||||
fn eq(&self, other: &Container) -> bool {
|
|
||||||
match (self.message, other.message) {
|
|
||||||
(Some(s), Some(o)) => s == o,
|
|
||||||
_ => self.id == other.id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_collection(
|
|
||||||
threads: &mut Vec<Container>,
|
|
||||||
id_table: &mut FnvHashMap<Cow<str>, usize>,
|
|
||||||
collection: &mut [Envelope],
|
|
||||||
) -> () {
|
|
||||||
for (i, x) in collection.iter_mut().enumerate() {
|
|
||||||
let x_index; /* x's index in threads */
|
|
||||||
let m_id = x.message_id_raw().into_owned();
|
|
||||||
let m_id = Cow::from(m_id);
|
|
||||||
if id_table.contains_key(&m_id) {
|
|
||||||
let t = id_table[&m_id];
|
|
||||||
/* the already existing Container should be empty, since we're
|
|
||||||
* seeing this message for the first time */
|
|
||||||
if threads[t].message.is_some() {
|
|
||||||
/* skip duplicate message-id, but this should be handled instead */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
x_index = t;
|
|
||||||
/* Store this message in the Container's message slot. */
|
|
||||||
threads[t].date = x.date();
|
|
||||||
x.set_thread(t);
|
|
||||||
threads[t].message = Some(i);
|
|
||||||
} else {
|
|
||||||
/* Create a new Container object holding this message */
|
|
||||||
x_index = threads.len();
|
|
||||||
threads.push(Container {
|
|
||||||
message: Some(i),
|
|
||||||
id: x_index,
|
|
||||||
date: x.date(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
x.set_thread(x_index);
|
|
||||||
id_table.insert(m_id, x_index);
|
|
||||||
}
|
|
||||||
/* For each element in the message's References field:
|
|
||||||
*
|
|
||||||
* Find a Container object for the given Message-ID:
|
|
||||||
* If there's one in id_table use that;
|
|
||||||
* Otherwise, make (and index) one with a null Message
|
|
||||||
*
|
|
||||||
* Link the References field's Container together in the order implied by the References header.
|
|
||||||
* If they are already linked, don't change the existing links.
|
|
||||||
* Do not add a link if adding that link would introduce a loop: that is, before asserting A->B, search down the children of B to see if A is reachable, and also search down the children of A to see if B is reachable. If either is already reachable as a child of the other, don't add the link.
|
|
||||||
*/
|
|
||||||
let mut curr_ref = x_index;
|
|
||||||
let mut iasf = 0;
|
|
||||||
for &r in x.references().iter().rev() {
|
|
||||||
if iasf == 1 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
iasf += 1;
|
|
||||||
let r = String::from_utf8_lossy(r.raw());
|
|
||||||
let parent_id = if id_table.contains_key(&r) {
|
|
||||||
let p = id_table[r.as_ref()];
|
|
||||||
if !(threads[p].is_descendant(threads, &threads[curr_ref])
|
|
||||||
|| threads[curr_ref].is_descendant(threads, &threads[p]))
|
|
||||||
{
|
|
||||||
threads[curr_ref].parent = Some(p);
|
|
||||||
if threads[p].first_child.is_none() {
|
|
||||||
threads[p].first_child = Some(curr_ref);
|
|
||||||
} else {
|
|
||||||
let mut child_iter = threads[p].first_child.unwrap();
|
|
||||||
while threads[child_iter].next_sibling.is_some() {
|
|
||||||
threads[child_iter].parent = Some(p);
|
|
||||||
child_iter = threads[child_iter].next_sibling.unwrap();
|
|
||||||
}
|
|
||||||
threads[child_iter].next_sibling = Some(curr_ref);
|
|
||||||
threads[child_iter].parent = Some(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p
|
|
||||||
} else {
|
|
||||||
let idx = threads.len();
|
|
||||||
threads.push(Container {
|
|
||||||
message: None,
|
|
||||||
id: idx,
|
|
||||||
first_child: Some(curr_ref),
|
|
||||||
date: x.date(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
if threads[curr_ref].parent.is_none() {
|
|
||||||
threads[curr_ref].parent = Some(idx);
|
|
||||||
}
|
|
||||||
/* Can't avoid copy here since we have different lifetimes */
|
|
||||||
id_table.insert(Cow::from(r.into_owned()), idx);
|
|
||||||
idx
|
|
||||||
};
|
|
||||||
/* update thread date */
|
|
||||||
let mut parent_iter = parent_id;
|
|
||||||
'date: loop {
|
|
||||||
let p = &mut threads[parent_iter];
|
|
||||||
if p.date < x.date() {
|
|
||||||
p.date = x.date();
|
|
||||||
}
|
|
||||||
match p.parent {
|
|
||||||
Some(p) => {
|
|
||||||
parent_iter = p;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
break 'date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
curr_ref = parent_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds threads from a collection.
|
|
||||||
pub fn build_threads(
|
|
||||||
collection: &mut Vec<Envelope>,
|
|
||||||
sent_folder: &Option<Result<Mailbox>>,
|
|
||||||
) -> Threads {
|
|
||||||
/* To reconstruct thread information from the mails we need: */
|
|
||||||
|
|
||||||
/* a vector to hold thread members */
|
|
||||||
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
|
||||||
/* A hash table of Message IDs */
|
|
||||||
let mut id_table: FnvHashMap<Cow<str>, usize> =
|
|
||||||
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
|
||||||
|
|
||||||
/* Add each message to id_table and threads, and link them together according to the
|
|
||||||
* References / In-Reply-To headers */
|
|
||||||
build_collection(&mut threads, &mut id_table, collection);
|
|
||||||
let mut idx = collection.len();
|
|
||||||
let mut tidx = threads.len();
|
|
||||||
/* Link messages from Sent folder if they are relevant to this folder.
|
|
||||||
* This means that
|
|
||||||
* - if a message from Sent is a reply to a message in this folder, we will
|
|
||||||
* add it to the threading (but not the collection; non-threading users shouldn't care
|
|
||||||
* about this)
|
|
||||||
* - if a message in this folder is a reply to a message we sent, we will add it to the
|
|
||||||
* threading
|
|
||||||
*/
|
|
||||||
|
|
||||||
if let Some(ref sent_box) = *sent_folder {
|
|
||||||
if sent_box.is_ok() {
|
|
||||||
let sent_mailbox = sent_box.as_ref();
|
|
||||||
let sent_mailbox = sent_mailbox.unwrap();
|
|
||||||
|
|
||||||
for x in &sent_mailbox.collection {
|
|
||||||
let m_id = x.message_id_raw();
|
|
||||||
let x_r_id = x.in_reply_to_raw();
|
|
||||||
if id_table.contains_key(&m_id)
|
|
||||||
|| (!x.in_reply_to_raw().is_empty()
|
|
||||||
&& id_table.contains_key(&x.in_reply_to_raw()))
|
|
||||||
{
|
|
||||||
let mut x: Envelope = (*x).clone();
|
|
||||||
if id_table.contains_key(&m_id) {
|
|
||||||
let c = id_table[&m_id];
|
|
||||||
if threads[c].message.is_some() {
|
|
||||||
/* skip duplicate message-id, but this should be handled instead */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
threads[c].message = Some(idx);
|
|
||||||
assert!(threads[c].has_children());
|
|
||||||
threads[c].date = x.date();
|
|
||||||
x.set_thread(c);
|
|
||||||
} else if !x.in_reply_to_raw().is_empty()
|
|
||||||
&& id_table.contains_key(&x.in_reply_to_raw())
|
|
||||||
{
|
|
||||||
let p = id_table[&x_r_id];
|
|
||||||
let c = if id_table.contains_key(&m_id) {
|
|
||||||
id_table[&m_id]
|
|
||||||
} else {
|
|
||||||
threads.push(Container {
|
|
||||||
message: Some(idx),
|
|
||||||
id: tidx,
|
|
||||||
parent: Some(p),
|
|
||||||
date: x.date(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
id_table.insert(Cow::from(m_id.into_owned()), tidx);
|
|
||||||
x.set_thread(tidx);
|
|
||||||
tidx += 1;
|
|
||||||
tidx - 1
|
|
||||||
};
|
|
||||||
threads[c].parent = Some(p);
|
|
||||||
if threads[p].is_descendant(&threads, &threads[c])
|
|
||||||
|| threads[c].is_descendant(&threads, &threads[p])
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if threads[p].first_child.is_none() {
|
|
||||||
threads[p].first_child = Some(c);
|
|
||||||
} else {
|
|
||||||
let mut fc = threads[p].first_child.unwrap();
|
|
||||||
while threads[fc].next_sibling.is_some() {
|
|
||||||
threads[fc].parent = Some(p);
|
|
||||||
fc = threads[fc].next_sibling.unwrap();
|
|
||||||
}
|
|
||||||
threads[fc].next_sibling = Some(c);
|
|
||||||
threads[fc].parent = Some(p);
|
|
||||||
}
|
|
||||||
/* update thread date */
|
|
||||||
let mut parent_iter = p;
|
|
||||||
'date: loop {
|
|
||||||
let p = &mut threads[parent_iter];
|
|
||||||
if p.date < x.date() {
|
|
||||||
p.date = x.date();
|
|
||||||
}
|
|
||||||
match p.parent {
|
|
||||||
Some(p) => {
|
|
||||||
parent_iter = p;
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
break 'date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
collection.push(x);
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Walk over the elements of id_table, and gather a list of the Container objects that have
|
|
||||||
* no parents. These are the root messages of each thread */
|
|
||||||
let mut root_set = Vec::with_capacity(collection.len());
|
|
||||||
'root_set: for v in id_table.values() {
|
|
||||||
if threads[*v].parent.is_none() {
|
|
||||||
if !threads[*v].has_message()
|
|
||||||
&& threads[*v].has_children()
|
|
||||||
&& !threads[threads[*v].first_child.unwrap()].has_sibling()
|
|
||||||
{
|
{
|
||||||
/* Do not promote the children if doing so would promote them to the root set
|
thread_nodes[idx].show_subject = false;
|
||||||
* -- unless there is only one child, in which case, do. */
|
|
||||||
root_set.push(threads[*v].first_child.unwrap());
|
|
||||||
|
|
||||||
continue 'root_set;
|
|
||||||
}
|
}
|
||||||
root_set.push(*v);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
if thread_nodes[idx].has_parent()
|
||||||
|
&& !thread_nodes[thread_nodes[idx].parent().unwrap()].has_message()
|
||||||
/* Group messages together by thread in a collection so we can print them together */
|
{
|
||||||
let threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
thread_nodes[idx].parent = None;
|
||||||
|
}
|
||||||
let mut t = Threads {
|
let indentation = if thread_nodes[idx].has_message() {
|
||||||
containers: threads,
|
thread_nodes[idx].indentation = indentation;
|
||||||
threaded_collection,
|
indentation + 1
|
||||||
root_set,
|
} else if indentation > 0 {
|
||||||
..Default::default()
|
indentation
|
||||||
|
} else {
|
||||||
|
indentation + 1
|
||||||
};
|
};
|
||||||
t.build_collection(&collection);
|
|
||||||
t
|
let mut has_unseen = thread_nodes[idx].has_unseen;
|
||||||
|
for c in thread_nodes[idx].children.clone().iter() {
|
||||||
|
node_build(*c, thread_nodes, indentation, *c, collection);
|
||||||
|
has_unseen |= thread_nodes[*c].has_unseen;
|
||||||
|
}
|
||||||
|
thread_nodes[idx].has_unseen = has_unseen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ use melib::Draft;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, container_index)
|
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
||||||
account_cursor: usize,
|
account_cursor: usize,
|
||||||
|
|
||||||
pager: Pager,
|
pager: Pager,
|
||||||
|
@ -98,19 +98,19 @@ impl fmt::Display for Composer {
|
||||||
|
|
||||||
impl Composer {
|
impl Composer {
|
||||||
/*
|
/*
|
||||||
* coordinates: (account index, mailbox index, root set container index)
|
* coordinates: (account index, mailbox index, root set thread_node index)
|
||||||
* msg: index of message we reply to in containers
|
* msg: index of message we reply to in thread_nodes
|
||||||
* context: current context
|
* context: current context
|
||||||
*/
|
*/
|
||||||
pub fn with_context(coordinates: (usize, usize, usize), msg: usize, context: &Context) -> Self {
|
pub fn with_context(coordinates: (usize, usize, usize), msg: usize, 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.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let containers = &threads.containers();
|
let thread_nodes = &threads.thread_nodes();
|
||||||
let mut ret = Composer::default();
|
let mut ret = Composer::default();
|
||||||
let p = containers[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
|
||||||
.operation(parent_message.hash());
|
.operation(parent_message.hash());
|
||||||
|
@ -122,10 +122,10 @@ impl Composer {
|
||||||
if p.show_subject() {
|
if p.show_subject() {
|
||||||
format!(
|
format!(
|
||||||
"Re: {}",
|
"Re: {}",
|
||||||
mailbox.collection[p.message().unwrap()].subject().clone()
|
mailbox.collection[&p.message().unwrap()].subject().clone()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
mailbox.collection[p.message().unwrap()].subject().into()
|
mailbox.collection[&p.message().unwrap()].subject().into()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,7 @@ impl CompactListing {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.length = mailbox.threads.root_len();
|
self.length = mailbox.collection.threads.root_set().len();
|
||||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
|
@ -134,24 +134,24 @@ impl CompactListing {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let threads = &mailbox.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||||
for (idx, (t, len, has_unseen)) in threads.root_set_iter().enumerate() {
|
for (idx, root_idx) in threads.root_set().iter().enumerate() {
|
||||||
let container = &threads.containers()[t];
|
let thread_node = &threads.thread_nodes()[*root_idx];
|
||||||
let i = if let Some(i) = container.message() {
|
let i = if let Some(i) = thread_node.message() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
threads.containers()[container.first_child().unwrap()]
|
threads.thread_nodes()[thread_node.children()[0]]
|
||||||
.message()
|
.message()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
let root_envelope: &Envelope = &mailbox.collection[&i];
|
||||||
let fg_color = if has_unseen {
|
let fg_color = if thread_node.has_unseen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
} else {
|
} else {
|
||||||
Color::Default
|
Color::Default
|
||||||
};
|
};
|
||||||
let bg_color = if has_unseen {
|
let bg_color = if thread_node.has_unseen() {
|
||||||
Color::Byte(251)
|
Color::Byte(251)
|
||||||
} else if idx % 2 == 0 {
|
} else if idx % 2 == 0 {
|
||||||
Color::Byte(236)
|
Color::Byte(236)
|
||||||
|
@ -159,7 +159,7 @@ impl CompactListing {
|
||||||
Color::Default
|
Color::Default
|
||||||
};
|
};
|
||||||
let (x, _) = write_string_to_grid(
|
let (x, _) = write_string_to_grid(
|
||||||
&CompactListing::make_entry_string(root_envelope, len, idx),
|
&CompactListing::make_entry_string(root_envelope, thread_node.len(), idx),
|
||||||
&mut self.content,
|
&mut self.content,
|
||||||
fg_color,
|
fg_color,
|
||||||
bg_color,
|
bg_color,
|
||||||
|
@ -179,17 +179,17 @@ impl CompactListing {
|
||||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let threads = &mailbox.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let container = threads.root_set()[idx];
|
let thread_node = threads.root_set()[idx];
|
||||||
let container = &threads.containers()[container];
|
let thread_node = &threads.thread_nodes()[thread_node];
|
||||||
let i = if let Some(i) = container.message() {
|
let i = if let Some(i) = thread_node.message() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
threads.containers()[container.first_child().unwrap()]
|
threads.thread_nodes()[thread_node.children()[0]]
|
||||||
.message()
|
.message()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
let root_envelope: &Envelope = &mailbox.collection[&i];
|
||||||
let fg_color = if !root_envelope.is_seen() {
|
let fg_color = if !root_envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub struct PlainListing {
|
||||||
cursor_pos: (usize, usize, usize),
|
cursor_pos: (usize, usize, usize),
|
||||||
new_cursor_pos: (usize, usize, usize),
|
new_cursor_pos: (usize, usize, usize),
|
||||||
length: usize,
|
length: usize,
|
||||||
local_collection: Vec<usize>,
|
local_collection: Vec<EnvelopeHash>,
|
||||||
sort: (SortField, SortOrder),
|
sort: (SortField, SortOrder),
|
||||||
subsort: (SortField, SortOrder),
|
subsort: (SortField, SortOrder),
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
|
@ -118,11 +118,7 @@ impl PlainListing {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.length = if threaded {
|
self.length = mailbox.len();
|
||||||
mailbox.threads.threaded_collection().len()
|
|
||||||
} else {
|
|
||||||
mailbox.len()
|
|
||||||
};
|
|
||||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||||
if self.length == 0 {
|
if self.length == 0 {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
|
@ -136,160 +132,68 @@ impl PlainListing {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
// Populate `CellBuffer` with every entry.
|
||||||
if threaded {
|
let mut idx = 0;
|
||||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
for y in 0..=self.length {
|
||||||
let mut thread_idx = 0; // needed for alternate thread colors
|
if idx >= self.length {
|
||||||
/* Draw threaded view. */
|
/* No more entries left, so fill the rest of the area with empty space */
|
||||||
let threads = &mailbox.threads;
|
clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
break;
|
||||||
let containers: &Vec<Container> = &threads.containers();
|
|
||||||
let mut iter = threads.into_iter().peekable();
|
|
||||||
let len = threads
|
|
||||||
.threaded_collection()
|
|
||||||
.len()
|
|
||||||
.to_string()
|
|
||||||
.chars()
|
|
||||||
.count();
|
|
||||||
/* This is just a desugared for loop so that we can use .peek() */
|
|
||||||
let mut idx = 0;
|
|
||||||
while let Some(i) = iter.next() {
|
|
||||||
let container = &containers[i];
|
|
||||||
let indentation = container.indentation();
|
|
||||||
|
|
||||||
if indentation == 0 {
|
|
||||||
thread_idx += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !container.has_message() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match iter.peek() {
|
|
||||||
Some(&x) if threads[x].indentation() == indentation => {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(true);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if container.has_sibling() {
|
|
||||||
indentations.pop();
|
|
||||||
indentations.push(true);
|
|
||||||
}
|
|
||||||
let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
|
||||||
Color::Byte(0)
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
let bg_color = if !envelope.is_seen() {
|
|
||||||
Color::Byte(251)
|
|
||||||
} else if thread_idx % 2 == 0 {
|
|
||||||
Color::Byte(236)
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
let (x, _) = write_string_to_grid(
|
|
||||||
&PlainListing::make_thread_entry(
|
|
||||||
envelope,
|
|
||||||
idx,
|
|
||||||
indentation,
|
|
||||||
container,
|
|
||||||
&indentations,
|
|
||||||
len,
|
|
||||||
// context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
|
|
||||||
),
|
|
||||||
&mut self.content,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
((0, idx), (MAX_COLS - 1, idx)),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
for x in x..MAX_COLS {
|
|
||||||
self.content[(x, idx)].set_ch(' ');
|
|
||||||
self.content[(x, idx)].set_bg(bg_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
match iter.peek() {
|
|
||||||
Some(&x) if containers[x].indentation() > indentation => {
|
|
||||||
indentations.push(false);
|
|
||||||
}
|
|
||||||
Some(&x) if containers[x].indentation() < indentation => {
|
|
||||||
for _ in 0..(indentation - containers[x].indentation()) {
|
|
||||||
indentations.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
idx += 1;
|
|
||||||
}
|
}
|
||||||
} else {
|
/* Write an entire line for each envelope entry. */
|
||||||
// Populate `CellBuffer` with every entry.
|
self.local_collection = mailbox.collection.keys().map(|v| *v).collect();
|
||||||
let mut idx = 0;
|
let sort = self.sort;
|
||||||
for y in 0..=self.length {
|
self.local_collection.sort_by(|a, b| match sort {
|
||||||
if idx >= self.length {
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
/* No more entries left, so fill the rest of the area with empty space */
|
let ma = &mailbox.collection[a];
|
||||||
clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
|
let mb = &mailbox.collection[b];
|
||||||
break;
|
mb.date().cmp(&ma.date())
|
||||||
}
|
}
|
||||||
/* Write an entire line for each envelope entry. */
|
(SortField::Date, SortOrder::Asc) => {
|
||||||
self.local_collection = (0..mailbox.collection.len()).collect();
|
let ma = &mailbox.collection[a];
|
||||||
let sort = self.sort;
|
let mb = &mailbox.collection[b];
|
||||||
self.local_collection.sort_by(|a, b| match sort {
|
ma.date().cmp(&mb.date())
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
|
||||||
let ma = &mailbox.collection[*a];
|
|
||||||
let mb = &mailbox.collection[*b];
|
|
||||||
mb.date().cmp(&ma.date())
|
|
||||||
}
|
|
||||||
(SortField::Date, SortOrder::Asc) => {
|
|
||||||
let ma = &mailbox.collection[*a];
|
|
||||||
let mb = &mailbox.collection[*b];
|
|
||||||
ma.date().cmp(&mb.date())
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Desc) => {
|
|
||||||
let ma = &mailbox.collection[*a];
|
|
||||||
let mb = &mailbox.collection[*b];
|
|
||||||
ma.subject().cmp(&mb.subject())
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Asc) => {
|
|
||||||
let ma = &mailbox.collection[*a];
|
|
||||||
let mb = &mailbox.collection[*b];
|
|
||||||
mb.subject().cmp(&ma.subject())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let envelope: &Envelope = &mailbox.collection[self.local_collection[idx]];
|
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
|
||||||
Color::Byte(0)
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
let bg_color = if !envelope.is_seen() {
|
|
||||||
Color::Byte(251)
|
|
||||||
} else if idx % 2 == 0 {
|
|
||||||
Color::Byte(236)
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
let (x, y) = write_string_to_grid(
|
|
||||||
&PlainListing::make_entry_string(envelope, idx),
|
|
||||||
&mut self.content,
|
|
||||||
fg_color,
|
|
||||||
bg_color,
|
|
||||||
((0, y), (MAX_COLS - 1, y)),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
for x in x..MAX_COLS {
|
|
||||||
self.content[(x, y)].set_ch(' ');
|
|
||||||
self.content[(x, y)].set_bg(bg_color);
|
|
||||||
}
|
}
|
||||||
|
(SortField::Subject, SortOrder::Desc) => {
|
||||||
|
let ma = &mailbox.collection[a];
|
||||||
|
let mb = &mailbox.collection[b];
|
||||||
|
ma.subject().cmp(&mb.subject())
|
||||||
|
}
|
||||||
|
(SortField::Subject, SortOrder::Asc) => {
|
||||||
|
let ma = &mailbox.collection[a];
|
||||||
|
let mb = &mailbox.collection[b];
|
||||||
|
mb.subject().cmp(&ma.subject())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||||
|
|
||||||
idx += 1;
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(251)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
&PlainListing::make_entry_string(envelope, idx),
|
||||||
|
&mut self.content,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
((0, y), (MAX_COLS - 1, y)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
for x in x..MAX_COLS {
|
||||||
|
self.content[(x, y)].set_ch(' ');
|
||||||
|
self.content[(x, y)].set_bg(bg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,12 +205,7 @@ impl PlainListing {
|
||||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope: &Envelope = if threaded {
|
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||||
let i = mailbox.threaded_mail(idx);
|
|
||||||
&mailbox.collection[i]
|
|
||||||
} else {
|
|
||||||
&mailbox.collection[idx]
|
|
||||||
};
|
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -336,12 +235,7 @@ impl PlainListing {
|
||||||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope: &Envelope = if threaded {
|
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||||
let i = mailbox.threaded_mail(idx);
|
|
||||||
&mailbox.collection[i]
|
|
||||||
} else {
|
|
||||||
&mailbox.collection[idx]
|
|
||||||
};
|
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
Color::Byte(0)
|
Color::Byte(0)
|
||||||
|
@ -423,14 +317,15 @@ impl PlainListing {
|
||||||
envelope: &Envelope,
|
envelope: &Envelope,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
container: &Container,
|
node_idx: usize,
|
||||||
|
nodes: &Threads,
|
||||||
indentations: &[bool],
|
indentations: &[bool],
|
||||||
idx_width: usize,
|
idx_width: usize,
|
||||||
//op: Box<BackendOp>,
|
//op: Box<BackendOp>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let has_sibling = container.has_sibling();
|
let has_sibling = nodes.has_sibling(node_idx);
|
||||||
let has_parent = container.has_parent();
|
let has_parent = nodes[node_idx].has_parent();
|
||||||
let show_subject = container.show_subject();
|
let show_subject = nodes[node_idx].show_subject();
|
||||||
|
|
||||||
let mut s = format!(
|
let mut s = format!(
|
||||||
"{}{}{} ",
|
"{}{}{} ",
|
||||||
|
@ -525,12 +420,10 @@ impl Component for PlainListing {
|
||||||
let account = &mut context.accounts[self.cursor_pos.0];
|
let account = &mut context.accounts[self.cursor_pos.0];
|
||||||
let (hash, is_seen) = {
|
let (hash, is_seen) = {
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
let envelope: &mut Envelope = if threaded {
|
let envelope: &mut Envelope = &mut mailbox
|
||||||
let i = mailbox.threaded_mail(idx);
|
.collection
|
||||||
&mut mailbox.collection[i]
|
.entry(self.local_collection[idx])
|
||||||
} else {
|
.or_default();
|
||||||
&mut mailbox.collection[self.local_collection[idx]]
|
|
||||||
};
|
|
||||||
(envelope.hash(), envelope.is_seen())
|
(envelope.hash(), envelope.is_seen())
|
||||||
};
|
};
|
||||||
if !is_seen {
|
if !is_seen {
|
||||||
|
@ -539,12 +432,10 @@ impl Component for PlainListing {
|
||||||
backend.operation(hash)
|
backend.operation(hash)
|
||||||
};
|
};
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
let envelope: &mut Envelope = if threaded {
|
let envelope: &mut Envelope = &mut mailbox
|
||||||
let i = mailbox.threaded_mail(idx);
|
.collection
|
||||||
&mut mailbox.collection[i]
|
.entry(self.local_collection[idx])
|
||||||
} else {
|
.or_default();
|
||||||
&mut mailbox.collection[self.local_collection[idx]]
|
|
||||||
};
|
|
||||||
envelope.set_seen(op).unwrap();
|
envelope.set_seen(op).unwrap();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -598,11 +489,11 @@ impl Component for PlainListing {
|
||||||
let account = &context.accounts[self.cursor_pos.0];
|
let account = &context.accounts[self.cursor_pos.0];
|
||||||
let mailbox = &account[self.cursor_pos.1].as_ref().unwrap();
|
let mailbox = &account[self.cursor_pos.1].as_ref().unwrap();
|
||||||
let mut coordinates = self.cursor_pos;
|
let mut coordinates = self.cursor_pos;
|
||||||
coordinates.2 = if threaded {
|
let coordinates = (
|
||||||
mailbox.threaded_mail(self.cursor_pos.2)
|
coordinates.0,
|
||||||
} else {
|
coordinates.1,
|
||||||
self.local_collection[self.cursor_pos.2]
|
self.local_collection[self.cursor_pos.2],
|
||||||
};
|
);
|
||||||
self.view = Some(MailView::new(coordinates, None, None));
|
self.view = Some(MailView::new(coordinates, None, None));
|
||||||
}
|
}
|
||||||
self.view.as_mut().unwrap().draw(
|
self.view.as_mut().unwrap().draw(
|
||||||
|
|
|
@ -144,7 +144,7 @@ impl AccountMenu {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.collection
|
.collection
|
||||||
.iter()
|
.values()
|
||||||
.filter(|e| !e.is_seen())
|
.filter(|e| !e.is_seen())
|
||||||
.count();
|
.count();
|
||||||
s.insert_str(
|
s.insert_str(
|
||||||
|
|
|
@ -61,7 +61,7 @@ impl ViewMode {
|
||||||
/// menus
|
/// menus
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MailView {
|
pub struct MailView {
|
||||||
coordinates: (usize, usize, usize),
|
coordinates: (usize, usize, EnvelopeHash),
|
||||||
pager: Option<Pager>,
|
pager: Option<Pager>,
|
||||||
subview: Option<Box<Component>>,
|
subview: Option<Box<Component>>,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
|
@ -79,7 +79,7 @@ impl fmt::Display for MailView {
|
||||||
|
|
||||||
impl MailView {
|
impl MailView {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
coordinates: (usize, usize, usize),
|
coordinates: (usize, usize, EnvelopeHash),
|
||||||
pager: Option<Pager>,
|
pager: Option<Pager>,
|
||||||
subview: Option<Box<Component>>,
|
subview: Option<Box<Component>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -217,7 +217,7 @@ impl Component for MailView {
|
||||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||||
|
|
||||||
if self.mode == ViewMode::Raw {
|
if self.mode == ViewMode::Raw {
|
||||||
clear_area(grid, area);
|
clear_area(grid, area);
|
||||||
|
@ -302,7 +302,7 @@ impl Component for MailView {
|
||||||
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
|
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let envelope: &Envelope = &mailbox.collection[mailbox_idx.2];
|
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
|
||||||
let op = context.accounts[mailbox_idx.0]
|
let op = context.accounts[mailbox_idx.0]
|
||||||
.backend
|
.backend
|
||||||
.operation(envelope.hash());
|
.operation(envelope.hash());
|
||||||
|
@ -419,7 +419,7 @@ impl Component for MailView {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||||
let op = context.accounts[self.coordinates.0]
|
let op = context.accounts[self.coordinates.0]
|
||||||
.backend
|
.backend
|
||||||
.operation(envelope.hash());
|
.operation(envelope.hash());
|
||||||
|
@ -514,7 +514,7 @@ impl Component for MailView {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||||
let finder = LinkFinder::new();
|
let finder = LinkFinder::new();
|
||||||
let op = context.accounts[self.coordinates.0]
|
let op = context.accounts[self.coordinates.0]
|
||||||
.backend
|
.backend
|
||||||
|
|
|
@ -25,9 +25,9 @@ use std::cmp;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct ThreadEntry {
|
struct ThreadEntry {
|
||||||
index: (usize, usize, usize),
|
index: (usize, usize, usize),
|
||||||
/// (indentation, container index, line number in listing)
|
/// (indentation, thread_node index, line number in listing)
|
||||||
indentation: usize,
|
indentation: usize,
|
||||||
msg_idx: usize,
|
msg_idx: EnvelopeHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -47,7 +47,7 @@ pub struct ThreadView {
|
||||||
|
|
||||||
impl ThreadView {
|
impl ThreadView {
|
||||||
/*
|
/*
|
||||||
* coordinates: (account index, mailbox index, root set container 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_idx: 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
|
||||||
|
@ -62,13 +62,13 @@ impl ThreadView {
|
||||||
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.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let container = &threads.containers()[threads.root_set()[coordinates.2]];
|
let thread_node = &threads.thread_nodes()[threads.root_set()[coordinates.2]];
|
||||||
|
|
||||||
if container.message().is_some() {
|
if thread_node.message().is_some() {
|
||||||
stack.push((0, threads.root_set()[coordinates.2]));
|
stack.push((0, threads.root_set()[coordinates.2]));
|
||||||
} else {
|
} else {
|
||||||
stack.push((1, container.first_child().unwrap()));
|
stack.push((1, thread_node.children()[0]));
|
||||||
}
|
}
|
||||||
let mut view = ThreadView {
|
let mut view = ThreadView {
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
@ -95,13 +95,9 @@ impl ThreadView {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let container = &threads.containers()[idx];
|
let thread_node = &threads.thread_nodes()[idx];
|
||||||
if let Some(i) = container.next_sibling() {
|
for &c in thread_node.children().iter() {
|
||||||
stack.push((ind, i));
|
stack.push((ind + 1, c));
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(i) = container.first_child() {
|
|
||||||
stack.push((ind + 1, i));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if expanded_idx.is_none() {
|
if expanded_idx.is_none() {
|
||||||
|
@ -117,9 +113,9 @@ impl ThreadView {
|
||||||
let mut highlight_reply_subjects: Vec<Option<usize>> =
|
let mut highlight_reply_subjects: Vec<Option<usize>> =
|
||||||
Vec::with_capacity(view.entries.len());
|
Vec::with_capacity(view.entries.len());
|
||||||
for e in &view.entries {
|
for e in &view.entries {
|
||||||
let envelope: &Envelope = &mailbox.collection[e.msg_idx];
|
let envelope: &Envelope = &mailbox.collection[&e.msg_idx];
|
||||||
let container = &threads.containers()[e.index.1];
|
let thread_node = &threads.thread_nodes()[e.index.1];
|
||||||
let string = if container.show_subject() {
|
let string = if thread_node.show_subject() {
|
||||||
let subject = envelope.subject();
|
let subject = envelope.subject();
|
||||||
highlight_reply_subjects.push(Some(subject.len()));
|
highlight_reply_subjects.push(Some(subject.len()));
|
||||||
format!(
|
format!(
|
||||||
|
@ -198,11 +194,11 @@ impl ThreadView {
|
||||||
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let container = &mailbox.threads.containers()[idx];
|
let thread_node = &mailbox.collection.threads.thread_nodes()[idx];
|
||||||
let msg_idx = if let Some(i) = container.message() {
|
let msg_idx = if let Some(i) = thread_node.message() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
mailbox.threads.containers()[container.first_child().unwrap()]
|
mailbox.collection.threads.thread_nodes()[thread_node.children()[0]]
|
||||||
.message()
|
.message()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
@ -316,16 +312,16 @@ impl ThreadView {
|
||||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let threads = &mailbox.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
|
let thread_node = &threads.thread_nodes()[threads.root_set()[self.coordinates.2]];
|
||||||
let i = if let Some(i) = container.message() {
|
let i = if let Some(i) = thread_node.message() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
threads.containers()[container.first_child().unwrap()]
|
threads.thread_nodes()[thread_node.children()[0]]
|
||||||
.message()
|
.message()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
let envelope: &Envelope = &mailbox.collection[i];
|
let envelope: &Envelope = &mailbox.collection[&i];
|
||||||
|
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&envelope.subject(),
|
&envelope.subject(),
|
||||||
|
@ -435,16 +431,16 @@ impl ThreadView {
|
||||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let threads = &mailbox.threads;
|
let threads = &mailbox.collection.threads;
|
||||||
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
|
let thread_node = &threads.thread_nodes()[threads.root_set()[self.coordinates.2]];
|
||||||
let i = if let Some(i) = container.message() {
|
let i = if let Some(i) = thread_node.message() {
|
||||||
i
|
i
|
||||||
} else {
|
} else {
|
||||||
threads.containers()[container.first_child().unwrap()]
|
threads.thread_nodes()[thread_node.children()[0]]
|
||||||
.message()
|
.message()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
let envelope: &Envelope = &mailbox.collection[i];
|
let envelope: &Envelope = &mailbox.collection[&i];
|
||||||
|
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&envelope.subject(),
|
&envelope.subject(),
|
||||||
|
|
|
@ -562,7 +562,7 @@ impl Component for StatusBar {
|
||||||
self.mode,
|
self.mode,
|
||||||
m.folder.name(),
|
m.folder.name(),
|
||||||
m.collection.len(),
|
m.collection.len(),
|
||||||
m.collection.iter().filter(|e| !e.is_seen()).count()
|
m.collection.values().filter(|e| !e.is_seen()).count()
|
||||||
);
|
);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ impl State {
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("startup-thread".to_string())
|
.name("startup-thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let dur = time::Duration::from_millis(100);
|
let dur = time::Duration::from_millis(800);
|
||||||
loop {
|
loop {
|
||||||
chan_select! {
|
chan_select! {
|
||||||
default => {},
|
default => {},
|
||||||
|
|
Loading…
Reference in New Issue