parent
591946a842
commit
daa28ae188
|
@ -130,6 +130,7 @@ impl StrBuilder {
|
||||||
let length = self.length;
|
let length = self.length;
|
||||||
String::from_utf8(s[offset..offset + length].to_vec()).unwrap()
|
String::from_utf8(s[offset..offset + length].to_vec()).unwrap()
|
||||||
}
|
}
|
||||||
|
#[cfg(test)]
|
||||||
fn display_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
|
fn display_bytes<'a>(&self, b: &'a [u8]) -> &'a [u8] {
|
||||||
&b[self.offset..(self.offset+self.length)]
|
&b[self.offset..(self.offset+self.length)]
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ use mailbox::backends::{folder_default, Folder, MailBackend};
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub use mailbox::accounts::Account;
|
pub use mailbox::accounts::Account;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub use mailbox::thread::{build_threads, Container};
|
pub use mailbox::thread::{build_threads, Container, Threads, SortOrder, SortField};
|
||||||
|
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
|
@ -43,8 +43,7 @@ use std::option::Option;
|
||||||
pub struct Mailbox {
|
pub struct Mailbox {
|
||||||
pub folder: Folder,
|
pub folder: Folder,
|
||||||
pub collection: Vec<Envelope>,
|
pub collection: Vec<Envelope>,
|
||||||
pub threaded_collection: Vec<usize>,
|
pub threads: Threads,
|
||||||
pub threads: Vec<Container>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Mailbox {
|
impl Clone for Mailbox {
|
||||||
|
@ -52,21 +51,21 @@ impl Clone for Mailbox {
|
||||||
Mailbox {
|
Mailbox {
|
||||||
folder: self.folder.clone(),
|
folder: self.folder.clone(),
|
||||||
collection: self.collection.clone(),
|
collection: self.collection.clone(),
|
||||||
threaded_collection: self.threaded_collection.clone(),
|
|
||||||
threads: self.threads.clone(),
|
threads: self.threads.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Default for Mailbox {
|
||||||
|
fn default() -> Self {
|
||||||
|
Mailbox {
|
||||||
|
folder: folder_default(),
|
||||||
|
collection: Vec::default(),
|
||||||
|
threads: Threads::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Mailbox {
|
impl Mailbox {
|
||||||
pub fn new_dummy() -> Self {
|
|
||||||
Mailbox {
|
|
||||||
folder: folder_default(),
|
|
||||||
collection: Vec::with_capacity(0),
|
|
||||||
threaded_collection: Vec::with_capacity(0),
|
|
||||||
threads: Vec::with_capacity(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn new(
|
pub fn new(
|
||||||
folder: &Folder,
|
folder: &Folder,
|
||||||
sent_folder: &Option<Result<Mailbox>>,
|
sent_folder: &Option<Result<Mailbox>>,
|
||||||
|
@ -74,20 +73,18 @@ impl Mailbox {
|
||||||
) -> Result<Mailbox> {
|
) -> Result<Mailbox> {
|
||||||
let mut collection: Vec<Envelope> = collection?;
|
let mut collection: Vec<Envelope> = collection?;
|
||||||
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||||
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
let threads = build_threads(&mut collection, sent_folder);
|
||||||
Ok(Mailbox {
|
Ok(Mailbox {
|
||||||
folder: (*folder).clone(),
|
folder: (*folder).clone(),
|
||||||
collection: collection,
|
collection: collection,
|
||||||
threads: threads,
|
threads: threads,
|
||||||
threaded_collection: threaded_collection,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
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) -> usize {
|
||||||
let thread = self.threads[self.threaded_collection[i]];
|
self.threads.thread_to_mail(i)
|
||||||
thread.message().unwrap()
|
|
||||||
}
|
}
|
||||||
pub fn mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
pub fn mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
||||||
let x = &mut self.collection.as_mut_slice()[i];
|
let x = &mut self.collection.as_mut_slice()[i];
|
||||||
|
|
|
@ -30,6 +30,57 @@ use mailbox::Mailbox;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
use self::fnv::FnvHashMap;
|
use self::fnv::FnvHashMap;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::ops::{Index, };
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::result::Result as StdResult;
|
||||||
|
use std::cell::{Ref, RefCell};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||||
|
pub enum SortOrder {
|
||||||
|
Asc,
|
||||||
|
Desc,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||||
|
pub enum SortField {
|
||||||
|
Subject,
|
||||||
|
Date,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SortField {
|
||||||
|
fn default() -> Self {
|
||||||
|
SortField::Date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SortOrder {
|
||||||
|
fn default() -> Self {
|
||||||
|
SortOrder::Desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SortField {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
|
||||||
|
match s.trim() {
|
||||||
|
"subject" | "s" | "sub" | "sbj" | "subj" => Ok(SortField::Subject),
|
||||||
|
"date" | "d" => Ok(SortField::Date),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SortOrder {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
|
||||||
|
match s.trim() {
|
||||||
|
"asc" => Ok(SortOrder::Asc),
|
||||||
|
"desc" => Ok(SortOrder::Desc),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type UnixTimestamp = u64;
|
type UnixTimestamp = u64;
|
||||||
|
|
||||||
|
@ -52,6 +103,274 @@ pub struct Container {
|
||||||
show_subject: bool,
|
show_subject: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct ContainerTree {
|
||||||
|
id: usize,
|
||||||
|
children: Option<Vec<ContainerTree>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContainerTree {
|
||||||
|
fn new(id: usize) -> Self {
|
||||||
|
ContainerTree {
|
||||||
|
id,
|
||||||
|
children: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Threads {
|
||||||
|
containers: Vec<Container>,
|
||||||
|
threaded_collection: Vec<usize>,
|
||||||
|
root_set: Vec<usize>,
|
||||||
|
tree: RefCell<Vec<ContainerTree>>,
|
||||||
|
sort: RefCell<(SortField, SortOrder)>,
|
||||||
|
subsort: RefCell<(SortField, SortOrder)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ThreadIterator<'a> {
|
||||||
|
pos: usize,
|
||||||
|
stack: Vec<usize>,
|
||||||
|
tree: Ref<'a ,Vec<ContainerTree>>,
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl Threads {
|
||||||
|
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]) {
|
||||||
|
let tree = &mut self.tree.borrow_mut();
|
||||||
|
let containers = &self.containers;
|
||||||
|
for mut t in tree.iter_mut() {
|
||||||
|
if let Some(ref mut c ) = t.children {
|
||||||
|
c.sort_by(|a, b| { match subsort {
|
||||||
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
|
let a = containers[a.id];
|
||||||
|
let b = containers[b.id];
|
||||||
|
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() {
|
||||||
|
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_sort_by(&self, sort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||||
|
let tree = &mut self.tree.borrow_mut();
|
||||||
|
let containers = &self.containers;
|
||||||
|
tree.sort_by(|a, b| { match sort {
|
||||||
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
|
let a = containers[a.id];
|
||||||
|
let b = containers[b.id];
|
||||||
|
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() {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pub fn sort_by(&self, sort: (SortField, SortOrder), subsort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||||
|
if *self.sort.borrow() != sort {
|
||||||
|
self.inner_sort_by(sort, collection);
|
||||||
|
*self.sort.borrow_mut() = sort;
|
||||||
|
}
|
||||||
|
if *self.subsort.borrow() != subsort {
|
||||||
|
self.inner_subsort_by(subsort, collection);
|
||||||
|
*self.subsort.borrow_mut() = subsort;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_collection(&mut self, collection: &[Envelope]) {
|
||||||
|
fn build_threaded(
|
||||||
|
tree: &mut ContainerTree,
|
||||||
|
containers: &mut Vec<Container>,
|
||||||
|
indentation: usize,
|
||||||
|
threaded: &mut Vec<usize>,
|
||||||
|
i: usize,
|
||||||
|
root_subject_idx: usize,
|
||||||
|
collection: &[Envelope],
|
||||||
|
) {
|
||||||
|
tree.id = i;
|
||||||
|
let thread = containers[i];
|
||||||
|
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
|
||||||
|
* promote its children to this level (that is, splice them in to the current child
|
||||||
|
* list.) */
|
||||||
|
if indentation > 0 && thread.has_message() {
|
||||||
|
let subject = collection[thread.message().unwrap()].subject();
|
||||||
|
if subject == root_subject
|
||||||
|
|| subject.starts_with("Re: ")
|
||||||
|
&& subject.as_ref().ends_with(root_subject.as_ref())
|
||||||
|
{
|
||||||
|
containers[i].set_show_subject(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
indentation + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
if thread.has_children() {
|
||||||
|
let mut child_vec = Vec::new();
|
||||||
|
|
||||||
|
let mut fc = thread.first_child().unwrap();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut new_child_tree = ContainerTree::new(fc);
|
||||||
|
build_threaded(&mut new_child_tree, containers, indentation, threaded, fc, i, collection);
|
||||||
|
child_vec.push(new_child_tree);
|
||||||
|
let thread_ = containers[fc];
|
||||||
|
if !thread_.has_sibling() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fc = thread_.next_sibling().unwrap();
|
||||||
|
}
|
||||||
|
tree.children = Some(child_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
type Output = Container;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Container {
|
||||||
|
self.containers.get(index).expect("thread index out of bounds")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Container {
|
impl Container {
|
||||||
pub fn date(&self) -> UnixTimestamp {
|
pub fn date(&self) -> UnixTimestamp {
|
||||||
self.date
|
self.date
|
||||||
|
@ -124,7 +443,7 @@ fn build_collection(
|
||||||
threads: &mut Vec<Container>,
|
threads: &mut Vec<Container>,
|
||||||
id_table: &mut FnvHashMap<Cow<str>, usize>,
|
id_table: &mut FnvHashMap<Cow<str>, usize>,
|
||||||
collection: &mut [Envelope],
|
collection: &mut [Envelope],
|
||||||
) -> () {
|
) -> () {
|
||||||
for (i, x) in collection.iter_mut().enumerate() {
|
for (i, x) in collection.iter_mut().enumerate() {
|
||||||
let x_index; /* x's index in threads */
|
let x_index; /* x's index in threads */
|
||||||
let m_id = x.message_id_raw().into_owned();
|
let m_id = x.message_id_raw().into_owned();
|
||||||
|
@ -179,7 +498,7 @@ fn build_collection(
|
||||||
let parent_id = if id_table.contains_key(&r) {
|
let parent_id = if id_table.contains_key(&r) {
|
||||||
let p = id_table[r.as_ref()];
|
let p = id_table[r.as_ref()];
|
||||||
if !(threads[p].is_descendant(threads, &threads[curr_ref])
|
if !(threads[p].is_descendant(threads, &threads[curr_ref])
|
||||||
|| threads[curr_ref].is_descendant(threads, &threads[p]))
|
|| threads[curr_ref].is_descendant(threads, &threads[p]))
|
||||||
{
|
{
|
||||||
threads[curr_ref].parent = Some(p);
|
threads[curr_ref].parent = Some(p);
|
||||||
if threads[p].first_child.is_none() {
|
if threads[p].first_child.is_none() {
|
||||||
|
@ -239,7 +558,7 @@ fn build_collection(
|
||||||
pub fn build_threads(
|
pub fn build_threads(
|
||||||
collection: &mut Vec<Envelope>,
|
collection: &mut Vec<Envelope>,
|
||||||
sent_folder: &Option<Result<Mailbox>>,
|
sent_folder: &Option<Result<Mailbox>>,
|
||||||
) -> (Vec<Container>, Vec<usize>) {
|
) -> 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 */
|
||||||
|
@ -273,77 +592,77 @@ pub fn build_threads(
|
||||||
if id_table.contains_key(&m_id)
|
if id_table.contains_key(&m_id)
|
||||||
|| (!x.in_reply_to_raw().is_empty()
|
|| (!x.in_reply_to_raw().is_empty()
|
||||||
&& id_table.contains_key(&x.in_reply_to_raw()))
|
&& 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 mut x: Envelope = (*x).clone();
|
||||||
let c = if id_table.contains_key(&m_id) {
|
if id_table.contains_key(&m_id) {
|
||||||
id_table[&m_id]
|
let c = id_table[&m_id];
|
||||||
} else {
|
if threads[c].message.is_some() {
|
||||||
threads.push(Container {
|
/* skip duplicate message-id, but this should be handled instead */
|
||||||
message: Some(idx),
|
continue;
|
||||||
id: tidx,
|
|
||||||
parent: Some(p),
|
|
||||||
first_child: None,
|
|
||||||
next_sibling: None,
|
|
||||||
date: x.date(),
|
|
||||||
indentation: 0,
|
|
||||||
show_subject: true,
|
|
||||||
});
|
|
||||||
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[c].message = Some(idx);
|
||||||
threads[fc].parent = Some(p);
|
assert!(threads[c].has_children());
|
||||||
}
|
threads[c].date = x.date();
|
||||||
/* update thread date */
|
x.set_thread(c);
|
||||||
let mut parent_iter = p;
|
} else if !x.in_reply_to_raw().is_empty()
|
||||||
'date: loop {
|
&& id_table.contains_key(&x.in_reply_to_raw())
|
||||||
let p = &mut threads[parent_iter];
|
{
|
||||||
if p.date < x.date() {
|
let p = id_table[&x_r_id];
|
||||||
p.date = x.date();
|
let c = if id_table.contains_key(&m_id) {
|
||||||
}
|
id_table[&m_id]
|
||||||
match p.parent {
|
} else {
|
||||||
Some(p) => {
|
threads.push(Container {
|
||||||
parent_iter = p;
|
message: Some(idx),
|
||||||
|
id: tidx,
|
||||||
|
parent: Some(p),
|
||||||
|
first_child: None,
|
||||||
|
next_sibling: None,
|
||||||
|
date: x.date(),
|
||||||
|
indentation: 0,
|
||||||
|
show_subject: true,
|
||||||
|
});
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
None => {
|
/* update thread date */
|
||||||
break '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;
|
||||||
}
|
}
|
||||||
collection.push(x);
|
|
||||||
idx += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,80 +673,28 @@ pub fn build_threads(
|
||||||
if threads[*v].parent.is_none() {
|
if threads[*v].parent.is_none() {
|
||||||
if !threads[*v].has_message()
|
if !threads[*v].has_message()
|
||||||
&& threads[*v].has_children()
|
&& threads[*v].has_children()
|
||||||
&& !threads[threads[*v].first_child.unwrap()].has_sibling()
|
&& !threads[threads[*v].first_child.unwrap()].has_sibling()
|
||||||
{
|
{
|
||||||
/* 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. */
|
||||||
root_set.push(threads[*v].first_child.unwrap());
|
root_set.push(threads[*v].first_child.unwrap());
|
||||||
continue 'root_set;
|
|
||||||
}
|
continue 'root_set;
|
||||||
|
}
|
||||||
root_set.push(*v);
|
root_set.push(*v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
||||||
|
|
||||||
/* Group messages together by thread in a collection so we can print them together */
|
/* Group messages together by thread in a collection so we can print them together */
|
||||||
let mut threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
let threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
||||||
fn build_threaded(
|
|
||||||
threads: &mut Vec<Container>,
|
|
||||||
indentation: usize,
|
|
||||||
threaded: &mut Vec<usize>,
|
|
||||||
i: usize,
|
|
||||||
root_subject_idx: usize,
|
|
||||||
collection: &[Envelope],
|
|
||||||
) {
|
|
||||||
let thread = threads[i];
|
|
||||||
if let Some(msg_idx) = threads[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
|
|
||||||
* promote its children to this level (that is, splice them in to the current child
|
|
||||||
* list.) */
|
|
||||||
if indentation > 0 && thread.has_message() {
|
|
||||||
let subject = collection[thread.message().unwrap()].subject();
|
|
||||||
if subject == root_subject
|
|
||||||
|| subject.starts_with("Re: ")
|
|
||||||
&& subject.as_ref().ends_with(root_subject.as_ref())
|
|
||||||
{
|
|
||||||
threads[i].set_show_subject(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if thread.has_parent() && !threads[thread.parent().unwrap()].has_message() {
|
|
||||||
threads[i].parent = None;
|
|
||||||
}
|
|
||||||
let indentation = if thread.has_message() {
|
|
||||||
threads[i].set_indentation(indentation);
|
|
||||||
if !threaded.contains(&i) {
|
|
||||||
threaded.push(i);
|
|
||||||
}
|
|
||||||
indentation + 1
|
|
||||||
} else if indentation > 0 {
|
|
||||||
indentation
|
|
||||||
} else {
|
|
||||||
indentation + 1
|
|
||||||
};
|
|
||||||
if thread.has_children() {
|
|
||||||
let mut fc = thread.first_child().unwrap();
|
|
||||||
loop {
|
|
||||||
build_threaded(threads, indentation, threaded, fc, i, collection);
|
|
||||||
let thread_ = threads[fc];
|
|
||||||
if !thread_.has_sibling() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fc = thread_.next_sibling().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i in &root_set {
|
|
||||||
build_threaded(
|
|
||||||
&mut threads,
|
|
||||||
0,
|
|
||||||
&mut threaded_collection,
|
|
||||||
*i,
|
|
||||||
*i,
|
|
||||||
collection,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
(threads, threaded_collection)
|
let mut t = Threads {
|
||||||
|
containers: threads,
|
||||||
|
threaded_collection,
|
||||||
|
root_set,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
t.build_collection(&collection);
|
||||||
|
t
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ impl CompactMailListing {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.length = mailbox.threads.len();
|
self.length = mailbox.threads.containers().len();
|
||||||
let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
let mut 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(
|
||||||
|
@ -113,8 +113,8 @@ impl CompactMailListing {
|
||||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||||
let mut thread_idx = 0; // needed for alternate thread colors
|
let mut thread_idx = 0; // needed for alternate thread colors
|
||||||
/* Draw threaded view. */
|
/* Draw threaded view. */
|
||||||
let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone();
|
let mut local_collection: Vec<usize> = mailbox.threads.threaded_collection().clone();
|
||||||
let threads: &Vec<Container> = &mailbox.threads;
|
let threads: &Vec<Container> = &mailbox.threads.containers();
|
||||||
local_collection.sort_by(|a, b| match self.sort {
|
local_collection.sort_by(|a, b| match self.sort {
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
|
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
|
||||||
|
@ -139,7 +139,8 @@ impl CompactMailListing {
|
||||||
});
|
});
|
||||||
let mut iter = local_collection.iter().enumerate().peekable();
|
let mut iter = local_collection.iter().enumerate().peekable();
|
||||||
let len = mailbox
|
let len = mailbox
|
||||||
.threaded_collection
|
.threads
|
||||||
|
.threaded_collection()
|
||||||
.len()
|
.len()
|
||||||
.to_string()
|
.to_string()
|
||||||
.chars()
|
.chars()
|
||||||
|
|
|
@ -34,8 +34,9 @@ pub struct MailListing {
|
||||||
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>,
|
||||||
sort: (SortField, SortOrder),
|
sort: (SortField, SortOrder),
|
||||||
//subsort: (SortField, SortOrder),
|
subsort: (SortField, SortOrder),
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
/// If we must redraw on next redraw event
|
/// If we must redraw on next redraw event
|
||||||
|
@ -75,8 +76,9 @@ impl MailListing {
|
||||||
cursor_pos: (0, 1, 0),
|
cursor_pos: (0, 1, 0),
|
||||||
new_cursor_pos: (0, 0, 0),
|
new_cursor_pos: (0, 0, 0),
|
||||||
length: 0,
|
length: 0,
|
||||||
sort: (SortField::Date, SortOrder::Desc),
|
local_collection: Vec::new(),
|
||||||
//subsort: (SortField::Date, SortOrder::Asc),
|
sort: (Default::default(), Default::default()),
|
||||||
|
subsort: (Default::default(), Default::default()),
|
||||||
content: content,
|
content: content,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
|
@ -113,7 +115,7 @@ impl MailListing {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.length = if threaded {
|
self.length = if threaded {
|
||||||
mailbox.threaded_collection.len()
|
mailbox.threads.threaded_collection().len()
|
||||||
} else {
|
} else {
|
||||||
mailbox.len()
|
mailbox.len()
|
||||||
};
|
};
|
||||||
|
@ -136,57 +138,33 @@ impl MailListing {
|
||||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||||
let mut thread_idx = 0; // needed for alternate thread colors
|
let mut thread_idx = 0; // needed for alternate thread colors
|
||||||
/* Draw threaded view. */
|
/* Draw threaded view. */
|
||||||
let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone();
|
let threads = &mailbox.threads;
|
||||||
let threads: &Vec<Container> = &mailbox.threads;
|
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||||
local_collection.sort_by(|a, b| match self.sort {
|
let containers: &Vec<Container> = &threads.containers();
|
||||||
(SortField::Date, SortOrder::Desc) => {
|
let mut iter = threads.into_iter().peekable();
|
||||||
let a = mailbox.thread(*a);
|
let len = threads
|
||||||
let b = mailbox.thread(*b);
|
.threaded_collection()
|
||||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
|
||||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
|
||||||
mb.date().cmp(&ma.date())
|
|
||||||
}
|
|
||||||
(SortField::Date, SortOrder::Asc) => {
|
|
||||||
let a = mailbox.thread(*a);
|
|
||||||
let b = mailbox.thread(*b);
|
|
||||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
|
||||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
|
||||||
ma.date().cmp(&mb.date())
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Desc) => {
|
|
||||||
let a = mailbox.thread(*a);
|
|
||||||
let b = mailbox.thread(*b);
|
|
||||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
|
||||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
|
||||||
ma.subject().cmp(&mb.subject())
|
|
||||||
}
|
|
||||||
(SortField::Subject, SortOrder::Asc) => {
|
|
||||||
let a = mailbox.thread(*a);
|
|
||||||
let b = mailbox.thread(*b);
|
|
||||||
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
|
||||||
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
|
||||||
mb.subject().cmp(&ma.subject())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let mut iter = local_collection.iter().enumerate().peekable();
|
|
||||||
let len = mailbox
|
|
||||||
.threaded_collection
|
|
||||||
.len()
|
.len()
|
||||||
.to_string()
|
.to_string()
|
||||||
.chars()
|
.chars()
|
||||||
.count();
|
.count();
|
||||||
/* 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() */
|
||||||
while let Some((idx, i)) = iter.next() {
|
let mut idx = 0;
|
||||||
let container = &threads[*i];
|
while let Some(i) = iter.next() {
|
||||||
|
let container = &containers[i];
|
||||||
let indentation = container.indentation();
|
let indentation = container.indentation();
|
||||||
|
|
||||||
if indentation == 0 {
|
if indentation == 0 {
|
||||||
thread_idx += 1;
|
thread_idx += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(container.has_message());
|
if !container.has_message() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x)) if threads[*x].indentation() == indentation => {
|
Some(&x) if threads[x].indentation() == indentation => {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
}
|
}
|
||||||
|
@ -234,16 +212,17 @@ impl MailListing {
|
||||||
}
|
}
|
||||||
|
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x)) if threads[*x].indentation() > indentation => {
|
Some(&x) if containers[x].indentation() > indentation => {
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
}
|
}
|
||||||
Some(&(_, x)) if threads[*x].indentation() < indentation => {
|
Some(&x) if containers[x].indentation() < indentation => {
|
||||||
for _ in 0..(indentation - threads[*x].indentation()) {
|
for _ in 0..(indentation - containers[x].indentation()) {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
idx += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Populate `CellBuffer` with every entry.
|
// Populate `CellBuffer` with every entry.
|
||||||
|
@ -255,6 +234,40 @@ impl MailListing {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Write an entire line for each envelope entry. */
|
/* Write an entire line for each envelope entry. */
|
||||||
|
/*
|
||||||
|
self.local_collection = (0..mailbox.collection.len()).collect();
|
||||||
|
let sort = self.sort;
|
||||||
|
self.local_collection.sort_by(|a, b| match sort {
|
||||||
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
|
let a = mailbox.thread(*a);
|
||||||
|
let b = mailbox.thread(*b);
|
||||||
|
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||||
|
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||||
|
mb.date().cmp(&ma.date())
|
||||||
|
}
|
||||||
|
(SortField::Date, SortOrder::Asc) => {
|
||||||
|
let a = mailbox.thread(*a);
|
||||||
|
let b = mailbox.thread(*b);
|
||||||
|
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||||
|
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||||
|
ma.date().cmp(&mb.date())
|
||||||
|
}
|
||||||
|
(SortField::Subject, SortOrder::Desc) => {
|
||||||
|
let a = mailbox.thread(*a);
|
||||||
|
let b = mailbox.thread(*b);
|
||||||
|
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||||
|
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||||
|
ma.subject().cmp(&mb.subject())
|
||||||
|
}
|
||||||
|
(SortField::Subject, SortOrder::Asc) => {
|
||||||
|
let a = mailbox.thread(*a);
|
||||||
|
let b = mailbox.thread(*b);
|
||||||
|
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
|
||||||
|
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
|
||||||
|
mb.subject().cmp(&ma.subject())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
let envelope: &Envelope = &mailbox.collection[idx];
|
let envelope: &Envelope = &mailbox.collection[idx];
|
||||||
|
|
||||||
let fg_color = if !envelope.is_seen() {
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
@ -752,13 +765,21 @@ impl Component for MailListing {
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Action::Sort(field, order) => {
|
Action::SubSort(field, order) => {
|
||||||
self.sort = (field.clone(), order.clone());
|
eprintln!("SubSort {:?} , {:?}", field, order);
|
||||||
|
self.subsort = (*field, *order);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
self.refresh_mailbox(context);
|
self.refresh_mailbox(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_ => {}
|
Action::Sort(field, order) => {
|
||||||
|
eprintln!("Sort {:?} , {:?}", field, order);
|
||||||
|
self.sort = (*field, *order);
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// _ => {}
|
||||||
},
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,46 +23,13 @@
|
||||||
* User actions that need to be handled by the UI
|
* User actions that need to be handled by the UI
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::str::FromStr;
|
pub use melib::mailbox::{SortOrder, SortField};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MailListingAction {
|
pub enum MailListingAction {
|
||||||
ToggleThreaded,
|
ToggleThreaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum SortOrder {
|
|
||||||
Asc,
|
|
||||||
Desc,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum SortField {
|
|
||||||
Subject,
|
|
||||||
Date,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SortField {
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.trim() {
|
|
||||||
"subject" | "s" | "sub" | "sbj" | "subj" => Ok(SortField::Subject),
|
|
||||||
"date" | "d" => Ok(SortField::Date),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for SortOrder {
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s.trim() {
|
|
||||||
"asc" => Ok(SortOrder::Asc),
|
|
||||||
"desc" => Ok(SortOrder::Desc),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
|
|
Loading…
Reference in New Issue