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 <manos@pitsidianak.is>
pull/372/head
Manos Pitsidianakis 2 months ago
parent 3e9144657b
commit 974502c6ff
No known key found for this signature in database
GPG Key ID: 7729C7707F7E09D0

@ -19,8 +19,7 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use std::collections::HashMap; use indexmap::IndexMap;
use melib::Card; use melib::Card;
use crate::{ use crate::{
@ -200,7 +199,7 @@ impl Component for ContactManager {
None => {} None => {}
Some(true) => { Some(true) => {
let fields = std::mem::take(&mut self.form).collect().unwrap(); let fields = std::mem::take(&mut self.form).collect().unwrap();
let fields: HashMap<String, String> = fields let fields: IndexMap<String, String> = fields
.into_iter() .into_iter()
.map(|(s, v)| { .map(|(s, v)| {
( (

@ -260,7 +260,7 @@ impl MailView {
} }
let envelope: EnvelopeRef = account.collection.get_env(coordinates.2); let envelope: EnvelopeRef = account.collection.get_env(coordinates.2);
let mut entries = Vec::new(); let mut entries: IndexMap<Card, (Card, String)> = IndexMap::default();
for addr in envelope.from().iter().chain(envelope.to().iter()) { for addr in envelope.from().iter().chain(envelope.to().iter()) {
let mut new_card: Card = Card::new(); let mut new_card: Card = Card::new();
new_card new_card
@ -269,12 +269,12 @@ impl MailView {
if let Some(display_name) = addr.get_display_name() { if let Some(display_name) = addr.get_display_name() {
new_card.set_name(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); drop(envelope);
self.contact_selector = Some(Box::new(Selector::new( self.contact_selector = Some(Box::new(Selector::new(
"select contacts to add", "select contacts to add",
entries, entries.into_iter().map(|(_, v)| v).collect(),
false, false,
Some(Box::new(move |id: ComponentId, results: &[Card]| { Some(Box::new(move |id: ComponentId, results: &[Card]| {
Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec()))) Some(UIEvent::FinishedUIDialog(id, Box::new(results.to_vec())))

@ -23,8 +23,13 @@ pub mod mutt;
#[cfg(feature = "vcard")] #[cfg(feature = "vcard")]
pub mod 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 uuid::Uuid;
use crate::utils::{ use crate::utils::{
@ -57,11 +62,7 @@ impl From<CardId> for String {
impl From<String> for CardId { impl From<String> for CardId {
fn from(s: String) -> Self { fn from(s: String) -> Self {
use std::{ use std::{collections::hash_map::DefaultHasher, str::FromStr};
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
str::FromStr,
};
if let Ok(u) = Uuid::parse_str(s.as_str()) { if let Ok(u) = Uuid::parse_str(s.as_str()) {
Self::Uuid(u) Self::Uuid(u)
@ -80,7 +81,7 @@ pub struct AddressBook {
display_name: String, display_name: String,
created: UnixTimestamp, created: UnixTimestamp,
last_edited: UnixTimestamp, last_edited: UnixTimestamp,
pub cards: HashMap<CardId, Card>, pub cards: IndexMap<CardId, Card>,
} }
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@ -97,7 +98,7 @@ pub struct Card {
key: String, key: String,
color: u8, color: u8,
last_edited: UnixTimestamp, last_edited: UnixTimestamp,
extra_properties: HashMap<String, String>, extra_properties: IndexMap<String, String>,
/// If `true`, we can't make any changes because we do not manage this /// If `true`, we can't make any changes because we do not manage this
/// resource. /// resource.
external_resource: bool, external_resource: bool,
@ -115,13 +116,24 @@ impl std::fmt::Display for Card {
} }
} }
impl Hash for Card {
fn hash<H: Hasher>(&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 { impl AddressBook {
pub fn new(display_name: String) -> Self { pub fn new(display_name: String) -> Self {
Self { Self {
display_name, display_name,
created: now(), created: now(),
last_edited: now(), last_edited: now(),
cards: HashMap::default(), cards: IndexMap::default(),
} }
} }
@ -199,9 +211,9 @@ impl AddressBook {
} }
impl Deref for AddressBook { impl Deref for AddressBook {
type Target = HashMap<CardId, Card>; type Target = IndexMap<CardId, Card>;
fn deref(&self) -> &HashMap<CardId, Card> { fn deref(&self) -> &IndexMap<CardId, Card> {
&self.cards &self.cards
} }
} }
@ -223,6 +235,12 @@ macro_rules! set_fn {
}; };
} }
impl Default for Card {
fn default() -> Self {
Self::new()
}
}
impl Card { impl Card {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -240,7 +258,7 @@ impl Card {
last_edited: now(), last_edited: now(),
external_resource: false, external_resource: false,
extra_properties: HashMap::default(), extra_properties: IndexMap::default(),
color: 0, color: 0,
} }
} }
@ -266,7 +284,7 @@ impl Card {
self.extra_properties.get(key).map(String::as_str) self.extra_properties.get(key).map(String::as_str)
} }
pub fn extra_properties(&self) -> &HashMap<String, String> { pub fn extra_properties(&self) -> &IndexMap<String, String> {
&self.extra_properties &self.extra_properties
} }
@ -299,8 +317,8 @@ impl Card {
} }
} }
impl From<HashMap<String, String>> for Card { impl From<IndexMap<String, String>> for Card {
fn from(mut map: HashMap<String, String>) -> Self { fn from(mut map: IndexMap<String, String>) -> Self {
let mut card = Self::new(); let mut card = Self::new();
macro_rules! get { macro_rules! get {
($key:literal, $field:tt) => { ($key:literal, $field:tt) => {
@ -322,8 +340,9 @@ impl From<HashMap<String, String>> for Card {
} }
} }
impl Default for Card { impl From<HashMap<String, String>> for Card {
fn default() -> Self { fn from(map: HashMap<String, String>) -> Self {
Self::new() let map: IndexMap<String, String> = map.into_iter().collect();
Self::from(map)
} }
} }

@ -186,7 +186,6 @@ impl<V: VCardVersion> TryInto<Card> for VCard<V> {
fn try_into(mut self) -> crate::error::Result<Card> { fn try_into(mut self) -> crate::error::Result<Card> {
let mut card = Card::new(); let mut card = Card::new();
card.set_id(CardId::Hash({ card.set_id(CardId::Hash({
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new(); let mut hasher = std::collections::hash_map::DefaultHasher::new();
if let Some(val) = self.0.get("FN") { if let Some(val) = self.0.get("FN") {
hasher.write(val.value.as_bytes()); hasher.write(val.value.as_bytes());

Loading…
Cancel
Save