From 974502c6ffa263c8cfebf40c46b8694dce7fe982 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 24 Mar 2024 15:12:19 +0200 Subject: [PATCH] melib/addressbook: impl Hash for Card Implement hashing for Card. This fixes the appearance of duplicate entries in the add contacts selector in an envelope view when an address appears more than one time in the envelope headers. Signed-off-by: Manos Pitsidianakis --- meli/src/contacts/editor.rs | 5 ++-- meli/src/mail/view.rs | 6 ++-- melib/src/addressbook.rs | 55 +++++++++++++++++++++++----------- melib/src/addressbook/vcard.rs | 1 - 4 files changed, 42 insertions(+), 25 deletions(-) diff --git a/meli/src/contacts/editor.rs b/meli/src/contacts/editor.rs index 45329a6d..57b4ee6b 100644 --- a/meli/src/contacts/editor.rs +++ b/meli/src/contacts/editor.rs @@ -19,8 +19,7 @@ * along with meli. If not, see . */ -use std::collections::HashMap; - +use indexmap::IndexMap; use melib::Card; use crate::{ @@ -200,7 +199,7 @@ impl Component for ContactManager { None => {} Some(true) => { let fields = std::mem::take(&mut self.form).collect().unwrap(); - let fields: HashMap = fields + let fields: IndexMap = fields .into_iter() .map(|(s, v)| { ( diff --git a/meli/src/mail/view.rs b/meli/src/mail/view.rs index c0236e5b..efed3360 100644 --- a/meli/src/mail/view.rs +++ b/meli/src/mail/view.rs @@ -260,7 +260,7 @@ impl MailView { } let envelope: EnvelopeRef = account.collection.get_env(coordinates.2); - let mut entries = Vec::new(); + let mut entries: IndexMap = IndexMap::default(); for addr in envelope.from().iter().chain(envelope.to().iter()) { let mut new_card: Card = Card::new(); new_card @@ -269,12 +269,12 @@ impl MailView { if let Some(display_name) = addr.get_display_name() { new_card.set_name(display_name); } - entries.push((new_card, format!("{}", addr))); + entries.insert(new_card.clone(), (new_card, format!("{}", addr))); } drop(envelope); self.contact_selector = Some(Box::new(Selector::new( "select contacts to add", - entries, + entries.into_iter().map(|(_, v)| v).collect(), false, Some(Box::new(move |id: ComponentId, results: &[Card]| { Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec()))) diff --git a/melib/src/addressbook.rs b/melib/src/addressbook.rs index d40cf0d0..c2bb0559 100644 --- a/melib/src/addressbook.rs +++ b/melib/src/addressbook.rs @@ -23,8 +23,13 @@ pub mod mutt; #[cfg(feature = "vcard")] pub mod vcard; -use std::{collections::HashMap, ops::Deref}; +use std::{ + collections::HashMap, + hash::{Hash, Hasher}, + ops::Deref, +}; +use indexmap::IndexMap; use uuid::Uuid; use crate::utils::{ @@ -57,11 +62,7 @@ impl From for String { impl From for CardId { fn from(s: String) -> Self { - use std::{ - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, - str::FromStr, - }; + use std::{collections::hash_map::DefaultHasher, str::FromStr}; if let Ok(u) = Uuid::parse_str(s.as_str()) { Self::Uuid(u) @@ -80,7 +81,7 @@ pub struct AddressBook { display_name: String, created: UnixTimestamp, last_edited: UnixTimestamp, - pub cards: HashMap, + pub cards: IndexMap, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -97,7 +98,7 @@ pub struct Card { key: String, color: u8, last_edited: UnixTimestamp, - extra_properties: HashMap, + extra_properties: IndexMap, /// If `true`, we can't make any changes because we do not manage this /// resource. external_resource: bool, @@ -115,13 +116,24 @@ impl std::fmt::Display for Card { } } +impl Hash for Card { + fn hash(&self, state: &mut H) { + let mut serialized = serde_json::json! { self }; + // The following anonymous let bind is just to make sure at compile-time that + // `id` field is present in Self and serialized["id"] will be a valid access. + let _: &CardId = &self.id; + serialized["id"] = serde_json::json! { CardId::Hash(0) }; + serialized.to_string().hash(state); + } +} + impl AddressBook { pub fn new(display_name: String) -> Self { Self { display_name, created: now(), last_edited: now(), - cards: HashMap::default(), + cards: IndexMap::default(), } } @@ -199,9 +211,9 @@ impl AddressBook { } impl Deref for AddressBook { - type Target = HashMap; + type Target = IndexMap; - fn deref(&self) -> &HashMap { + fn deref(&self) -> &IndexMap { &self.cards } } @@ -223,6 +235,12 @@ macro_rules! set_fn { }; } +impl Default for Card { + fn default() -> Self { + Self::new() + } +} + impl Card { pub fn new() -> Self { Self { @@ -240,7 +258,7 @@ impl Card { last_edited: now(), external_resource: false, - extra_properties: HashMap::default(), + extra_properties: IndexMap::default(), color: 0, } } @@ -266,7 +284,7 @@ impl Card { self.extra_properties.get(key).map(String::as_str) } - pub fn extra_properties(&self) -> &HashMap { + pub fn extra_properties(&self) -> &IndexMap { &self.extra_properties } @@ -299,8 +317,8 @@ impl Card { } } -impl From> for Card { - fn from(mut map: HashMap) -> Self { +impl From> for Card { + fn from(mut map: IndexMap) -> Self { let mut card = Self::new(); macro_rules! get { ($key:literal, $field:tt) => { @@ -322,8 +340,9 @@ impl From> for Card { } } -impl Default for Card { - fn default() -> Self { - Self::new() +impl From> for Card { + fn from(map: HashMap) -> Self { + let map: IndexMap = map.into_iter().collect(); + Self::from(map) } } diff --git a/melib/src/addressbook/vcard.rs b/melib/src/addressbook/vcard.rs index d1a47d5c..79bb6735 100644 --- a/melib/src/addressbook/vcard.rs +++ b/melib/src/addressbook/vcard.rs @@ -186,7 +186,6 @@ impl TryInto for VCard { fn try_into(mut self) -> crate::error::Result { let mut card = Card::new(); card.set_id(CardId::Hash({ - use std::hash::Hasher; let mut hasher = std::collections::hash_map::DefaultHasher::new(); if let Some(val) = self.0.get("FN") { hasher.write(val.value.as_bytes());