From d436a47c1f0903669719da8a3f3f10763c900170 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 19 Feb 2022 11:55:31 +0200 Subject: [PATCH] lib: Implement Mu::MessageContact Implement a new struct Mu::MessageContact to usurps some of the different types for contact information. Sprinkle some "modern C++" on it for convenience. --- lib/meson.build | 2 + lib/mu-message-contact.cc | 180 ++++++++++++++++++++++++++++++++++++ lib/mu-message-contact.hh | 186 ++++++++++++++++++++++++++++++++++++++ lib/tests/meson.build | 8 ++ 4 files changed, 376 insertions(+) create mode 100644 lib/mu-message-contact.cc create mode 100644 lib/mu-message-contact.hh diff --git a/lib/meson.build b/lib/meson.build index d9c31207..c9833fb3 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -63,6 +63,8 @@ lib_mu=static_library( 'mu-msg-sexp.cc', 'mu-msg.cc', 'mu-msg.hh', + 'mu-message-contact.hh', + 'mu-message-contact.cc', 'mu-message-flags.hh', 'mu-message-flags.cc', 'mu-message-priority.hh', diff --git a/lib/mu-message-contact.cc b/lib/mu-message-contact.cc new file mode 100644 index 00000000..cae91262 --- /dev/null +++ b/lib/mu-message-contact.cc @@ -0,0 +1,180 @@ +/* +** Copyright (C) 2022 Dirk-Jan C. Binnema +** +** This program 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, or (at your option) any +** later version. +** +** This program 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 this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include "mu-message-contact.hh" +#include +#include + +using namespace Mu; + + +std::string +MessageContact::display_name() const +{ + if (name.empty()) + return email; + else + return name + " <" + email + '>'; +} + +Mu::MessageContacts +Mu::make_message_contacts(InternetAddressList* addr_lst, + MessageContact::Type type, + ::time_t message_date) +{ + MessageContacts contacts; + size_t num{}; + + g_return_val_if_fail(addr_lst, contacts); + + auto lst_len{internet_address_list_length(addr_lst)}; + contacts.reserve(lst_len); + for (auto i = 0; i != lst_len; ++i) { + + auto&& addr{internet_address_list_get_address(addr_lst, i)}; + const auto name{internet_address_get_name(addr)}; + + if (G_UNLIKELY(!INTERNET_ADDRESS_IS_MAILBOX(addr))) + continue; + + const auto email{internet_address_mailbox_get_addr ( + INTERNET_ADDRESS_MAILBOX(addr))}; + if (G_UNLIKELY(!email)) + continue; + + contacts.push_back(MessageContact{email, name ? name : "", + type, message_date}); + ++num; + } + + return contacts; +} + + +Mu::MessageContacts +Mu::make_message_contacts(const std::string& addrs, + MessageContact::Type type, + ::time_t message_date) +{ + auto addr_list = internet_address_list_parse(NULL, addrs.c_str()); + if (!addr_list) { + g_warning("no addresses found in '%s'", addrs.c_str()); + return {}; + } + + auto contacts{make_message_contacts(addr_list, type, message_date)}; + g_object_unref(addr_list); + + return contacts; +} + + +size_t +Mu::lowercase_hash(const std::string& s) +{ + std::size_t djb = 5381; // djb hash + for (const auto c : s) + djb = ((djb << 5) + djb) + + static_cast(g_ascii_tolower(c)); + return djb; +} + +#ifdef BUILD_TESTS +/* + * Tests. + * + */ + +#include "utils/mu-utils.hh" + +static void +test_ctor_01() +{ + MessageContact c{ + "foo@example.com", + "Foo Bar", + MessageContact::Type::Bcc, + 1645214647 + }; + + assert_equal(c.email, "foo@example.com"); + assert_equal(c.name, "Foo Bar"); + g_assert_true(c.type == MessageContact::Type::Bcc); + g_assert_cmpuint(c.message_date,==,1645214647); + + assert_equal(c.display_name(), "Foo Bar "); +} + + +static void +test_ctor_02() +{ + MessageContact c{ + "bar@example.com", + "Blinky", + 1645215014, + true, /* personal */ + 13, /*freq*/ + 12345 /* tstamp */ + }; + + assert_equal(c.email, "bar@example.com"); + assert_equal(c.name, "Blinky"); + g_assert_true(c.personal); + g_assert_cmpuint(c.frequency,==,13); + g_assert_cmpuint(c.tstamp,==,12345); + assert_equal(c.name, "Blinky"); + g_assert_cmpuint(c.message_date,==,1645215014); + + assert_equal(c.display_name(), "Blinky "); +} + + + +static void +test_make_contacts() +{ + const auto str = "Abc , " + "Def , " + "Ghi "; + InternetAddressList *lst{ + internet_address_list_parse(NULL, str)}; + + g_assert_true(lst); + const auto addrs{make_message_contacts(lst, MessageContact::Type::Cc, 54321 )}; + g_object_unref(lst); + + g_assert_cmpuint(addrs.size(),==,3); +} + + + +int +main(int argc, char* argv[]) +{ + g_test_init(&argc, &argv, NULL); + g_mime_init(); + + g_test_add_func("/lib/message-contacts/ctor-01", test_ctor_01); + g_test_add_func("/lib/message-contacts/ctor-02", test_ctor_02); + g_test_add_func("/lib/message-contacts/make-contacts", test_make_contacts); + + return g_test_run(); +} +#endif /*BUILD_TESTS*/ diff --git a/lib/mu-message-contact.hh b/lib/mu-message-contact.hh new file mode 100644 index 00000000..76078d90 --- /dev/null +++ b/lib/mu-message-contact.hh @@ -0,0 +1,186 @@ +/* +** Copyright (C) 2022 Dirk-Jan C. Binnema +** +** This program 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, or (at your option) any +** later version. +** +** This program 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 this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#ifndef MU_MESSAGE_CONTACT_HH__ +#define MU_MESSAGE_CONTACT_HH__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct _InternetAddressList; + +namespace Mu { + +/** + * Get the hash value for a lowercase value of s; useful for email-addresses + * + * @param s a string + * + * @return a hash value. + */ +size_t lowercase_hash(const std::string& s); + + +struct MessageContact { + /** + * Contact types + */ + enum struct Type { + To, /**< To recipient */ + From, /**< Sender */ + Cc, /**< Cc recipient */ + Bcc, /**< Bcc recipient */ + ReplyTo, /**< Reply-To wannabe recipient */ + Unknown, /**< Unknown type */ + }; + + /** + * Construct a new MessageContact + * + * @param email_ email address + * @param name_ name or empty + * @param type_ contact type + * @param message_date_ data for the message for this contact + */ + MessageContact(const std::string& email_, const std::string& name_ = "", + Type type_ = Type::Unknown, time_t message_date_ = 0) + : email{email_}, name{name_}, type{type_}, + message_date{message_date_}, personal{}, frequency{1}, tstamp{} + { + } + + /** + * Construct a new MessageContact + * + * @param email_ email address + * @param name_ name or empty + * @param personal_ is this a personal contact? + * @param last_seen_ when was this contact last seen? + * @param freq_ how often was this contact seen? + */ + MessageContact(const std::string& email_, const std::string& name_, + time_t message_date_, bool personal_, size_t freq_, + int64_t tstamp_) + : email{email_}, name{name_}, type{Type::Unknown}, + message_date{message_date_}, personal{personal_}, frequency{freq_}, + tstamp{tstamp_} + { + } + + /** + * Get the "display name" for this contact; basically, if there's a + * non-empty name, it's + * Jane Doe + * otherwise it's just the e-mail address. + * + * @return the display name + */ + std::string display_name() const; + + /** + * Operator==; based on the hash values (ie. lowercase e-mail address) + * + * @param rhs some other MessageContact + * + * @return true orf false. + */ + bool operator== (const MessageContact& rhs) const noexcept { + return hash() == rhs.hash(); + } + + /** + * Get a hash-value for this contact, which gets lazily calculated. This + * is for use with container classes. This uses the _lowercase_ email + * address. + * + * @return the hash + */ + size_t hash() const { + static size_t cached_hash; + if (cached_hash == 0) { + cached_hash = lowercase_hash(email); + } + return cached_hash; + } + + /* + * data members + */ + + std::string email; /**< Email address for this contact.Not empty */ + std::string name; /**< Name for this contact; can be empty. */ + Type type{Type::Unknown}; /**< Type of contact */ + ::time_t message_date; /**< date of the message from which the + * contact originates */ + bool personal; /**< A personal message? */ + size_t frequency; /**< Frequency of this contact */ + int64_t tstamp; /**< Timestamp for this contact */ + +}; + +using MessageContacts = std::vector; + +/** + * Create a sequence of MessageContact objects from an InternetAddressList + * + * @param addr_lst an address list + * @param type the type of addresses + * @param message_date the date of the message from which the InternetAddressList + * originates. + * + * @return a sequence of MessageContact objects. + */ +MessageContacts +make_message_contacts(/*const*/ struct _InternetAddressList* addr_lst, + MessageContact::Type type, ::time_t message_date); + +/** + * Create a sequence of MessageContact objects from an InternetAddressList + * + * @param addrs a string with one more valid addresses (as per internet_address_list_parse()) + * @param type the type of addresses + * @param message_date the date of the message from which the addresses originate + * + * @return a sequence of MessageContact objects. + */ +MessageContacts +make_message_contacts(const std::string& addrs, + MessageContact::Type type, ::time_t message_date); +} // namespace Mu + +/** + * Implement our hash int std:: + */ +template<> struct std::hash { + std::size_t operator()(const Mu::MessageContact& c) const noexcept { + return c.hash(); + } +}; + + + + +#endif /* MU_MESSAGE_CONTACT_HH__ */ diff --git a/lib/tests/meson.build b/lib/tests/meson.build index 9a8904c6..2436a38f 100644 --- a/lib/tests/meson.build +++ b/lib/tests/meson.build @@ -61,6 +61,14 @@ test('test_contacts', install: false, cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, lib_mu_dep, lib_test_mu_common_dep])) + +test('test_message_contact', + executable('test-message-contact', + '../mu-message-contact.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, gmime_dep, lib_mu_dep, lib_test_mu_common_dep])) + test('test_parser', executable('test-parser', 'test-parser.cc',