meli/melib/src/thread.rs

1622 lines
54 KiB
Rust
Raw Permalink Normal View History

/*
* meli - mailbox threading module.
*
* Copyright 2017 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//! e-mail threading (conversations)
//!
//! This module implements Jamie Zawinski's [threading
//! algorithm](https://www.jwz.org/doc/threading.html). Quoted comments (/* " .. " */) are taken
//! almost verbatim from the algorithm.
//!
//! The entry point of this module is the [`Threads`] struct and its
//! [`new`](Threads::new) method. It contains [`ThreadNode`s](ThreadNode) which
//! are the nodes in the thread trees that might have messages associated with
//! them. The root nodes (first messages in each thread) are stored in
//! [`root_set`](Threads::root_set) and [`thread_nodes`](Threads::thread_nodes)
//! vectors. [`Threads`] has inner mutability since we need to sort without the
//! user having mutable ownership.
2018-08-06 22:20:34 +03:00
2023-04-30 19:39:41 +03:00
use crate::{
email::{address::StrBuild, parser::BytesExt, *},
SortField, SortOrder, UnixTimestamp,
2023-04-30 19:39:41 +03:00
};
mod iterators;
2023-04-30 19:39:41 +03:00
use std::{
cmp::Ordering,
collections::{HashMap, HashSet, VecDeque},
2023-04-30 19:39:41 +03:00
iter::FromIterator,
ops::Index,
string::ToString,
sync::{Arc, RwLock},
};
pub use iterators::*;
2023-04-30 19:39:41 +03:00
use smallvec::SmallVec;
use uuid::Uuid;
use crate::text::grapheme_clusters::*;
2020-05-10 21:14:49 +03:00
type Envelopes = Arc<RwLock<HashMap<EnvelopeHash, Envelope>>>;
2018-10-14 19:49:16 +03:00
macro_rules! uuid_hash_type {
($n:ident) => {
#[derive(PartialEq, Hash, Eq, Copy, Clone, Serialize, Deserialize, Default)]
pub struct $n(Uuid);
2019-05-14 21:47:47 +03:00
2023-08-11 13:16:47 +03:00
impl std::fmt::Debug for $n {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0.to_string())
}
}
2023-08-11 13:16:47 +03:00
impl std::fmt::Display for $n {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.0.to_string())
}
}
impl<B: AsRef<[u8]>> From<B> for $n {
fn from(val: B) -> Self {
let val = val.as_ref();
$n(Uuid::new_v5(&Uuid::NAMESPACE_URL, val))
}
}
impl $n {
pub fn new() -> Self {
$n(Uuid::new_v4())
}
2023-07-01 16:34:06 +03:00
pub const fn null() -> Self {
$n(Uuid::nil())
}
}
};
2019-05-14 21:47:47 +03:00
}
uuid_hash_type!(ThreadNodeHash);
uuid_hash_type!(ThreadHash);
/* Helper macros to avoid repeating ourselves */
macro_rules! remove_from_parent {
2019-05-14 21:47:47 +03:00
($buf:expr, $idx:expr) => {{
let mut parent: Option<ThreadNodeHash> = None;
2019-06-18 21:13:58 +03:00
let entry_parent = $buf.entry($idx).or_default().parent;
if let Some(p) = entry_parent {
parent = Some(p);
2019-05-14 21:47:47 +03:00
if let Some(pos) = $buf[&p].children.iter().position(|c| *c == $idx) {
$buf.entry(p).and_modify(|e| {
e.children.remove(pos);
});
}
}
2019-05-14 21:47:47 +03:00
$buf.entry($idx).and_modify(|e| e.parent = None);
parent
2019-05-14 21:47:47 +03:00
}};
}
macro_rules! make {
(($p:expr)parent of($c:expr), $threads:expr) => {{
let old_group_hash = $threads.find_group($threads.thread_nodes[&$c].group);
let parent_group_hash = $threads.find_group($threads.thread_nodes[&$p].group);
if old_group_hash != parent_group_hash {
2023-04-30 19:39:41 +03:00
if let Some(old_env_hashes) = $threads.thread_to_envelope.get(&old_group_hash).cloned()
{
for &env_hash in &old_env_hashes {
*$threads.envelope_to_thread.entry(env_hash).or_default() = parent_group_hash;
}
2023-04-30 19:39:41 +03:00
$threads
.thread_to_envelope
.entry(parent_group_hash)
.or_default()
.extend(old_env_hashes.into_iter());
}
let prev_parent = remove_from_parent!(&mut $threads.thread_nodes, $c);
if !($threads.thread_nodes[&$p]).children.contains(&$c) {
2023-04-30 19:39:41 +03:00
/* Pruned nodes keep their children in case they show up in a later merge, so
* do not panic if children exists */
$threads
.thread_nodes
.entry($p)
.and_modify(|e| e.children.push($c));
}
$threads.thread_nodes.entry($c).and_modify(|e| {
e.parent = Some($p);
});
2023-04-30 19:39:41 +03:00
let old_group = std::mem::replace(
$threads.groups.entry(old_group_hash).or_default(),
ThreadGroup::Node {
parent: Arc::new(RwLock::new(parent_group_hash)),
},
);
$threads.thread_nodes.entry($c).and_modify(|e| {
e.group = parent_group_hash;
});
$threads.thread_nodes.entry($p).and_modify(|e| {
e.group = parent_group_hash;
});
{
let parent_group = $threads.thread_ref_mut(parent_group_hash);
match (parent_group, old_group) {
2023-04-30 19:39:41 +03:00
(
Thread {
ref mut date,
ref mut len,
ref mut unseen,
ref mut snoozed,
ref mut attachments,
..
},
ThreadGroup::Root(Thread {
date: old_date,
len: old_len,
unseen: old_unseen,
snoozed: old_snoozed,
attachments: old_attachments,
..
}),
) => {
*date = std::cmp::max(old_date, *date);
*len += old_len;
*unseen += old_unseen;
*attachments += old_attachments;
*snoozed |= old_snoozed;
}
_ => unreachable!(),
2023-04-30 19:39:41 +03:00
}
}
prev_parent
} else {
None
}
2023-04-30 19:39:41 +03:00
}};
}
/// Strip common prefixes from subjects
///
///
/// ```rust
/// use melib::thread::SubjectPrefix;
///
/// let mut subject = "Re: RE: Res: Re: Res: Subject";
2023-04-30 19:39:41 +03:00
/// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, None),
/// &"Subject"
/// );
/// let mut subject = "Re: RE: Res: Re: Res: Subject";
2023-04-30 19:39:41 +03:00
/// assert_eq!(
/// subject.strip_prefixes_from_list(<&str>::USUAL_PREFIXES, Some(1)),
/// &"RE: Res: Re: Res: Subject"
/// );
/// ```
pub trait SubjectPrefix {
const USUAL_PREFIXES: &'static [&'static str] = &[
"Re:",
// Canada (Réponse)
// Spanish (Respuesta)
"RE:",
"Fwd:",
"Fw:",
/* taken from
* https://en.wikipedia.org/wiki/List_of_email_subject_abbreviations#Abbreviations_in_other_languages
2023-04-30 19:39:41 +03:00
*/
"回复:",
"回覆:",
// Dutch (Antwoord)
"Antw:",
// Dutch (Doorsturen)
"Doorst:",
// Finnish (Välitetty)
"VL:",
// French (Référence)
"REF:",
// French (Transfert)
// Canada (Transfert)
"TR:",
// German (Antwort)
"AW:",
// German (Weitergeleitet)
"WG:",
// Greek (Απάντηση)
"ΑΠ:",
"Απ:",
"απ:",
// Greek (Προωθημένο)
"ΠΡΘ:",
"Πρθ:",
"πρθ:",
// Greek (Σχετικό)
"ΣΧΕΤ:",
"Σχετ:",
"σχετ:",
// Greek (Προωθημένο)
"ΠΡΘ:",
"Πρθ:",
"πρθ:",
// Hungarian (Válasz)
"Vá:",
// Hungarian
"Továbbítás:",
// Italian (Riferimento)
"R:",
// Italian (Inoltro)
"I:",
// Italian (Riferimento)
"RIF:",
// Icelandic (Svara)
// Swedish (Svar)
// Norwegian (Svar)
// Danish (Svar)
"SV:",
"Sv:",
// Icelandic (Framsenda)
"FS:",
"Fs:",
// Indonesian (Balas)
"BLS:",
// Indonesian (Terusan)
"TRS:",
// Norwegian (Videresendt)
// Danish (Videresendt)
// Finnish (Vastaus)
"VS:",
"Vs:",
// Swedish (Vidarebefordrat)
"VB:",
"Vb:",
// Spanish (Reenviado)
"RV:",
"Rv:",
// Portuguese (Resposta)
"RES:",
"Res:",
// Portuguese (Encaminhado)
"ENC:",
// Polish (Odpowiedź)
"Odp:",
// Polish (Podaj dalej)
"PD:",
// Turkish (Yanıt)
"YNT:",
// Turkish (İlet)
"İLT:",
// Welsh (Ateb)
"ATB:",
// Welsh (Ymlaen)
"YML:",
];
2019-04-04 15:06:48 +03:00
fn is_a_reply(&self) -> bool;
fn strip_prefixes(&mut self) -> &mut Self;
fn strip_prefixes_from_list(&mut self, list: &[&str], times: Option<u8>) -> &mut Self;
}
2019-04-04 15:06:48 +03:00
impl SubjectPrefix for &[u8] {
fn is_a_reply(&self) -> bool {
let self_ = self.trim();
self_.starts_with(b"RE: ")
|| self_.starts_with(b"Re: ")
|| self_.starts_with(b"RES: ")
|| self_.starts_with(b"Res: ")
|| self_.starts_with(b"FW: ")
|| self_.starts_with(b"Fw: ")
2019-04-04 15:06:48 +03:00
}
fn strip_prefixes(&mut self) -> &mut Self {
let result = {
let mut slice = self.trim();
loop {
2018-09-17 07:53:16 +03:00
if slice.starts_with(b"RE: ")
|| slice.starts_with(b"Re: ")
|| slice.starts_with(b"FW: ")
|| slice.starts_with(b"Fw: ")
{
slice = &slice[b"RE: ".len()..];
continue;
}
2018-09-17 07:53:16 +03:00
if slice.starts_with(b"FWD: ")
|| slice.starts_with(b"Fwd: ")
|| slice.starts_with(b"fwd: ")
{
slice = &slice[b"FWD: ".len()..];
continue;
}
2018-09-17 07:53:16 +03:00
if slice.starts_with(b" ") || slice.starts_with(b"\t") || slice.starts_with(b"\r") {
// [ref:FIXME]: just trim whitespace
slice = &slice[b" ".len()..];
continue;
}
2018-09-17 07:53:16 +03:00
if slice.starts_with(b"[")
&& !(slice.starts_with(b"[PATCH") || slice.starts_with(b"[RFC"))
{
2018-09-17 07:53:16 +03:00
if let Some(pos) = slice.find(b"]") {
slice = &slice[pos + 1..];
continue;
}
}
break;
}
2018-09-17 07:53:16 +03:00
slice
};
2018-09-17 07:53:16 +03:00
*self = result;
self
}
fn strip_prefixes_from_list(&mut self, list: &[&str], mut times: Option<u8>) -> &mut Self {
let result = {
let mut slice = self.trim();
'outer: loop {
let len = slice.len();
for prefix in list.iter() {
if slice
.get(0..prefix.as_bytes().len())
.map(|p| p.eq_ignore_ascii_case(prefix.as_bytes()))
.unwrap_or(false)
{
slice = &slice[prefix.len()..];
slice = slice.trim();
times = times.map(|u| u.saturating_sub(1));
if times == Some(0) {
break 'outer;
}
}
}
if slice.len() == len || times == Some(0) {
break;
}
}
slice
};
*self = result;
self
}
}
impl SubjectPrefix for &str {
fn is_a_reply(&self) -> bool {
self.as_bytes().is_a_reply()
}
fn strip_prefixes(&mut self) -> &mut Self {
let result = {
let mut slice = self.trim();
loop {
if slice.starts_with("RE: ")
|| slice.starts_with("Re: ")
|| slice.starts_with("FW: ")
|| slice.starts_with("Fw: ")
{
slice = &slice["RE: ".len()..];
continue;
}
if slice.starts_with("FWD: ")
|| slice.starts_with("Fwd: ")
|| slice.starts_with("fwd: ")
{
slice = &slice["FWD: ".len()..];
continue;
}
2022-09-18 00:09:23 +03:00
if slice.starts_with(' ') || slice.starts_with('\t') || slice.starts_with('\r') {
// [ref:FIXME]: just trim whitespace
slice = &slice[1..];
continue;
}
2022-09-18 00:09:23 +03:00
if slice.starts_with('[')
&& !(slice.starts_with("[PATCH") || slice.starts_with("[RFC"))
{
2022-09-18 00:09:23 +03:00
if let Some(pos) = slice.find(']') {
slice = &slice[pos + 1..];
continue;
}
}
break;
}
slice
};
*self = result;
self
}
fn strip_prefixes_from_list(&mut self, list: &[&str], mut times: Option<u8>) -> &mut Self {
let result = {
let mut slice = self.trim();
'outer: loop {
let len = slice.len();
for prefix in list.iter() {
if slice
.get(0..prefix.as_bytes().len())
.map(|p| p.eq_ignore_ascii_case(prefix))
.unwrap_or(false)
{
slice = &slice[prefix.len()..];
slice = slice.trim();
times = times.map(|u| u.saturating_sub(1));
if times == Some(0) {
break 'outer;
}
}
}
if slice.len() == len || times == Some(0) {
break;
}
}
slice
};
*self = result;
self
}
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Thread {
pub root: ThreadNodeHash,
pub date: UnixTimestamp,
pub len: usize,
pub unseen: usize,
pub attachments: usize,
pub snoozed: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum ThreadGroup {
Root(Thread),
Node { parent: Arc<RwLock<ThreadHash>> },
}
impl Default for ThreadGroup {
fn default() -> Self {
2023-07-01 16:34:06 +03:00
Self::Root(Thread::default())
}
}
impl ThreadGroup {
pub fn root(&self) -> Option<&Thread> {
2023-07-01 16:34:06 +03:00
if let Self::Root(ref root) = self {
Some(root)
} else {
None
}
}
2018-10-14 19:49:16 +03:00
}
macro_rules! property {
($name:ident: $t:ty) => {
pub fn $name(&self) -> $t {
(self.$name).into()
2018-10-14 19:49:16 +03:00
}
2020-07-05 15:28:55 +03:00
};
2018-10-14 19:49:16 +03:00
}
impl Thread {
property!(root: ThreadNodeHash);
property!(len: usize);
property!(unseen: usize);
property!(snoozed: bool);
property!(date: UnixTimestamp);
pub fn has_attachments(&self) -> bool {
self.attachments > 0
}
2022-11-14 19:14:19 +02:00
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn set_snoozed(&mut self, val: bool) {
self.snoozed = val;
2018-09-08 18:04:16 +03:00
}
}
2018-09-17 07:53:16 +03:00
#[derive(Clone, Debug, Deserialize, Serialize)]
2018-09-06 13:05:35 +03:00
pub struct ThreadNode {
pub message: Option<EnvelopeHash>,
pub other_mailbox: bool,
pub parent: Option<ThreadNodeHash>,
pub children: Vec<ThreadNodeHash>,
pub date: UnixTimestamp,
pub show_subject: bool,
pub group: ThreadHash,
pub unseen: bool,
}
2018-09-06 13:05:35 +03:00
impl Default for ThreadNode {
2023-07-01 16:34:06 +03:00
fn default() -> Self {
Self {
message: None,
parent: None,
other_mailbox: false,
2018-09-06 13:05:35 +03:00
children: Vec::new(),
date: UnixTimestamp::default(),
show_subject: true,
group: ThreadHash::new(),
unseen: false,
}
}
}
2018-09-06 13:05:35 +03:00
impl ThreadNode {
fn new() -> Self {
2023-07-01 16:34:06 +03:00
Self::default()
2019-05-14 21:47:47 +03:00
}
2018-09-06 13:05:35 +03:00
pub fn show_subject(&self) -> bool {
self.show_subject
}
2018-08-14 17:36:45 +03:00
pub fn unseen(&self) -> bool {
self.unseen
2018-08-14 17:36:45 +03:00
}
pub fn set_unseen(&mut self, new_val: bool) {
self.unseen = new_val;
2018-09-06 13:05:35 +03:00
}
2018-08-14 17:36:45 +03:00
pub fn date(&self) -> UnixTimestamp {
self.date
}
pub fn is_empty(&self) -> bool {
self.parent.is_none() && self.message.is_none() && self.children.is_empty()
2018-09-06 13:05:35 +03:00
}
2018-09-06 13:05:35 +03:00
pub fn message(&self) -> Option<EnvelopeHash> {
self.message
2018-08-14 17:36:45 +03:00
}
2018-09-06 13:05:35 +03:00
pub fn has_message(&self) -> bool {
self.message.is_some()
}
2018-08-14 17:36:45 +03:00
pub fn parent(&self) -> Option<ThreadNodeHash> {
2018-09-06 13:05:35 +03:00
self.parent
}
pub fn has_parent(&self) -> bool {
self.parent.is_some()
}
pub fn children(&self) -> &[ThreadNodeHash] {
2018-09-06 13:05:35 +03:00
&self.children
2018-08-14 17:36:45 +03:00
}
}
2018-09-17 07:53:16 +03:00
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
2018-09-06 13:05:35 +03:00
pub struct Threads {
2020-05-10 21:14:49 +03:00
pub thread_nodes: HashMap<ThreadNodeHash, ThreadNode>,
root_set: Arc<RwLock<Vec<ThreadNodeHash>>>,
tree_index: Arc<RwLock<Vec<ThreadNodeHash>>>,
2020-05-10 21:14:49 +03:00
pub groups: HashMap<ThreadHash, ThreadGroup>,
2018-09-06 13:05:35 +03:00
2020-05-10 21:14:49 +03:00
message_ids: HashMap<Vec<u8>, ThreadNodeHash>,
pub message_ids_set: HashSet<Vec<u8>>,
pub missing_message_ids: HashSet<Vec<u8>>,
pub hash_set: HashSet<EnvelopeHash>,
pub thread_to_envelope: HashMap<ThreadHash, Vec<EnvelopeHash>>,
pub envelope_to_thread: HashMap<EnvelopeHash, ThreadHash>,
sort: Arc<RwLock<(SortField, SortOrder)>>,
subsort: Arc<RwLock<(SortField, SortOrder)>>,
}
2018-09-06 13:05:35 +03:00
impl PartialEq for ThreadNode {
2023-07-01 16:34:06 +03:00
fn eq(&self, other: &Self) -> bool {
2018-09-06 13:05:35 +03:00
match (self.message, other.message) {
(Some(s), Some(o)) => s == o,
_ => false,
}
}
}
2018-08-14 17:36:45 +03:00
2022-11-14 19:14:19 +02:00
impl Eq for ThreadNode {}
2018-08-14 17:36:45 +03:00
impl Threads {
pub fn is_snoozed(&self, h: ThreadNodeHash) -> bool {
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::Root(_) => return h,
ThreadGroup::Node { ref parent } => *parent.read().unwrap(),
};
let parent_group = self.find_group(p);
match self.groups[&h] {
ThreadGroup::Node { ref parent } => {
*parent.write().unwrap() = parent_group;
}
_ => unreachable!(),
}
parent_group
}
2023-07-01 16:34:06 +03:00
pub fn new(length: usize) -> Self {
2018-09-06 13:05:35 +03:00
/* To reconstruct thread information from the mails we need: */
/* a vector to hold thread members */
2020-05-10 21:14:49 +03:00
let thread_nodes: HashMap<ThreadNodeHash, ThreadNode> =
HashMap::with_capacity_and_hasher((length as f64 * 1.2) as usize, Default::default());
2018-09-06 13:05:35 +03:00
/* A hash table of Message IDs */
2020-05-10 21:14:49 +03:00
let message_ids: HashMap<Vec<u8>, ThreadNodeHash> =
HashMap::with_capacity_and_hasher(length, Default::default());
/* A hash set of Message IDs we haven't encountered yet as an Envelope */
2020-05-10 21:14:49 +03:00
let missing_message_ids: HashSet<Vec<u8>> =
HashSet::with_capacity_and_hasher(length, Default::default());
/* A hash set of Message IDs we have encountered as a MessageID */
2020-05-10 21:14:49 +03:00
let message_ids_set: HashSet<Vec<u8>> =
HashSet::with_capacity_and_hasher(length, Default::default());
let hash_set: HashSet<EnvelopeHash> =
HashSet::with_capacity_and_hasher(length, Default::default());
let thread_to_envelope: HashMap<ThreadHash, Vec<EnvelopeHash>> =
HashMap::with_capacity_and_hasher(length, Default::default());
let envelope_to_thread: HashMap<EnvelopeHash, ThreadHash> =
HashMap::with_capacity_and_hasher(length, Default::default());
2023-07-01 16:34:06 +03:00
Self {
thread_nodes,
message_ids,
message_ids_set,
missing_message_ids,
hash_set,
thread_to_envelope,
envelope_to_thread,
sort: Arc::new(RwLock::new((SortField::Date, SortOrder::Desc))),
subsort: Arc::new(RwLock::new((SortField::Subject, SortOrder::Desc))),
..Default::default()
}
2018-10-14 19:49:16 +03:00
}
pub fn threads_iter(&self, root_tree: SmallVec<[ThreadNodeHash; 1024]>) -> ThreadsIterator {
ThreadsIterator {
root_tree,
2018-10-14 19:49:16 +03:00
pos: 0,
stack: SmallVec::new(),
thread_nodes: &self.thread_nodes,
2018-09-08 18:04:16 +03:00
}
}
pub fn thread_iter(&self, index: ThreadHash) -> ThreadIterator {
ThreadIterator {
group: self.thread_ref(index).root(),
pos: 0,
stack: SmallVec::new(),
thread_nodes: &self.thread_nodes,
}
}
2023-07-01 16:34:06 +03:00
#[allow(clippy::result_unit_err)]
2018-10-14 19:49:16 +03:00
pub fn update_envelope(
&mut self,
envelopes: &Envelopes,
2018-10-14 19:49:16 +03:00
old_hash: EnvelopeHash,
new_hash: EnvelopeHash,
) -> Result<(), ()> {
/* must update:
* - hash_set
* - message fields in thread_nodes
*/
let thread_node_hash = if let Some((key, _)) = self
.thread_nodes
.iter()
.find(|(_, n)| n.message.map(|n| n == old_hash).unwrap_or(false))
2018-10-14 19:49:16 +03:00
{
*key
2018-10-14 19:49:16 +03:00
} else {
return Err(());
};
self.thread_nodes
.get_mut(&thread_node_hash)
.unwrap()
.message = Some(new_hash);
let was_unseen = self.thread_nodes[&thread_node_hash].unseen;
let is_unseen = !envelopes.read().unwrap()[&new_hash].is_seen();
if was_unseen != is_unseen {
2020-01-27 17:32:12 +02:00
let Thread { ref mut unseen, .. } =
self.thread_ref_mut(self.thread_nodes[&thread_node_hash].group);
2020-01-27 17:32:12 +02:00
if was_unseen {
*unseen -= 1;
} else {
*unseen += 1;
}
}
self.thread_nodes.get_mut(&thread_node_hash).unwrap().unseen = is_unseen;
self.hash_set.remove(&old_hash);
2018-10-14 19:49:16 +03:00
self.hash_set.insert(new_hash);
let thread_hash = self.envelope_to_thread.remove(&old_hash).unwrap();
self.thread_to_envelope
.entry(thread_hash)
.or_default()
.retain(|h| *h != old_hash);
self.thread_to_envelope
.entry(thread_hash)
.or_default()
.push(new_hash);
*self.envelope_to_thread.entry(new_hash).or_default() = thread_hash;
2018-10-14 19:49:16 +03:00
Ok(())
}
2018-10-14 19:49:16 +03:00
#[inline]
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
2018-10-14 19:49:16 +03:00
self.hash_set.remove(&envelope_hash);
let t_id: ThreadNodeHash = if let Some((pos, n)) = self
.thread_nodes
.iter_mut()
.find(|(_, n)| n.message.map(|n| n == envelope_hash).unwrap_or(false))
2018-10-14 19:49:16 +03:00
{
n.message = None;
*pos
} else {
return;
};
if self.thread_nodes[&t_id].parent.is_none() {
let mut tree_index = self.tree_index.write().unwrap();
if let Some(i) = tree_index.iter().position(|t| *t == t_id) {
tree_index.remove(i);
2018-10-14 19:49:16 +03:00
}
}
if let Some((message_id, _)) = self.message_ids.iter().find(|(_, h)| **h == t_id) {
self.missing_message_ids.insert(message_id.to_vec());
}
2018-10-14 19:49:16 +03:00
}
pub fn amend(&mut self, envelopes: &Envelopes) {
let envelopes_lck = envelopes.read().unwrap();
2020-05-10 21:14:49 +03:00
let new_hash_set = HashSet::from_iter(envelopes_lck.keys().cloned());
2018-09-07 15:36:42 +03:00
2018-10-14 19:49:16 +03:00
let difference: Vec<EnvelopeHash> =
self.hash_set.difference(&new_hash_set).cloned().collect();
for h in difference {
self.remove(h);
2018-10-14 19:49:16 +03:00
}
drop(envelopes_lck);
2018-10-14 19:49:16 +03:00
2018-09-07 15:36:42 +03:00
let difference: Vec<EnvelopeHash> =
new_hash_set.difference(&self.hash_set).cloned().collect();
for h in difference {
//debug!("inserting {}", envelopes_lck[&h].subject());
self.insert(envelopes, h);
2018-09-07 15:36:42 +03:00
}
}
/// Update `show_subject` details of [`ThreadNode`].
pub fn update_show_subject(
&mut self,
id: ThreadNodeHash,
env_hash: EnvelopeHash,
envelopes: &Envelopes,
) {
let mut stack = VecDeque::new();
stack.push_back((id, Some(env_hash)));
let envelopes = envelopes.read().unwrap();
while let Some((id, env_hash)) = stack.pop_front() {
if let Some(env_hash) = env_hash {
let mut subject = envelopes[&env_hash].subject();
let mut subject = subject.to_mut().as_bytes();
let stripped_subject = subject.strip_prefixes();
{
// check immediate parent envelope, skipping thread nodes that have no
// corresponding envelope
let mut parent_cursor = self.thread_nodes[&id].parent;
while let Some(parent_id) = parent_cursor {
if let Some(parent_hash) = self.thread_nodes[&parent_id].message {
debug_assert!(envelopes.contains_key(&parent_hash));
/* decide if the subject should be shown in the UI.
* If parent subject is Foobar and reply is `Re: Foobar`
* then showing the reply's subject is redundant
*/
let mut parent_subject = envelopes[&parent_hash].subject();
let mut parent_subject = parent_subject.to_mut().as_bytes();
parent_subject.strip_prefixes();
if stripped_subject == &parent_subject
|| stripped_subject.ends_with(parent_subject)
{
self.thread_nodes.entry(id).and_modify(|e| {
e.show_subject = false;
});
}
break;
} else {
parent_cursor = self.thread_nodes[&parent_id].parent;
}
}
}
for i in 0..self.thread_nodes[&id].children.len() {
let child_hash = self.thread_nodes[&id].children[i];
stack.push_back((child_hash, self.thread_nodes[&child_hash].message()));
if let Some(child_env_hash) = self.thread_nodes[&child_hash].message() {
let mut child_subject = envelopes[&child_env_hash].subject();
let mut child_subject = child_subject.to_mut().as_bytes();
child_subject.strip_prefixes();
if stripped_subject == &child_subject
|| child_subject.ends_with(stripped_subject)
{
self.thread_nodes.entry(child_hash).and_modify(|e| {
e.show_subject = false;
});
}
}
}
} else {
for i in 0..self.thread_nodes[&id].children.len() {
let child_hash = self.thread_nodes[&id].children[i];
stack.push_back((child_hash, self.thread_nodes[&child_hash].message()));
}
}
}
2018-09-07 15:36:42 +03:00
}
pub fn insert(&mut self, envelopes: &Envelopes, env_hash: EnvelopeHash) {
self.insert_internal(envelopes, env_hash, false);
}
fn insert_internal(
&mut self,
envelopes: &Envelopes,
env_hash: EnvelopeHash,
other_mailbox: bool,
) -> bool {
if self.hash_set.contains(&env_hash) {
return true;
}
{
let envelopes_lck = envelopes.read().unwrap();
if !envelopes_lck.contains_key(&env_hash) {
return false;
}
let message_id = envelopes_lck[&env_hash].message_id().raw();
if self.message_ids.contains_key(message_id)
&& !self.missing_message_ids.contains(message_id)
{
let thread_hash = self.message_ids[message_id];
let node = self.thread_nodes.entry(thread_hash).or_default();
drop(envelopes_lck);
envelopes
.write()
.unwrap()
.get_mut(&env_hash)
.unwrap()
.set_thread(thread_hash);
2023-04-30 19:39:41 +03:00
/* If thread node currently has a message from a foreign mailbox and env_hash
* is from current mailbox we want to update it, otherwise
* return */
if node.other_mailbox || other_mailbox {
return false;
}
}
}
let envelopes_lck = envelopes.read().unwrap();
let message_id = envelopes_lck[&env_hash].message_id().raw();
let reply_to_id: Option<ThreadNodeHash> = envelopes_lck[&env_hash]
.in_reply_to()
.map(StrBuild::raw)
.filter(|irt| irt != &message_id)
.and_then(|r| self.message_ids.get(r).cloned());
if other_mailbox
&& reply_to_id.is_none()
&& !self.message_ids.contains_key(message_id)
&& !envelopes_lck[&env_hash]
.references()
.iter()
.any(|r| self.message_ids.contains_key(r.raw()))
{
return false;
}
let new_id = self
.message_ids
.get(message_id)
.cloned()
2020-03-12 09:47:39 +02:00
.or(
if envelopes_lck[&env_hash].thread() != ThreadNodeHash::null() {
Some(envelopes_lck[&env_hash].thread())
} else {
None
},
)
.unwrap_or_else(|| ThreadNodeHash::from(message_id));
{
2023-07-19 17:12:15 +03:00
let node = self.thread_nodes.entry(new_id).or_default();
node.message = Some(env_hash);
if node.parent.is_none() {
node.parent = reply_to_id;
}
node.other_mailbox = other_mailbox;
node.date = envelopes_lck[&env_hash].date();
node.unseen = !envelopes_lck[&env_hash].is_seen();
}
let thread_hash = self.thread_nodes[&new_id].group;
2023-07-01 16:34:06 +03:00
if let std::collections::hash_map::Entry::Vacant(e) = self.groups.entry(thread_hash) {
e.insert(ThreadGroup::Root(Thread {
root: new_id,
date: envelopes_lck[&env_hash].date(),
len: 1,
unseen: if !envelopes_lck[&env_hash].is_seen() {
1
} else {
0
},
attachments: if envelopes_lck[&env_hash].has_attachments() {
1
} else {
0
},
snoozed: false,
}));
} else {
let parent_group = self.thread_ref_mut(thread_hash);
parent_group.date = std::cmp::max(parent_group.date, envelopes_lck[&env_hash].date());
parent_group.len += 1;
parent_group.unseen += if !envelopes_lck[&env_hash].is_seen() {
1
} else {
0
};
parent_group.attachments += if envelopes_lck[&env_hash].has_attachments() {
1
} else {
0
};
}
self.message_ids.insert(message_id.to_vec(), new_id);
self.message_ids_set.insert(message_id.to_vec());
self.missing_message_ids.remove(message_id);
self.hash_set.insert(env_hash);
self.thread_to_envelope
.entry(thread_hash)
.or_default()
.push(env_hash);
*self.envelope_to_thread.entry(env_hash).or_default() = thread_hash;
if let Some(reply_to_id) = reply_to_id {
make!((reply_to_id) parent of (new_id), self);
} else if let Some(r) = envelopes_lck[&env_hash]
.in_reply_to()
.map(StrBuild::raw)
.filter(|irt| irt != &message_id)
{
let reply_to_id = ThreadNodeHash::from(r);
self.thread_nodes.insert(
reply_to_id,
ThreadNode {
date: envelopes_lck[&env_hash].date(),
..ThreadNode::new()
},
);
self.groups.insert(
self.thread_nodes[&reply_to_id].group,
ThreadGroup::Root(Thread {
root: reply_to_id,
date: envelopes_lck[&env_hash].date(),
len: 0,
unseen: 0,
attachments: 0,
snoozed: false,
}),
);
make!((reply_to_id) parent of (new_id), self);
self.message_ids.insert(r.to_vec(), reply_to_id);
self.message_ids_set.insert(r.to_vec());
self.missing_message_ids.insert(r.to_vec());
}
if envelopes_lck[&env_hash].references.is_some() {
let mut current_descendant_id = new_id;
let mut references = envelopes_lck[&env_hash].references();
if references.first().filter(|irt| irt.raw() != message_id)
== envelopes_lck[&env_hash].in_reply_to().as_ref()
{
references.reverse();
}
for reference in references.into_iter().rev() {
if reference.raw() == message_id {
continue;
}
if let Some(&id) = self.message_ids.get(reference.raw()) {
if self.thread_nodes[&id].date > self.thread_nodes[&current_descendant_id].date
|| self.thread_nodes[&current_descendant_id].parent.is_some()
{
current_descendant_id = id;
continue;
}
make!((id) parent of (current_descendant_id), self);
current_descendant_id = id;
} else {
let id = ThreadNodeHash::from(reference.raw());
self.thread_nodes.insert(
id,
ThreadNode {
date: envelopes_lck[&env_hash].date(),
..ThreadNode::new()
},
);
self.groups.insert(
self.thread_nodes[&id].group,
ThreadGroup::Root(Thread {
root: id,
date: envelopes_lck[&env_hash].date(),
len: 0,
unseen: 0,
attachments: 0,
snoozed: false,
}),
);
make!((id) parent of (current_descendant_id), self);
self.missing_message_ids.insert(reference.raw().to_vec());
self.message_ids.insert(reference.raw().to_vec(), id);
self.message_ids_set.insert(reference.raw().to_vec());
current_descendant_id = id;
}
}
2018-09-17 07:53:16 +03:00
}
drop(envelopes_lck);
self.update_show_subject(new_id, env_hash, envelopes);
envelopes
.write()
.unwrap()
.get_mut(&env_hash)
.unwrap()
.set_thread(new_id);
/*
save_graph(
&self.tree_index.read().unwrap(),
&self.thread_nodes,
&self
.message_ids
.iter()
.map(|(a, &b)| (b, a.to_vec()))
2020-05-10 21:14:49 +03:00
.collect::<HashMap<ThreadNodeHash, Vec<u8>>>(),
&envelopes,
);
*/
true
2018-09-07 10:16:36 +03:00
}
/* Insert or update */
pub fn insert_reply(&mut self, envelopes: &Envelopes, env_hash: EnvelopeHash) -> bool {
self.insert_internal(envelopes, env_hash, true)
}
fn inner_subsort_by(&self, _subsort: (SortField, SortOrder), _envelopes: &Envelopes) {
// [ref:FIXME]: self\.thread_nodes needs interior mutability */
/*
let Threads {
ref tree_index,
ref thread_nodes,
..
} = self;
let tree = &mut tree_index.write().unwrap();
2019-04-04 15:06:48 +03:00
for t in tree.iter_mut() {
thread_nodes[t].children.sort_by(|a, b| match subsort {
2018-09-08 18:04:16 +03:00
(SortField::Date, SortOrder::Desc) => {
let a = &thread_nodes[&a];
let b = &thread_nodes[&b];
2018-09-08 18:04:16 +03:00
b.date.cmp(&a.date)
}
(SortField::Date, SortOrder::Asc) => {
let a = &thread_nodes[&a];
let b = &thread_nodes[&b];
2018-09-08 18:04:16 +03:00
a.date.cmp(&b.date)
}
(SortField::Subject, SortOrder::Desc) => {
let a = &thread_nodes[&a].message();
let b = &thread_nodes[&b].message();
2018-09-08 18:04:16 +03:00
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Greater;
}
(None, Some(_)) => {
return Ordering::Less;
}
(None, None) => {
return Ordering::Equal;
}
2018-09-08 18:04:16 +03:00
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
2018-09-08 18:04:16 +03:00
ma.subject().cmp(&mb.subject())
}
(SortField::Subject, SortOrder::Asc) => {
let a = &thread_nodes[&a].message();
let b = &thread_nodes[&b].message();
2018-09-08 18:04:16 +03:00
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Less;
}
(None, Some(_)) => {
return Ordering::Greater;
}
(None, None) => {
return Ordering::Equal;
}
2018-09-08 18:04:16 +03:00
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
2018-09-08 18:04:16 +03:00
mb.subject().cmp(&ma.subject())
}
});
}
*/
2018-09-06 13:05:35 +03:00
}
2018-08-14 17:36:45 +03:00
pub fn group_inner_sort_by(
2019-10-12 18:03:58 +03:00
&self,
vec: &mut [ThreadHash],
2019-10-12 18:03:58 +03:00
sort: (SortField, SortOrder),
envelopes: &Envelopes,
) {
let envelopes = envelopes.read().unwrap();
vec.sort_by(|a, b| match sort {
2019-10-12 18:03:58 +03:00
(SortField::Date, SortOrder::Desc) => {
let a = self.thread_ref(*a).date();
let b = self.thread_ref(*b).date();
b.cmp(&a)
2019-10-12 18:03:58 +03:00
}
(SortField::Date, SortOrder::Asc) => {
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.thread_ref(*a).root()].message();
let b = &self.thread_nodes[&self.thread_ref(*b).root()].message();
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Greater;
}
(None, Some(_)) => {
return Ordering::Less;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
ma.subject()
.split_graphemes()
.cmp(&mb.subject().split_graphemes())
}
}
(SortField::Subject, SortOrder::Asc) => {
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(_)) => {}
(Some(_), None) => {
return Ordering::Less;
}
(None, Some(_)) => {
return Ordering::Greater;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
mb.subject()
.as_ref()
.split_graphemes()
.cmp(&ma.subject().split_graphemes())
}
}
});
}
pub fn node_inner_sort_by(
&self,
vec: &mut [ThreadNodeHash],
sort: (SortField, SortOrder),
envelopes: &Envelopes,
) {
let envelopes = envelopes.read().unwrap();
vec.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
2022-09-18 00:09:23 +03:00
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) => {
2022-09-18 00:09:23 +03:00
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)
2019-10-12 18:03:58 +03:00
}
(SortField::Subject, SortOrder::Desc) => {
2022-09-18 00:09:23 +03:00
let a = &self.thread_nodes[a].message();
let b = &self.thread_nodes[b].message();
2019-10-12 18:03:58 +03:00
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Greater;
}
(None, Some(_)) => {
return Ordering::Less;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
ma.subject()
.split_graphemes()
.cmp(&mb.subject().split_graphemes())
}
}
(SortField::Subject, SortOrder::Asc) => {
2022-09-18 00:09:23 +03:00
let a = &self.thread_nodes[a].message();
let b = &self.thread_nodes[b].message();
2019-10-12 18:03:58 +03:00
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Less;
}
(None, Some(_)) => {
return Ordering::Greater;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
mb.subject()
.as_ref()
.split_graphemes()
.cmp(&ma.subject().split_graphemes())
}
}
});
}
fn inner_sort_by(&self, sort: (SortField, SortOrder), envelopes: &Envelopes) {
let tree = &mut self.tree_index.write().unwrap();
let envelopes = envelopes.read().unwrap();
tree.sort_by(|a, b| match sort {
(SortField::Date, SortOrder::Desc) => {
2022-09-18 00:09:23 +03:00
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) => {
2022-09-18 00:09:23 +03:00
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) => {
2022-09-18 00:09:23 +03:00
let a = &self.thread_nodes[a].message();
let b = &self.thread_nodes[b].message();
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Greater;
}
(None, Some(_)) => {
return Ordering::Less;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
ma.subject()
.split_graphemes()
.cmp(&mb.subject().split_graphemes())
}
}
(SortField::Subject, SortOrder::Asc) => {
2022-09-18 00:09:23 +03:00
let a = &self.thread_nodes[a].message();
let b = &self.thread_nodes[b].message();
match (a, b) {
(Some(_), Some(_)) => {}
(Some(_), None) => {
return Ordering::Less;
}
(None, Some(_)) => {
return Ordering::Greater;
}
(None, None) => {
return Ordering::Equal;
}
}
let ma = &envelopes[&a.unwrap()];
let mb = &envelopes[&b.unwrap()];
{
mb.subject()
.as_ref()
.split_graphemes()
.cmp(&ma.subject().split_graphemes())
}
}
});
2018-08-14 17:36:45 +03:00
}
2018-09-06 13:05:35 +03:00
2018-08-23 15:36:52 +03:00
pub fn sort_by(
&self,
sort: (SortField, SortOrder),
subsort: (SortField, SortOrder),
envelopes: &Envelopes,
2018-08-23 15:36:52 +03:00
) {
if *self.sort.read().unwrap() != sort {
self.inner_sort_by(sort, envelopes);
*self.sort.write().unwrap() = sort;
2018-08-14 17:36:45 +03:00
}
if *self.subsort.read().unwrap() != subsort {
self.inner_subsort_by(subsort, envelopes);
*self.subsort.write().unwrap() = subsort;
2018-08-14 17:36:45 +03:00
}
}
pub fn thread_to_mail(&self, i: ThreadNodeHash) -> EnvelopeHash {
2019-05-14 21:47:47 +03:00
let thread = &self.thread_nodes[&i];
2018-09-06 13:05:35 +03:00
thread.message().unwrap()
2018-08-14 17:36:45 +03:00
}
2020-05-10 21:14:49 +03:00
pub fn thread_nodes(&self) -> &HashMap<ThreadNodeHash, ThreadNode> {
2018-09-06 13:05:35 +03:00
&self.thread_nodes
}
2018-10-14 19:49:16 +03:00
pub fn len(&self) -> usize {
2019-02-10 21:06:30 +02:00
self.hash_set.len()
2018-10-14 19:49:16 +03:00
}
2022-11-14 19:14:19 +02:00
pub fn is_empty(&self) -> bool {
self.hash_set.is_empty()
}
pub fn root_len(&self) -> usize {
self.tree_index.read().unwrap().len()
}
pub fn root_set(&self, idx: usize) -> ThreadNodeHash {
self.tree_index.read().unwrap()[idx]
}
pub fn roots(&self) -> SmallVec<[ThreadHash; 1024]> {
// [ref:FIXME]: refactor filter
self.groups
.iter()
.filter_map(|(h, g)| g.root().map(|_| *h))
.collect::<SmallVec<[ThreadHash; 1024]>>()
}
}
impl Index<&ThreadNodeHash> for Threads {
2018-09-06 13:05:35 +03:00
type Output = ThreadNode;
fn index(&self, index: &ThreadNodeHash) -> &ThreadNode {
self.thread_nodes.get(index).expect("thread node not found")
}
2018-09-06 13:05:35 +03:00
}
/*
fn print_threadnodes(
node_hash: ThreadNodeHash,
2020-05-10 21:14:49 +03:00
nodes: &HashMap<ThreadNodeHash, ThreadNode>,
envelopes: &Envelopes,
) {
fn help(
level: usize,
node_hash: ThreadNodeHash,
2020-05-10 21:14:49 +03:00
nodes: &HashMap<ThreadNodeHash, ThreadNode>,
envelopes: &Envelopes,
) {
eprint!("{}ThreadNode {}\n{}\tmessage: {}\n{}\tparent: {}\n{}\tthread_group: {}\n{}\tchildren (len: {}):\n",
"\t".repeat(level),
node_hash,
"\t".repeat(level),
2019-06-18 21:13:58 +03:00
nodes[&node_hash].message().as_ref().map(|m| format!("{} - {}\n{}\t\t{}", envelopes[m].message_id_display(), envelopes[m].subject(), "\t".repeat(level), envelopes[m].references().iter().map(ToString::to_string).collect::<Vec<String>>().join(", "))).unwrap_or_else(|| "None".to_string()),
"\t".repeat(level),
2019-06-18 21:13:58 +03:00
nodes[&node_hash].parent().as_ref().map(ToString::to_string).unwrap_or_else(|| "None".to_string()),
"\t".repeat(level),
nodes[&node_hash].thread_group,
"\t".repeat(level),
nodes[&node_hash].children.len(),
);
for c in &nodes[&node_hash].children {
help(level + 2, *c, nodes, envelopes);
}
}
help(0, node_hash, nodes, envelopes);
}
*/
#[derive(Serialize)]
struct Node {
id: String,
subject: String,
from: String,
to: String,
date: UnixTimestamp,
references: String,
in_reply_to: String,
}
#[derive(Serialize)]
struct Link {
source: String,
target: String,
}
#[derive(Serialize)]
struct Graph {
nodes: Vec<Node>,
links: Vec<Link>,
}
/*
fn save_graph(
node_arr: &[ThreadNodeHash],
2020-05-10 21:14:49 +03:00
nodes: &HashMap<ThreadNodeHash, ThreadNode>,
ids: &HashMap<ThreadNodeHash, Vec<u8>>,
envelopes: &Envelopes,
) {
let envelopes = envelopes.read().unwrap();
let mut graph = Graph {
nodes: vec![],
links: vec![],
};
let mut stack: SmallVec<[(ThreadNodeHash, String); 16]> = SmallVec::new();
for n in node_arr {
stack.extend(
nodes[n].children.iter().cloned().zip(
std::iter::repeat(unsafe { std::str::from_utf8_unchecked(&ids[n]) }.to_string())
.take(nodes[n].children.len()),
),
);
graph.nodes.push(Node {
id: unsafe { std::str::from_utf8_unchecked(&ids[n]).to_string() },
subject: nodes[n]
.message
.as_ref()
.map(|h| envelopes[h].subject().to_string())
.unwrap_or("missing".into()),
from: nodes[n]
.message
.as_ref()
.map(|h| envelopes[h].field_from_to_string())
.unwrap_or("missing".into()),
to: nodes[n]
.message
.as_ref()
.map(|h| envelopes[h].field_to_to_string())
.unwrap_or("missing".into()),
date: nodes[n]
.message
.as_ref()
.map(|h| envelopes[h].date())
.unwrap_or(0),
references: nodes[n]
.message
.as_ref()
.map(|h| envelopes[h].field_references_to_string())
.unwrap_or("missing".into()),
in_reply_to: nodes[n]
.message
.as_ref()
.and_then(|h| envelopes[h].in_reply_to())
.map(|h| h.to_string())
.unwrap_or("missing".into()),
});
while let Some((target, parent)) = stack.pop() {
graph.nodes.push(Node {
id: unsafe { std::str::from_utf8_unchecked(&ids[&target]).to_string() },
subject: nodes[&target]
.message
.as_ref()
.map(|h| envelopes[h].subject().to_string())
.unwrap_or("missing".into()),
from: nodes[&target]
.message
.as_ref()
.map(|h| envelopes[h].field_from_to_string())
.unwrap_or("missing".into()),
to: nodes[&target]
.message
.as_ref()
.map(|h| envelopes[h].field_to_to_string())
.unwrap_or("missing".into()),
date: nodes[&target]
.message
.as_ref()
.map(|h| envelopes[h].date())
.unwrap_or(0),
references: nodes[&target]
.message
.as_ref()
.map(|h| envelopes[h].field_references_to_string())
.unwrap_or("missing".into()),
in_reply_to: nodes[&target]
.message
.as_ref()
.and_then(|h| envelopes[h].in_reply_to())
.map(|h| h.to_string())
.unwrap_or("missing".into()),
});
graph.links.push(Link {
source: parent,
target: unsafe { std::str::from_utf8_unchecked(&ids[&target]).to_string() },
});
stack.extend(
nodes[&target].children.iter().cloned().zip(
std::iter::repeat(
unsafe { std::str::from_utf8_unchecked(&ids[&target]) }.to_string(),
)
.take(nodes[&target].children.len()),
),
);
}
let s = serde_json::to_string(&graph).unwrap();
use std::fs::File;
use std::io::prelude::*;
let mut file = File::create(format!(
"/tmp/meli/threads/threads_{}.json",
2023-06-18 22:28:40 +03:00
crate::utils::datetime::now()
))
.unwrap();
file.write_all(s.as_bytes()).unwrap();
}
}
*/