Commit
Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: 394236ba8a2875eaeb0fb7cfe4d90855853523df
Timestamp: Mon, 19 Aug 2024 06:02:16 +0000 (4 months ago)

+181 -143 +/-7 browse
email/address: Refactor References struct
email/address: Refactor References struct

Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>
1diff --git a/meli/src/mail/view/envelope.rs b/meli/src/mail/view/envelope.rs
2index 351ce87..bac7a73 100644
3--- a/meli/src/mail/view/envelope.rs
4+++ b/meli/src/mail/view/envelope.rs
5 @@ -841,17 +841,15 @@ impl Component for EnvelopeView {
6 )
7 );
8 if self.view_settings.expand_headers {
9- if let Some(val) = envelope.in_reply_to_display() {
10+ if let Some(val) = envelope.in_reply_to() {
11 print_header!(
12- (HeaderName::IN_REPLY_TO, val),
13+ (
14+ HeaderName::IN_REPLY_TO,
15+ melib::MessageID::display_slice(val.refs(), Some(" "))
16+ ),
17 (
18 HeaderName::REFERENCES,
19- envelope
20- .references()
21- .iter()
22- .map(std::string::ToString::to_string)
23- .collect::<Vec<String>>()
24- .join(", ")
25+ melib::MessageID::display_slice(envelope.references(), Some(" "))
26 )
27 );
28 }
29 diff --git a/meli/src/sqlite3.rs b/meli/src/sqlite3.rs
30index 9276842..da66bbd 100644
31--- a/meli/src/sqlite3.rs
32+++ b/meli/src/sqlite3.rs
33 @@ -219,8 +219,8 @@ impl AccountCache {
34 envelope.subject().into_owned().trim_end_matches('\u{0}'),
35 envelope.message_id().to_string(),
36 envelope
37- .in_reply_to_display()
38- .map(|f| f.to_string())
39+ .in_reply_to()
40+ .map(|f| melib::MessageID::display_slice(f.refs(), Some(" ")))
41 .unwrap_or_default(),
42 envelope.field_references_to_string(),
43 i64::from(envelope.flags().bits()),
44 @@ -367,8 +367,11 @@ impl AccountCache {
45 e.field_bcc_to_string(),
46 e.subject().into_owned().trim_end_matches('\u{0}'),
47 e.message_id().to_string(),
48- e.in_reply_to_display()
49- .map(|f| f.to_string())
50+ e.in_reply_to()
51+ .map(|f| melib::MessageID::display_slice(
52+ f.refs(),
53+ Some(" ")
54+ ))
55 .unwrap_or_default(),
56 e.field_references_to_string(),
57 i64::from(e.flags().bits()),
58 diff --git a/melib/src/email.rs b/melib/src/email.rs
59index 2dca118..8205ec6 100644
60--- a/melib/src/email.rs
61+++ b/melib/src/email.rs
62 @@ -266,22 +266,19 @@ crate::declare_u64_hash!(EnvelopeHash);
63 /// bytes into an `Attachment` object.
64 #[derive(Clone, Deserialize, Serialize)]
65 pub struct Envelope {
66- // ----- IMAP4rev1 -----
67 pub date: String,
68 pub subject: Option<String>,
69 pub from: SmallVec<[Address; 1]>,
70- // pub sender
71- // pub reply_to
72 pub to: SmallVec<[Address; 1]>,
73 pub cc: SmallVec<[Address; 1]>,
74 pub bcc: Vec<Address>,
75- pub in_reply_to: Option<MessageID>,
76+ pub in_reply_to: Option<References>,
77+ pub references: Option<References>,
78 pub message_id: MessageID,
79+ pub other_headers: HeaderMap,
80 // ----- Other -----
81 pub hash: EnvelopeHash,
82 pub timestamp: UnixTimestamp,
83- pub references: Option<References>,
84- pub other_headers: HeaderMap,
85 pub thread: ThreadNodeHash,
86 pub flags: Flag,
87 pub has_attachments: bool,
88 @@ -461,8 +458,15 @@ impl Envelope {
89 *
90 * if self.message_id.is_none() ...
91 */
92- if let Some(x) = self.in_reply_to.clone() {
93- self.push_references(x);
94+ if let Some(ref x) = self.in_reply_to {
95+ match self.references {
96+ Some(ref mut s) => {
97+ s.extend(x.refs());
98+ }
99+ None => {
100+ self.references = Some(x.clone());
101+ }
102+ }
103 }
104 if let Ok(d) = parser::dates::rfc5322_date(self.date.as_bytes()) {
105 self.set_datetime(d);
106 @@ -471,17 +475,8 @@ impl Envelope {
107 let hash = self.hash;
108 self.set_message_id(format!("<{:x}>", hash.0).as_bytes());
109 }
110- if self.references.is_some() {
111- if let Some(pos) = self
112- .references
113- .as_ref()
114- .map(|r| &r.refs)
115- .unwrap()
116- .iter()
117- .position(|r| r == &self.message_id)
118- {
119- self.references.as_mut().unwrap().refs.remove(pos);
120- }
121+ if let Some(ref mut r) = self.references {
122+ r.remove(&self.message_id);
123 }
124
125 Ok(())
126 @@ -624,22 +619,13 @@ impl Envelope {
127 }
128 }
129
130- pub fn in_reply_to(&self) -> Option<&MessageID> {
131- self.in_reply_to
132- .as_ref()
133- .or_else(|| self.references.as_ref().and_then(|r| r.refs.last()))
134- }
135-
136- pub fn in_reply_to_display(&self) -> Option<Cow<str>> {
137- self.in_reply_to
138- .as_ref()
139- .map(|m| String::from_utf8_lossy(m.val()))
140- }
141-
142- pub fn in_reply_to_raw(&self) -> Option<Cow<str>> {
143- self.in_reply_to
144- .as_ref()
145- .map(|m| String::from_utf8_lossy(m.raw()))
146+ pub fn in_reply_to(&'_ self) -> Option<Cow<'_, References>> {
147+ self.in_reply_to.as_ref().map(Cow::Borrowed).or_else(|| {
148+ self.references
149+ .as_ref()
150+ .and_then(|r| r.refs().last())
151+ .and_then(|msgid| Some(Cow::Owned(References::new(vec![msgid.clone()])?)))
152+ })
153 }
154
155 pub fn message_id(&self) -> &MessageID {
156 @@ -673,23 +659,35 @@ impl Envelope {
157 }
158
159 pub fn set_in_reply_to(&mut self, new_val: &[u8]) -> &mut Self {
160- // [ref:FIXME]: msg_id_list
161- let new_val = new_val.trim();
162 if !new_val.is_empty() {
163- let val = match parser::address::msg_id(new_val) {
164- Ok(v) => v.1,
165- Err(_) => {
166- self.in_reply_to = Some(MessageID::new(new_val, new_val));
167- return self;
168- }
169- };
170- self.in_reply_to = Some(val);
171- } else {
172 self.in_reply_to = None;
173+ {
174+ let parse_result = parser::address::msg_id_list(new_val);
175+ if let Ok((_, value)) = parse_result {
176+ for v in value {
177+ self.push_in_reply_to(v);
178+ }
179+ }
180+ }
181+ self.other_headers_mut().insert(
182+ HeaderName::IN_REPLY_TO,
183+ String::from_utf8_lossy(new_val).to_string(),
184+ );
185 }
186 self
187 }
188
189+ pub fn push_in_reply_to(&mut self, new_ref: MessageID) {
190+ match self.in_reply_to {
191+ Some(ref mut s) => {
192+ s.extend(std::iter::once(new_ref));
193+ }
194+ None => {
195+ self.in_reply_to = References::new(vec![new_ref]);
196+ }
197+ }
198+ }
199+
200 pub fn set_subject(&mut self, new_val: Vec<u8>) -> &mut Self {
201 let mut new_val = String::from_utf8(new_val)
202 .unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).into());
203 @@ -719,28 +717,13 @@ impl Envelope {
204 self
205 }
206
207- pub fn push_references(&mut self, new_ref: MessageID) {
208+ pub fn push_references(&mut self, new_refs: &References) {
209 match self.references {
210 Some(ref mut s) => {
211- if s.refs.contains(&new_ref) {
212- if s.refs[s.refs.len() - 1] != new_ref {
213- if let Some(index) = s.refs.iter().position(|x| *x == new_ref) {
214- s.refs.remove(index);
215- } else {
216- panic!();
217- }
218- } else {
219- return;
220- }
221- }
222- s.refs.push(new_ref);
223+ s.extend(new_refs.refs());
224 }
225 None => {
226- let v = vec![new_ref];
227- self.references = Some(References {
228- raw: "".into(),
229- refs: v,
230- });
231+ self.references = Some(new_refs.clone());
232 }
233 }
234 }
235 @@ -752,33 +735,21 @@ impl Envelope {
236 {
237 let parse_result = parser::address::msg_id_list(new_val);
238 if let Ok((_, value)) = parse_result {
239- for v in value {
240- self.push_references(v);
241- }
242- }
243- }
244- match self.references {
245- Some(ref mut s) => {
246- s.raw = new_val.into();
247- }
248- None => {
249- self.references = Some(References {
250- raw: new_val.into(),
251- refs: Vec::new(),
252- });
253+ self.references = References::new(value);
254 }
255 }
256+ self.other_headers_mut().insert(
257+ HeaderName::REFERENCES,
258+ String::from_utf8_lossy(new_val).to_string(),
259+ );
260 }
261 self
262 }
263
264- pub fn references(&self) -> SmallVec<[&MessageID; 8]> {
265+ pub fn references(&self) -> &[MessageID] {
266 match self.references {
267- Some(ref s) => s.refs.iter().fold(SmallVec::new(), |mut acc, x| {
268- acc.push(x);
269- acc
270- }),
271- None => SmallVec::new(),
272+ Some(ref s) => s.refs(),
273+ None => &[],
274 }
275 }
276
277 diff --git a/melib/src/email/address.rs b/melib/src/email/address.rs
278index 98a4a00..44b501a 100644
279--- a/melib/src/email/address.rs
280+++ b/melib/src/email/address.rs
281 @@ -658,15 +658,63 @@ impl Hash for MessageID {
282 }
283 }
284
285- #[derive(Clone, Deserialize, Serialize)]
286+ #[derive(Clone, Deserialize, Eq, Hash, PartialEq, Serialize)]
287 pub struct References {
288- pub raw: Vec<u8>,
289- pub refs: Vec<MessageID>,
290+ refs: Vec<MessageID>,
291 }
292
293 impl std::fmt::Debug for References {
294- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
295- write!(f, "{:#?}", self.refs)
296+ fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
297+ let mut dbg_t = fmt.debug_tuple(crate::identify! {References});
298+ for r in &self.refs {
299+ dbg_t.field(r);
300+ }
301+ dbg_t.finish()
302+ }
303+ }
304+
305+ impl References {
306+ pub fn new(refs: Vec<MessageID>) -> Option<Self> {
307+ if refs.is_empty() {
308+ return None;
309+ }
310+ Some(Self { refs })
311+ }
312+
313+ pub fn push(&mut self, new: MessageID) {
314+ self.refs.push(new);
315+ }
316+
317+ /// A parent reference should only be removed in order to break cycles (when
318+ /// an envelope refers to its own `Message-ID` as a parent).
319+ pub fn remove(&mut self, msgid: &MessageID) {
320+ self.refs.retain(|r| r != msgid);
321+ }
322+
323+ pub fn refs(&self) -> &[MessageID] {
324+ &self.refs
325+ }
326+ }
327+
328+ impl Extend<MessageID> for References {
329+ /// Insert new [`MessageID`] values, de-duplicated.
330+ fn extend<T: IntoIterator<Item = MessageID>>(&mut self, iter: T) {
331+ for elem in iter {
332+ if !self.refs.contains(&elem) {
333+ self.refs.push(elem);
334+ }
335+ }
336+ }
337+ }
338+
339+ impl<'a> Extend<&'a MessageID> for References {
340+ /// Insert new [`MessageID`] values, de-duplicated.
341+ fn extend<T: IntoIterator<Item = &'a MessageID>>(&mut self, iter: T) {
342+ for elem in iter {
343+ if !self.refs.contains(elem) {
344+ self.refs.push(elem.clone());
345+ }
346+ }
347 }
348 }
349
350 diff --git a/melib/src/imap/protocol_parser.rs b/melib/src/imap/protocol_parser.rs
351index 4541635..fa87647 100644
352--- a/melib/src/imap/protocol_parser.rs
353+++ b/melib/src/imap/protocol_parser.rs
354 @@ -1221,8 +1221,8 @@ pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> {
355 }
356 if let Some(in_reply_to) = in_reply_to {
357 env.set_in_reply_to(&in_reply_to);
358- if let Some(in_reply_to) = env.in_reply_to().cloned() {
359- env.push_references(in_reply_to);
360+ if let Some(in_reply_to) = env.in_reply_to().map(|r| r.as_ref().clone()) {
361+ env.push_references(&in_reply_to);
362 }
363 }
364
365 diff --git a/melib/src/jmap/email.rs b/melib/src/jmap/email.rs
366index cb17cb4..25da8c6 100644
367--- a/melib/src/jmap/email.rs
368+++ b/melib/src/jmap/email.rs
369 @@ -286,15 +286,15 @@ impl From<EmailObject> for crate::Envelope {
370 if let Some(v) = t.message_id.first() {
371 env.set_message_id(v.as_bytes());
372 }
373+ if let Some(v) = t.headers.get(HeaderName::REFERENCES.as_str()) {
374+ env.set_references(v.as_bytes());
375+ }
376 if let Some(ref in_reply_to) = t.in_reply_to {
377 env.set_in_reply_to(in_reply_to[0].as_bytes());
378- if let Some(in_reply_to) = env.in_reply_to().cloned() {
379- env.push_references(in_reply_to);
380+ if let Some(in_reply_to) = env.in_reply_to().map(|r| r.as_ref().clone()) {
381+ env.push_references(&in_reply_to);
382 }
383 }
384- if let Some(v) = t.headers.get(HeaderName::REFERENCES.as_str()) {
385- env.set_references(v.as_bytes());
386- }
387 if let Some(v) = t.headers.get(HeaderName::DATE.as_str()) {
388 env.set_date(v.as_bytes());
389 if let Ok(d) = rfc5322_date(v.as_bytes()) {
390 @@ -372,7 +372,7 @@ impl From<EmailObject> for crate::Envelope {
391 }
392
393 if let (Some(ref mut r), message_id) = (&mut env.references, &env.message_id) {
394- r.refs.retain(|r| r != message_id);
395+ r.remove(message_id);
396 }
397
398 env
399 diff --git a/melib/src/thread.rs b/melib/src/thread.rs
400index a0b2b17..e39d27d 100644
401--- a/melib/src/thread.rs
402+++ b/melib/src/thread.rs
403 @@ -606,9 +606,9 @@ pub struct Threads {
404 tree_index: Arc<RwLock<Vec<ThreadNodeHash>>>,
405 pub groups: HashMap<ThreadHash, ThreadGroup>,
406
407- message_ids: HashMap<Vec<u8>, ThreadNodeHash>,
408- pub message_ids_set: HashSet<Vec<u8>>,
409- pub missing_message_ids: HashSet<Vec<u8>>,
410+ message_ids: HashMap<MessageID, ThreadNodeHash>,
411+ pub message_ids_set: HashSet<MessageID>,
412+ pub missing_message_ids: HashSet<MessageID>,
413 pub hash_set: HashSet<EnvelopeHash>,
414 pub thread_to_envelope: HashMap<ThreadHash, Vec<EnvelopeHash>>,
415 pub envelope_to_thread: HashMap<EnvelopeHash, ThreadHash>,
416 @@ -669,13 +669,13 @@ impl Threads {
417 let thread_nodes: HashMap<ThreadNodeHash, ThreadNode> =
418 HashMap::with_capacity_and_hasher((length as f64 * 1.2) as usize, Default::default());
419 /* A hash table of Message IDs */
420- let message_ids: HashMap<Vec<u8>, ThreadNodeHash> =
421+ let message_ids: HashMap<MessageID, ThreadNodeHash> =
422 HashMap::with_capacity_and_hasher(length, Default::default());
423 /* A hash set of Message IDs we haven't encountered yet as an Envelope */
424- let missing_message_ids: HashSet<Vec<u8>> =
425+ let missing_message_ids: HashSet<MessageID> =
426 HashSet::with_capacity_and_hasher(length, Default::default());
427 /* A hash set of Message IDs we have encountered as a MessageID */
428- let message_ids_set: HashSet<Vec<u8>> =
429+ let message_ids_set: HashSet<MessageID> =
430 HashSet::with_capacity_and_hasher(length, Default::default());
431 let hash_set: HashSet<EnvelopeHash> =
432 HashSet::with_capacity_and_hasher(length, Default::default());
433 @@ -791,7 +791,7 @@ impl Threads {
434 }
435 }
436 if let Some((message_id, _)) = self.message_ids.iter().find(|(_, h)| **h == t_id) {
437- self.missing_message_ids.insert(message_id.to_vec());
438+ self.missing_message_ids.insert(message_id.clone());
439 }
440 }
441
442 @@ -901,7 +901,7 @@ impl Threads {
443 if !envelopes_lck.contains_key(&env_hash) {
444 return false;
445 }
446- let message_id = envelopes_lck[&env_hash].message_id().raw();
447+ let message_id = envelopes_lck[&env_hash].message_id();
448 if self.message_ids.contains_key(message_id)
449 && !self.missing_message_ids.contains(message_id)
450 {
451 @@ -924,12 +924,15 @@ impl Threads {
452 }
453 }
454 let envelopes_lck = envelopes.read().unwrap();
455- let message_id = envelopes_lck[&env_hash].message_id().raw();
456- let reply_to_id: Option<ThreadNodeHash> = envelopes_lck[&env_hash]
457- .in_reply_to()
458- .map(StrBuild::raw)
459- .filter(|irt| irt != &message_id)
460- .and_then(|r| self.message_ids.get(r).cloned());
461+ let message_id = envelopes_lck[&env_hash].message_id();
462+ let reply_to_id: Option<ThreadNodeHash> =
463+ envelopes_lck[&env_hash].in_reply_to().and_then(|r| {
464+ r.refs()
465+ .iter()
466+ .rev()
467+ .filter(|irt| irt != &message_id)
468+ .find_map(|r| self.message_ids.get(r).cloned())
469+ });
470
471 if other_mailbox
472 && reply_to_id.is_none()
473 @@ -937,7 +940,7 @@ impl Threads {
474 && !envelopes_lck[&env_hash]
475 .references()
476 .iter()
477- .any(|r| self.message_ids.contains_key(r.raw()))
478+ .any(|r| self.message_ids.contains_key(r))
479 {
480 return false;
481 }
482 @@ -953,7 +956,7 @@ impl Threads {
483 None
484 },
485 )
486- .unwrap_or_else(|| ThreadNodeHash::from(message_id));
487+ .unwrap_or_else(|| ThreadNodeHash::from(message_id.raw()));
488 {
489 let node = self.thread_nodes.entry(new_id).or_default();
490 node.message = Some(env_hash);
491 @@ -999,8 +1002,8 @@ impl Threads {
492 };
493 }
494
495- self.message_ids.insert(message_id.to_vec(), new_id);
496- self.message_ids_set.insert(message_id.to_vec());
497+ self.message_ids.insert(message_id.clone(), new_id);
498+ self.message_ids_set.insert(message_id.clone());
499 self.missing_message_ids.remove(message_id);
500 self.hash_set.insert(env_hash);
501 self.thread_to_envelope
502 @@ -1012,10 +1015,20 @@ impl Threads {
503 make!((reply_to_id) parent of (new_id), self);
504 } else if let Some(r) = envelopes_lck[&env_hash]
505 .in_reply_to()
506- .map(StrBuild::raw)
507- .filter(|irt| irt != &message_id)
508+ .clone()
509+ .into_iter()
510+ .find_map(|r| {
511+ r.refs()
512+ .iter()
513+ .rev()
514+ .filter(|irt| *irt != message_id)
515+ .find(|r| {
516+ self.message_ids.contains_key(r) && !self.missing_message_ids.contains(r)
517+ })
518+ .cloned()
519+ })
520 {
521- let reply_to_id = ThreadNodeHash::from(r);
522+ let reply_to_id = ThreadNodeHash::from(&r.raw());
523 self.thread_nodes.insert(
524 reply_to_id,
525 ThreadNode {
526 @@ -1036,25 +1049,30 @@ impl Threads {
527 }),
528 );
529 make!((reply_to_id) parent of (new_id), self);
530- self.message_ids.insert(r.to_vec(), reply_to_id);
531- self.message_ids_set.insert(r.to_vec());
532- self.missing_message_ids.insert(r.to_vec());
533+ self.message_ids.insert(r.clone(), reply_to_id);
534+ self.message_ids_set.insert(r.clone());
535+ self.missing_message_ids.insert(r);
536 }
537
538 if envelopes_lck[&env_hash].references.is_some() {
539 let mut current_descendant_id = new_id;
540- let mut references = envelopes_lck[&env_hash].references();
541- if references.first().filter(|irt| irt.raw() != message_id)
542- == envelopes_lck[&env_hash].in_reply_to().as_ref()
543+ let mut references = envelopes_lck[&env_hash].references().to_vec();
544+ if envelopes_lck[&env_hash]
545+ .in_reply_to()
546+ .map(|r| {
547+ r.refs().last().as_ref()
548+ == references.first().filter(|irt| *irt != message_id).as_ref()
549+ })
550+ .unwrap_or(false)
551 {
552 references.reverse();
553 }
554
555 for reference in references.into_iter().rev() {
556- if reference.raw() == message_id {
557+ if &reference == message_id {
558 continue;
559 }
560- if let Some(&id) = self.message_ids.get(reference.raw()) {
561+ if let Some(&id) = self.message_ids.get(&reference) {
562 if self.thread_nodes[&id].date > self.thread_nodes[&current_descendant_id].date
563 || self.thread_nodes[&current_descendant_id].parent.is_some()
564 {
565 @@ -1084,9 +1102,9 @@ impl Threads {
566 }),
567 );
568 make!((id) parent of (current_descendant_id), self);
569- self.missing_message_ids.insert(reference.raw().to_vec());
570- self.message_ids.insert(reference.raw().to_vec(), id);
571- self.message_ids_set.insert(reference.raw().to_vec());
572+ self.missing_message_ids.insert(reference.clone());
573+ self.message_ids.insert(reference.clone(), id);
574+ self.message_ids_set.insert(reference.clone());
575 current_descendant_id = id;
576 }
577 }
578 @@ -1108,7 +1126,7 @@ impl Threads {
579 .message_ids
580 .iter()
581 .map(|(a, &b)| (b, a.to_vec()))
582- .collect::<HashMap<ThreadNodeHash, Vec<u8>>>(),
583+ .collect::<HashMap<ThreadNodeHash, MessageID>>(),
584 &envelopes,
585 );
586 */
587 @@ -1507,7 +1525,7 @@ fn print_threadnodes(
588 fn save_graph(
589 node_arr: &[ThreadNodeHash],
590 nodes: &HashMap<ThreadNodeHash, ThreadNode>,
591- ids: &HashMap<ThreadNodeHash, Vec<u8>>,
592+ ids: &HashMap<ThreadNodeHash, MessageID>,
593 envelopes: &Envelopes,
594 ) {
595 let envelopes = envelopes.read().unwrap();