melib/threads: Split ThreadGroup::Group to Thread

Create Thread struct.
async
Manos Pitsidianakis 2020-01-18 02:46:59 +02:00
parent d9269335a1
commit 5e68d600b9
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 182 additions and 175 deletions

View File

@ -122,7 +122,9 @@ macro_rules! make {
$threads.thread_nodes.entry($c).and_modify(|e| {
e.parent = Some($p);
});
let old_group = $threads.groups[&old_group_hash].clone();
let old_group = std::mem::replace($threads.groups.entry(old_group_hash).or_default(), ThreadGroup::Node {
parent: RefCell::new(parent_group_hash),
});
$threads.thread_nodes.entry($c).and_modify(|e| {
e.group = parent_group_hash;
});
@ -130,21 +132,21 @@ macro_rules! make {
e.group = parent_group_hash;
});
{
let parent_group = $threads.groups.entry(parent_group_hash).or_default();
let parent_group = $threads.thread_ref_mut(parent_group_hash);
match (parent_group, old_group) {
(ThreadGroup::Group {
(Thread {
ref mut date,
ref mut len,
ref mut unseen,
ref mut snoozed,
..
}, ThreadGroup::Group {
}, ThreadGroup::Root(Thread {
date: old_date,
len: old_len,
unseen: old_unseen,
snoozed: old_snoozed,
..
}) => {
})) => {
*date = std::cmp::max(old_date, *date);
*len += old_len;
*unseen |= old_unseen;
@ -153,14 +155,10 @@ macro_rules! make {
_ => unreachable!(),
}
}
{
let old_group = $threads.groups.entry(old_group_hash).or_default();
*old_group = ThreadGroup::Node {
parent: RefCell::new(parent_group_hash),
};
}
prev_parent
} else { None }
prev_parent
} else {
None
}
}};
}
@ -269,69 +267,62 @@ impl FromStr for SortOrder {
}
}
#[derive(Default, Clone, Debug, Deserialize, Serialize)]
pub struct Thread {
root: ThreadNodeHash,
date: UnixTimestamp,
len: usize,
unseen: usize,
snoozed: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum ThreadGroup {
Group {
root: ThreadNodeHash,
date: UnixTimestamp,
len: usize,
unseen: usize,
snoozed: bool,
},
Node {
parent: RefCell<ThreadHash>,
},
Root(Thread),
Node { parent: RefCell<ThreadHash> },
}
impl Default for ThreadGroup {
fn default() -> Self {
ThreadGroup::Group {
root: ThreadNodeHash::null(),
date: 0,
len: 0,
unseen: 0,
snoozed: false,
ThreadGroup::Root(Thread::default())
}
}
impl ThreadGroup {
fn root(&self) -> Option<&Thread> {
if let ThreadGroup::Root(ref root) = self {
Some(root)
} else {
None
}
}
fn root_mut(&mut self) -> Option<&mut Thread> {
if let ThreadGroup::Root(ref mut root) = self {
Some(root)
} else {
None
}
}
}
macro_rules! property {
($name:ident: $t:ty, $e:expr) => {
($name:ident: $t:ty) => {
pub fn $name(&self) -> $t {
match self {
ThreadGroup::Group { $name, .. } => (*$name).into(),
_ => {
debug!(
"ThreadGroup::{}() called on a ThreadGroup::Node: {:?}",
stringify!($name),
self
);
$e
}
}
(self.$name).into()
}
}
}
impl ThreadGroup {
property!(root: Option<ThreadNodeHash>, None);
property!(len: usize, 0);
property!(unseen: usize, 0);
property!(snoozed: bool, false);
property!(date: UnixTimestamp, 0);
impl Thread {
property!(root: ThreadNodeHash);
property!(len: usize);
property!(unseen: usize);
property!(snoozed: bool);
property!(date: UnixTimestamp);
pub fn set_snoozed(&mut self, val: bool) {
match self {
ThreadGroup::Group {
ref mut snoozed, ..
} => *snoozed = val,
_ => {
debug!(
"ThreadGroup::set_snoozed() called on a ThreadGroup::Node: {:?}",
self
);
}
}
self.snoozed = val;
}
}
@ -504,13 +495,26 @@ impl PartialEq for ThreadNode {
impl Threads {
pub fn is_snoozed(&self, h: ThreadNodeHash) -> bool {
let root = &self.find_group(self.thread_nodes[&h].group);
self.groups[&root].snoozed()
self.thread_ref(self.thread_nodes[&h].group).snoozed()
}
pub fn thread_ref(&self, h: ThreadHash) -> &Thread {
match self.groups[&self.find_group(h)] {
ThreadGroup::Root(ref root) => root,
ThreadGroup::Node { .. } => unreachable!(),
}
}
pub fn thread_ref_mut(&mut self, h: ThreadHash) -> &mut Thread {
match self.groups.get_mut(&self.find_group(h)) {
Some(ThreadGroup::Root(ref mut root)) => root,
Some(ThreadGroup::Node { .. }) | None => unreachable!(),
}
}
pub fn find_group(&self, h: ThreadHash) -> ThreadHash {
let p = match self.groups[&h] {
ThreadGroup::Group { .. } => return h,
ThreadGroup::Root(_) => return h,
ThreadGroup::Node { ref parent } => *parent.borrow(),
};
@ -579,7 +583,7 @@ impl Threads {
pub fn thread_group_iter(&self, index: ThreadHash) -> ThreadGroupIterator {
ThreadGroupIterator {
group: self.groups[&index].root().unwrap(),
group: self.thread_ref(index).root(),
pos: 0,
stack: SmallVec::new(),
thread_nodes: &self.thread_nodes,
@ -610,16 +614,15 @@ impl Threads {
let was_unseen = self.thread_nodes[&thread_hash].unseen;
let is_unseen = !envelopes.read().unwrap()[&new_hash].is_seen();
if was_unseen != is_unseen {
let thread = self.find_group(self.thread_nodes[&thread_hash].group);
self.groups.entry(thread).and_modify(|e| {
if let ThreadGroup::Group { ref mut unseen, .. } = e {
if was_unseen {
*unseen -= 1;
} else {
*unseen += 1;
}
if let Thread { ref mut unseen, .. } =
self.thread_ref_mut(self.thread_nodes[&thread_hash].group)
{
if was_unseen {
*unseen -= 1;
} else {
*unseen += 1;
}
});
}
}
self.thread_nodes.get_mut(&thread_hash).unwrap().unseen = is_unseen;
self.hash_set.remove(&old_hash);
@ -753,7 +756,7 @@ impl Threads {
self.groups.insert(
self.thread_nodes[&new_id].group,
ThreadGroup::Group {
ThreadGroup::Root(Thread {
root: new_id,
date: envelopes_lck[&env_hash].date(),
len: 1,
@ -763,7 +766,7 @@ impl Threads {
0
},
snoozed: false,
},
}),
);
self.message_ids
.insert(envelopes_lck[&env_hash].message_id().raw().to_vec(), new_id);
@ -795,13 +798,13 @@ impl Threads {
self.groups.insert(
self.thread_nodes[&reply_to_id].group,
ThreadGroup::Group {
ThreadGroup::Root(Thread {
root: reply_to_id,
date: envelopes_lck[&env_hash].date(),
len: 0,
unseen: 0,
snoozed: false,
},
}),
);
make!((reply_to_id) parent of (new_id), self);
self.missing_message_ids.insert(r.to_vec());
@ -838,13 +841,13 @@ impl Threads {
);
self.groups.insert(
self.thread_nodes[&id].group,
ThreadGroup::Group {
ThreadGroup::Root(Thread {
root: id,
date: envelopes_lck[&env_hash].date(),
len: 0,
unseen: 0,
snoozed: false,
},
}),
);
make!((id) parent of (current_descendant_id), self);
self.missing_message_ids.insert(reference.raw().to_vec());
@ -960,18 +963,18 @@ impl Threads {
let envelopes = envelopes.read().unwrap();
vec.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
let a = self.groups[&a].date();
let b = self.groups[&b].date();
let a = self.thread_ref(*a).date();
let b = self.thread_ref(*b).date();
b.cmp(&a)
}
(SortField::Date, SortOrder::Asc) => {
let a = self.groups[&a].date();
let b = self.groups[&b].date();
let a = self.thread_ref(*a).date();
let b = self.thread_ref(*b).date();
a.cmp(&b)
}
(SortField::Subject, SortOrder::Desc) => {
let a = &self.thread_nodes[&self.groups[&a].root().unwrap()].message();
let b = &self.thread_nodes[&self.groups[&b].root().unwrap()].message();
let a = &self.thread_nodes[&self.thread_ref(*a).root()].message();
let b = &self.thread_nodes[&self.thread_ref(*b).root()].message();
match (a, b) {
(Some(_), Some(_)) => {}
@ -999,8 +1002,8 @@ impl Threads {
}
}
(SortField::Subject, SortOrder::Asc) => {
let a = &self.thread_nodes[&self.groups[&a].root().unwrap()].message();
let b = &self.thread_nodes[&self.groups[&b].root().unwrap()].message();
let a = &self.thread_nodes[&self.thread_ref(*a).root()].message();
let b = &self.thread_nodes[&self.thread_ref(*b).root()].message();
match (a, b) {
(Some(_), Some(_)) => {}
@ -1040,17 +1043,13 @@ impl Threads {
let envelopes = envelopes.read().unwrap();
vec.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
let a_group = self.find_group(self.thread_nodes[&a].group);
let b_group = self.find_group(self.thread_nodes[&b].group);
let a = self.groups[&a_group].date();
let b = self.groups[&b_group].date();
let a = self.thread_ref(self.thread_nodes[&a].group).date();
let b = self.thread_ref(self.thread_nodes[&b].group).date();
b.cmp(&a)
}
(SortField::Date, SortOrder::Asc) => {
let a_group = self.find_group(self.thread_nodes[&a].group);
let b_group = self.find_group(self.thread_nodes[&b].group);
let a = self.groups[&a_group].date();
let b = self.groups[&b_group].date();
let a = self.thread_ref(self.thread_nodes[&a].group).date();
let b = self.thread_ref(self.thread_nodes[&b].group).date();
a.cmp(&b)
}
(SortField::Subject, SortOrder::Desc) => {
@ -1120,17 +1119,13 @@ impl Threads {
let envelopes = envelopes.read().unwrap();
tree.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
let a_group = self.find_group(self.thread_nodes[&a].group);
let b_group = self.find_group(self.thread_nodes[&b].group);
let a = self.groups[&a_group].date();
let b = self.groups[&b_group].date();
let a = self.thread_ref(self.thread_nodes[&a].group).date();
let b = self.thread_ref(self.thread_nodes[&b].group).date();
b.cmp(&a)
}
(SortField::Date, SortOrder::Asc) => {
let a_group = self.find_group(self.thread_nodes[&a].group);
let b_group = self.find_group(self.thread_nodes[&b].group);
let a = self.groups[&a_group].date();
let b = self.groups[&b_group].date();
let a = self.thread_ref(self.thread_nodes[&a].group).date();
let b = self.thread_ref(self.thread_nodes[&b].group).date();
a.cmp(&b)
}
(SortField::Subject, SortOrder::Desc) => {

View File

@ -118,13 +118,14 @@ impl ListingTrait for CompactListing {
if self.length == 0 {
return;
}
let thread = self.get_thread_under_cursor(idx, context);
let thread_hash = self.get_thread_under_cursor(idx, context);
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let thread = threads.thread_ref(thread_hash);
let fg_color = if threads.groups[&thread].unseen() > 0 {
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
@ -132,9 +133,9 @@ impl ListingTrait for CompactListing {
let bg_color = if context.settings.terminal.theme == "light" {
if self.cursor_pos.2 == idx {
Color::Byte(244)
} else if self.selection[&thread] {
} else if self.selection[&thread_hash] {
Color::Byte(210)
} else if threads.groups[&thread].unseen() > 0 {
} else if thread.unseen() > 0 {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(252)
@ -144,9 +145,9 @@ impl ListingTrait for CompactListing {
} else {
if self.cursor_pos.2 == idx {
Color::Byte(246)
} else if self.selection[&thread] {
} else if self.selection[&thread_hash] {
Color::Byte(210)
} else if threads.groups[&thread].unseen() > 0 {
} else if thread.unseen() > 0 {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
@ -549,7 +550,7 @@ impl CompactListing {
threads: &Threads,
hash: ThreadHash,
) -> EntryStrings {
let thread = &threads.groups[&hash];
let thread = threads.thread_ref(hash);
let folder_hash = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.unwrap()
.folder
@ -714,7 +715,7 @@ impl CompactListing {
for (idx, thread) in items.enumerate() {
debug!(thread);
self.length += 1;
let thread_node = &threads.thread_nodes()[&threads.groups[&thread].root().unwrap()];
let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()];
let root_env_hash = thread_node.message().unwrap_or_else(|| {
let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[&iter_ptr].message().is_none() {
@ -811,13 +812,14 @@ impl CompactListing {
panic!();
}
let fg_color = if threads.groups[&thread].unseen() > 0 {
let thread = threads.thread_ref(thread);
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if context.settings.terminal.theme == "light" {
if threads.groups[&thread].unseen() > 0 {
if thread.unseen() > 0 {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(252)
@ -825,7 +827,7 @@ impl CompactListing {
Color::Default
}
} else {
if threads.groups[&thread].unseen() > 0 {
if thread.unseen() > 0 {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(236)
@ -921,7 +923,7 @@ impl CompactListing {
self.data_columns.columns[4][(x, idx)].set_bg(bg_color);
}
match (
threads.groups[&thread].snoozed(),
thread.snoozed(),
context.accounts[self.cursor_pos.0]
.collection
.get_env(root_env_hash)
@ -983,9 +985,9 @@ impl CompactListing {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
if let Some(env_hash) =
threads.thread_nodes()[&threads.groups[&thread_hash].root().unwrap()].message()
{
let thread = threads.thread_ref(thread_hash);
// FIXME: Thread root doesn't nessessarily have message set
if let Some(env_hash) = threads.thread_nodes()[&thread.root()].message() {
if !account.contains_key(env_hash) {
/* The envelope has been renamed or removed, so wait for the appropriate event to
* arrive */
@ -993,14 +995,14 @@ impl CompactListing {
}
let envelope: EnvelopeRef = account.collection.get_env(env_hash);
let has_attachments = envelope.has_attachments();
let fg_color = if threads.groups[&thread_hash].unseen() > 0 {
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
};
let idx = self.order[&thread_hash];
let bg_color = if context.settings.terminal.theme == "light" {
if threads.groups[&thread_hash].unseen() > 0 {
if thread.unseen() > 0 {
Color::Byte(251)
} else if idx % 2 == 0 {
Color::Byte(252)
@ -1008,7 +1010,7 @@ impl CompactListing {
Color::Default
}
} else {
if threads.groups[&thread_hash].unseen() > 0 {
if thread.unseen() > 0 {
Color::Byte(253)
} else if idx % 2 == 0 {
Color::Byte(236)
@ -1117,7 +1119,7 @@ impl CompactListing {
columns[4][c].set_ch(' ');
columns[4][c].set_bg(bg_color);
}
match (threads.groups[&thread_hash].snoozed(), has_attachments) {
match (thread.snoozed(), has_attachments) {
(true, true) => {
columns[3][(0, idx)].set_fg(Color::Byte(103));
columns[3][(2, idx)].set_fg(Color::Red);
@ -1246,41 +1248,45 @@ impl Component for CompactListing {
let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2, context);
self.selection.entry(thread_hash).and_modify(|e| *e = !*e);
}
UIEvent::Action(ref action) => match action {
Action::Sort(field, order) if !self.unfocused => {
debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order);
if !self.filtered_selection.is_empty() {
// FIXME: perform sort
self.dirty = true;
} else {
self.refresh_mailbox(context);
UIEvent::Action(ref action) => {
match action {
Action::Sort(field, order) if !self.unfocused => {
debug!("Sort {:?} , {:?}", field, order);
self.sort = (*field, *order);
if !self.filtered_selection.is_empty() {
// FIXME: perform sort
self.dirty = true;
} else {
self.refresh_mailbox(context);
}
return true;
}
Action::SubSort(field, order) if !self.unfocused => {
debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order);
// FIXME: perform subsort.
return true;
}
Action::ToggleThreadSnooze if !self.unfocused => {
let thread = self.get_thread_under_cursor(self.cursor_pos.2, context);
let account = &mut context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
account
.collection
.threads
.entry(folder_hash)
.and_modify(|threads| {
let is_snoozed = threads.thread_ref(thread).snoozed();
threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
});
self.row_updates.push(thread);
self.refresh_mailbox(context);
return true;
}
return true;
}
Action::SubSort(field, order) if !self.unfocused => {
debug!("SubSort {:?} , {:?}", field, order);
self.subsort = (*field, *order);
// FIXME: perform subsort.
return true;
}
Action::ToggleThreadSnooze if !self.unfocused => {
let thread = self.get_thread_under_cursor(self.cursor_pos.2, context);
let account = &mut context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = account.collection.threads.entry(folder_hash).or_default();
let is_snoozed = threads.groups[&thread].snoozed();
threads
.groups
.entry(thread)
.and_modify(|entry| entry.set_snoozed(!is_snoozed));
self.row_updates.push(thread);
self.refresh_mailbox(context);
return true;
}
_ => {}
},
_ => {}
}
}
_ => {}
}
}

View File

@ -149,22 +149,23 @@ impl ListingTrait for ConversationsListing {
if self.length == 0 {
return;
}
let thread = self.get_thread_under_cursor(idx, context);
let thread_hash = self.get_thread_under_cursor(idx, context);
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let thread = threads.thread_ref(thread_hash);
let fg_color = if threads.groups[&thread].unseen() > 0 {
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if self.cursor_pos.2 == idx {
Color::Byte(246)
} else if self.selection[&thread] {
} else if self.selection[&thread_hash] {
Color::Byte(210)
} else if threads.groups[&thread].unseen() > 0 {
} else if thread.unseen() > 0 {
Color::Byte(251)
} else {
Color::Default
@ -186,7 +187,7 @@ impl ListingTrait for ConversationsListing {
let (upper_left, bottom_right) = area;
let width = self.content.size().0;
let (x, y) = upper_left;
if self.cursor_pos.2 == idx || self.selection[&thread] {
if self.cursor_pos.2 == idx || self.selection[&thread_hash] {
for x in x..=get_x(bottom_right) {
grid[(x, y)].set_fg(fg_color);
grid[(x, y)].set_bg(bg_color);
@ -522,7 +523,7 @@ impl ConversationsListing {
threads: &Threads,
hash: ThreadHash,
) -> EntryStrings {
let thread = &threads.groups[&hash];
let thread = threads.thread_ref(hash);
let folder_hash = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.unwrap()
.folder
@ -676,7 +677,7 @@ impl ConversationsListing {
std::collections::HashSet::new();
for (idx, thread) in items.enumerate() {
self.length += 1;
let thread_node = &threads.thread_nodes()[&threads.groups[&thread].root().unwrap()];
let thread_node = &threads.thread_nodes()[&threads.thread_ref(thread).root()];
let root_env_hash = thread_node.message().unwrap_or_else(|| {
let mut iter_ptr = thread_node.children()[0];
while threads.thread_nodes()[&iter_ptr].message().is_none() {
@ -750,12 +751,13 @@ impl ConversationsListing {
if !context.accounts[self.cursor_pos.0].contains_key(root_env_hash) {
panic!();
}
let fg_color = if threads.groups[&thread].unseen() > 0 {
let thread = threads.thread_ref(thread);
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if threads.groups[&thread].unseen() > 0 {
let bg_color = if thread.unseen() > 0 {
Color::Byte(251)
} else {
Color::Default
@ -919,19 +921,18 @@ impl ConversationsListing {
let account = &context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = &account.collection.threads[&folder_hash];
let thread = threads.thread_ref(thread_hash);
let idx: usize = self.order[&thread_hash];
let width = self.content.size().0;
let env_hash = threads.thread_nodes()[&threads.groups[&thread_hash].root().unwrap()]
.message()
.unwrap();
let env_hash = threads.thread_nodes()[&thread.root()].message().unwrap();
let fg_color = if threads.groups[&thread_hash].unseen() > 0 {
let fg_color = if thread.unseen() > 0 {
Color::Byte(0)
} else {
Color::Default
};
let bg_color = if threads.groups[&thread_hash].unseen() > 0 {
let bg_color = if thread.unseen() > 0 {
Color::Byte(251)
} else {
Color::Default
@ -1262,12 +1263,14 @@ impl Component for ConversationsListing {
let thread = self.get_thread_under_cursor(self.cursor_pos.2, context);
let account = &mut context.accounts[self.cursor_pos.0];
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
let threads = account.collection.threads.entry(folder_hash).or_default();
let is_snoozed = threads.groups[&thread].snoozed();
threads
.groups
.entry(thread)
.and_modify(|entry| entry.set_snoozed(!is_snoozed));
account
.collection
.threads
.entry(folder_hash)
.and_modify(|threads| {
let is_snoozed = threads.thread_ref(thread).snoozed();
threads.thread_ref_mut(thread).set_snoozed(!is_snoozed);
});
self.row_updates.push(thread);
self.refresh_mailbox(context);
return true;

View File

@ -599,7 +599,10 @@ impl Account {
self.collection.threads[&folder_hash]
.find_group(self.collection.threads[&folder_hash][&thread_hash].group)
};
if self.collection.threads[&folder_hash].groups[&thread].snoozed() {
if self.collection.threads[&folder_hash]
.thread_ref(thread)
.snoozed()
{
return Some(UIEvent::MailboxUpdate((self.index, folder_hash)));
}
if is_seen || is_draft {