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 1 month 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/>.
*/
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<String, String> = fields
let fields: IndexMap<String, String> = fields
.into_iter()
.map(|(s, v)| {
(

@ -260,7 +260,7 @@ impl MailView {
}
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()) {
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())))

@ -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<CardId> for String {
impl From<String> 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<CardId, Card>,
pub cards: IndexMap<CardId, Card>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@ -97,7 +98,7 @@ pub struct Card {
key: String,
color: u8,
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
/// resource.
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 {
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<CardId, Card>;
type Target = IndexMap<CardId, Card>;
fn deref(&self) -> &HashMap<CardId, Card> {
fn deref(&self) -> &IndexMap<CardId, Card> {
&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<String, String> {
pub fn extra_properties(&self) -> &IndexMap<String, String> {
&self.extra_properties
}
@ -299,8 +317,8 @@ impl Card {
}
}
impl From<HashMap<String, String>> for Card {
fn from(mut map: HashMap<String, String>) -> Self {
impl From<IndexMap<String, String>> for Card {
fn from(mut map: IndexMap<String, String>) -> Self {
let mut card = Self::new();
macro_rules! get {
($key:literal, $field:tt) => {
@ -322,8 +340,9 @@ impl From<HashMap<String, String>> for Card {
}
}
impl Default for Card {
fn default() -> Self {
Self::new()
impl From<HashMap<String, String>> for Card {
fn from(map: HashMap<String, String>) -> Self {
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> {
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());

Loading…
Cancel
Save