diff --git a/lib/mu-message-contact.hh b/lib/mu-message-contact.hh index 76078d90..39af06b0 100644 --- a/lib/mu-message-contact.hh +++ b/lib/mu-message-contact.hh @@ -77,9 +77,10 @@ struct MessageContact { * * @param email_ email address * @param name_ name or empty + * @param message_date_ date of message this contact originate from * @param personal_ is this a personal contact? - * @param last_seen_ when was this contact last seen? * @param freq_ how often was this contact seen? + * @param tstamp_ timestamp for last change */ MessageContact(const std::string& email_, const std::string& name_, time_t message_date_, bool personal_, size_t freq_, diff --git a/lib/mu-msg-sexp.cc b/lib/mu-msg-sexp.cc index d6f3b5f0..b27080db 100644 --- a/lib/mu-msg-sexp.cc +++ b/lib/mu-msg-sexp.cc @@ -48,21 +48,17 @@ add_prop_nonempty(Sexp::List& list, const char* name, const char* str) list.add_prop(name, Sexp::make_string(str)); } -static Sexp -make_contact_sexp(MuMsgContact* c) + +static Mu::Sexp +make_contact_sexp(const MessageContact& contact) { - // a cons-pair...perhaps make this a plist too? - - Sexp::List contact; - if (mu_msg_contact_name(c)) - contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_name(c)))); - else - contact.add(Sexp::make_symbol("nil")); - - contact.add(Sexp::make_symbol(".")); - contact.add(Sexp::make_string(Mu::remove_ctrl(mu_msg_contact_email(c)))); - - return Sexp::make_list(std::move(contact)); + return Sexp::make_list( + /* name */ + Sexp::make_string(contact.name, true/*?nil*/), + /* dot */ + Sexp::make_symbol("."), + /* email */ + Sexp::make_string(contact.email)); } static void @@ -86,9 +82,8 @@ add_list_post(Sexp::List& list, MuMsg* msg) g_return_if_fail(rx); if (g_regex_match(rx, list_post, (GRegexMatchFlags)0, &minfo)) { - auto address = (char*)g_match_info_fetch(minfo, 1); - MuMsgContact contact{NULL, address, {}, {}}; - list.add_prop(":list-post", Sexp::make_list(make_contact_sexp(&contact))); + auto address = (char*)g_match_info_fetch(minfo, 1); + list.add_prop(":list-post", make_contact_sexp(MessageContact{address})); g_free(address); } @@ -96,47 +91,31 @@ add_list_post(Sexp::List& list, MuMsg* msg) g_regex_unref(rx); } -struct _ContactData { - Sexp::List from, to, cc, bcc, reply_to; -}; -typedef struct _ContactData ContactData; - -static gboolean -each_contact(MuMsgContact* c, ContactData* cdata) -{ - switch (mu_msg_contact_type(c)) { - case MU_MSG_CONTACT_TYPE_FROM: cdata->from.add(make_contact_sexp(c)); break; - case MU_MSG_CONTACT_TYPE_TO: cdata->to.add(make_contact_sexp(c)); break; - case MU_MSG_CONTACT_TYPE_CC: cdata->cc.add(make_contact_sexp(c)); break; - case MU_MSG_CONTACT_TYPE_BCC: cdata->bcc.add(make_contact_sexp(c)); break; - case MU_MSG_CONTACT_TYPE_REPLY_TO: cdata->reply_to.add(make_contact_sexp(c)); break; - default: g_return_val_if_reached(FALSE); return FALSE; - } - return TRUE; -} - -static void -add_prop_nonempty_list(Sexp::List& list, std::string&& name, Sexp::List&& sexp) -{ - if (sexp.empty()) - return; - - list.add_prop(std::move(name), Sexp::make_list(std::move(sexp))); -} - static void add_contacts(Sexp::List& list, MuMsg* msg) { - ContactData cdata{}; - mu_msg_contact_foreach(msg, (MuMsgContactForeachFunc)each_contact, &cdata); + using ContactPair = std::pair; + constexpr std::array contact_types = {{ + { MessageContact::Type::From, ":from" }, + { MessageContact::Type::To, ":to" }, + { MessageContact::Type::Cc, ":cc" }, + { MessageContact::Type::ReplyTo, ":reply-to" }, + { MessageContact::Type::Bcc, ":bcc" }, + }}; + + for (auto&& contact_type : contact_types) { + + const auto contacts{mu_msg_get_contacts(msg, contact_type.first)}; + if (contacts.empty()) + continue; - add_prop_nonempty_list(list, ":from", std::move(cdata.from)); - add_prop_nonempty_list(list, ":to", std::move(cdata.to)); - add_prop_nonempty_list(list, ":cc", std::move(cdata.cc)); - add_prop_nonempty_list(list, ":bcc", std::move(cdata.bcc)); - add_prop_nonempty_list(list, ":reply-to", std::move(cdata.reply_to)); + Sexp::List c_list; + for (auto&& contact: contacts) + c_list.add(make_contact_sexp(contact)); - add_list_post(list, msg); + list.add_prop(std::string{contact_type.second}, + Sexp::make_list(std::move(c_list))); + } } @@ -383,8 +362,10 @@ Mu::msg_to_sexp_list(MuMsg* msg, unsigned docid, MuMsgOptions opts) /* in the no-headers-only case (see below) we get a more complete list of contacts, so no * need to get them here if that's the case */ - if (opts & MU_MSG_OPTION_HEADERS_ONLY) + if (opts & MU_MSG_OPTION_HEADERS_ONLY) { add_contacts(items, msg); + add_list_post(items, msg); + } add_prop_nonempty(items, ":references", mu_msg_get_references(msg)); add_prop_nonempty(items, ":in-reply-to", mu_msg_get_header(msg, "In-Reply-To")); diff --git a/lib/mu-msg.cc b/lib/mu-msg.cc index 796abaad..bbb6e3d0 100644 --- a/lib/mu-msg.cc +++ b/lib/mu-msg.cc @@ -17,6 +17,7 @@ ** */ +#include #include #include #include @@ -26,7 +27,12 @@ #include #include +#include +#include + +#include "gmime/gmime-message.h" +#include "mu-message-contact.hh" #include "mu-msg-priv.hh" /* include before mu-msg.h */ #include "mu-msg.hh" #include "utils/mu-str.h" @@ -608,131 +614,66 @@ Mu::mu_msg_get_field_numeric(MuMsg* self, MuMsgFieldId mfid) return get_num_field(self, mfid); } -static gboolean -fill_contact(MuMsgContact* self, InternetAddress* addr, MuMsgContactType ctype) + +static Mu::MessageContacts +get_all_contacts(MuMsg *self) { - if (!addr) - return FALSE; + MessageContacts contacts; - self->full_address = internet_address_to_string(addr, NULL, FALSE); - - self->name = internet_address_get_name(addr); - if (mu_str_is_empty(self->name)) { - self->name = NULL; + for (auto&& mtype : { MessageContact::Type::From, MessageContact::Type::To, + MessageContact::Type::Cc, MessageContact::Type::ReplyTo, + MessageContact::Type::Bcc}) { + auto type_contacts{mu_msg_get_contacts(self, mtype)}; + + contacts.reserve(contacts.size() + type_contacts.size()); + contacts.insert(contacts.end(), type_contacts.begin(), type_contacts.end()); } - self->type = ctype; - - /* we only support internet mailbox addresses; if we don't - * check, g_mime hits an assert - */ - if (INTERNET_ADDRESS_IS_MAILBOX(addr)) - self->email = internet_address_mailbox_get_addr(INTERNET_ADDRESS_MAILBOX(addr)); - else - self->email = NULL; - - /* if there's no address, just a name, it's probably a local - * address (without @) */ - if (self->name && !self->email) - self->email = self->name; - - /* note, the address could be NULL e.g. when the recipient is something - * like 'Undisclosed recipients' - */ - return self->email != NULL; + return contacts; } - -static void -address_list_foreach(InternetAddressList* addrlist, - MuMsgContactType ctype, - MuMsgContactForeachFunc func, - gpointer user_data) + +Mu::MessageContacts +Mu::mu_msg_get_contacts(MuMsg *self, MessageContact::Type mtype) { - int i, len; + typedef const char*(*AddressFunc)(MuMsg*); + using AddressInfo = std::pair; - if (!addrlist) - return; + g_return_val_if_fail(self, MessageContacts{}); - len = internet_address_list_length(addrlist); + if (mtype == MessageContact::Type::Unknown) + return get_all_contacts(self); + + const auto info = std::invoke([&]()->AddressInfo { + switch (mtype) { + case MessageContact::Type::From: + return { GMIME_ADDRESS_TYPE_FROM, mu_msg_get_from }; + case MessageContact::Type::To: + return { GMIME_ADDRESS_TYPE_TO, mu_msg_get_to }; + case MessageContact::Type::Cc: + return { GMIME_ADDRESS_TYPE_CC, mu_msg_get_cc }; + case MessageContact::Type::ReplyTo: + return { GMIME_ADDRESS_TYPE_REPLY_TO, {} }; + case MessageContact::Type::Bcc: + return { GMIME_ADDRESS_TYPE_BCC, mu_msg_get_bcc }; + default: + throw std::logic_error("bug"); + } + }); - for (i = 0; i != len; ++i) { - MuMsgContact contact; - gboolean keep_going; - - if (!fill_contact(&contact, internet_address_list_get_address(addrlist, i), ctype)) - continue; - - keep_going = func(&contact, user_data); - g_free((char*)contact.full_address); - - if (!keep_going) - break; + const auto mdate{mu_msg_get_date(self)}; + if (self->_file) { + if (auto&& lst{g_mime_message_get_addresses( + self->_file->_mime_msg, info.first)}; lst) + return make_message_contacts(lst, mtype, mdate); + } else if (info.second) { + if (auto&& lst_str{info.second(self)}; lst_str) + return make_message_contacts(lst_str, mtype, mdate); } + + return {}; + } -static void -addresses_foreach(const char* addrs, - MuMsgContactType ctype, - MuMsgContactForeachFunc func, - gpointer user_data) -{ - InternetAddressList* addrlist; - - if (!addrs) - return; - - addrlist = internet_address_list_parse(NULL, addrs); - if (addrlist) { - address_list_foreach(addrlist, ctype, func, user_data); - g_object_unref(addrlist); - } -} - -static void -msg_contact_foreach_file(MuMsg* msg, MuMsgContactForeachFunc func, gpointer user_data) -{ - int i; - struct { - GMimeAddressType _gmime_type; - MuMsgContactType _type; - } ctypes[] = { - {GMIME_ADDRESS_TYPE_FROM, MU_MSG_CONTACT_TYPE_FROM}, - {GMIME_ADDRESS_TYPE_REPLY_TO, MU_MSG_CONTACT_TYPE_REPLY_TO}, - {GMIME_ADDRESS_TYPE_TO, MU_MSG_CONTACT_TYPE_TO}, - {GMIME_ADDRESS_TYPE_CC, MU_MSG_CONTACT_TYPE_CC}, - {GMIME_ADDRESS_TYPE_BCC, MU_MSG_CONTACT_TYPE_BCC}, - }; - - for (i = 0; i != G_N_ELEMENTS(ctypes); ++i) { - InternetAddressList* addrlist; - addrlist = - g_mime_message_get_addresses(msg->_file->_mime_msg, ctypes[i]._gmime_type); - address_list_foreach(addrlist, ctypes[i]._type, func, user_data); - } -} - -static void -msg_contact_foreach_doc(MuMsg* msg, MuMsgContactForeachFunc func, gpointer user_data) -{ - addresses_foreach(mu_msg_get_from(msg), MU_MSG_CONTACT_TYPE_FROM, func, user_data); - addresses_foreach(mu_msg_get_to(msg), MU_MSG_CONTACT_TYPE_TO, func, user_data); - addresses_foreach(mu_msg_get_cc(msg), MU_MSG_CONTACT_TYPE_CC, func, user_data); - addresses_foreach(mu_msg_get_bcc(msg), MU_MSG_CONTACT_TYPE_BCC, func, user_data); -} - -void -Mu::mu_msg_contact_foreach(MuMsg* msg, MuMsgContactForeachFunc func, gpointer user_data) -{ - g_return_if_fail(msg); - g_return_if_fail(func); - - if (msg->_file) - msg_contact_foreach_file(msg, func, user_data); - else if (msg->_doc) - msg_contact_foreach_doc(msg, func, user_data); - else - g_return_if_reached(); -} gboolean Mu::mu_msg_is_readable(MuMsg* self) diff --git a/lib/mu-msg.hh b/lib/mu-msg.hh index 2023054a..2e1262f1 100644 --- a/lib/mu-msg.hh +++ b/lib/mu-msg.hh @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -439,80 +440,17 @@ bool mu_msg_move_to_maildir(MuMsg* msg, bool ignore_dups, bool new_name, GError** err); - -enum _MuMsgContactType { /* Reply-To:? */ - MU_MSG_CONTACT_TYPE_TO = 0, - MU_MSG_CONTACT_TYPE_FROM, - MU_MSG_CONTACT_TYPE_CC, - MU_MSG_CONTACT_TYPE_BCC, - MU_MSG_CONTACT_TYPE_REPLY_TO, - - MU_MSG_CONTACT_TYPE_NUM -}; -typedef guint MuMsgContactType; - -/* not a 'real' contact type */ -#define MU_MSG_CONTACT_TYPE_ALL (MU_MSG_CONTACT_TYPE_NUM + 1) - -#define mu_msg_contact_type_is_valid(MCT) ((MCT) < MU_MSG_CONTACT_TYPE_NUM) - -typedef struct { - const char* name; /**< Foo Bar */ - const char* email; /**< foo@bar.cuux */ - const char* full_address; /**< Foo Bar */ - MuMsgContactType type; /**< MU_MSG_CONTACT_TYPE_{ TO, - CC, BCC, FROM, REPLY_TO} */ -} MuMsgContact; - -/** - * macro to get the name of a contact +/** + * Get a sequence with contacts of the given type for this message. * - * @param ct a MuMsgContact - * - * @return the name + * @param msg a valid MuMsg* instance + * @param mtype the contact type; with Type::Unknown, get _all_ types. + * + * @return a sequence */ -#define mu_msg_contact_name(ct) ((ct)->name) - -/** - * macro to get the email address of a contact - * - * @param ct a MuMsgContact - * - * @return the address - */ -#define mu_msg_contact_email(ct) ((ct)->email) - -/** - * macro to get the contact type - * - * @param ct a MuMsgContact - * - * @return the contact type - */ -#define mu_msg_contact_type(ct) ((ct)->type) - -/** - * callback function - * - * @param contact - * @param user_data a user provided data pointer - * - * @return TRUE if we should continue the foreach, FALSE otherwise - */ -typedef gboolean (*MuMsgContactForeachFunc)(MuMsgContact* contact, gpointer user_data); - -/** - * call a function for each of the contacts in a message; the order is: - * from to cc bcc (of each there are zero or more) - * - * @param msg a valid MuMsgGMime* instance - * @param func a callback function to call for each contact; when - * the callback does not return TRUE, it won't be called again - * @param user_data a user-provide pointer that will be passed to the callback - * - */ -void mu_msg_contact_foreach(MuMsg* msg, MuMsgContactForeachFunc func, gpointer user_data); - +Mu::MessageContacts mu_msg_get_contacts (MuMsg *self, + MessageContact::Type mtype=MessageContact::Type::Unknown); + /** * create a 'display contact' from an email header To/Cc/Bcc/From-type address * ie., turn @@ -534,6 +472,8 @@ void mu_msg_contact_foreach(MuMsg* msg, MuMsgContactForeachFunc func, gpointer u const char* mu_str_display_contact_s(const char* str) G_GNUC_CONST; char* mu_str_display_contact(const char* str) G_GNUC_WARN_UNUSED_RESULT; + + struct QueryMatch; /** diff --git a/lib/tests/test-mu-msg.cc b/lib/tests/test-mu-msg.cc index 3070e9d6..16846f5c 100644 --- a/lib/tests/test-mu-msg.cc +++ b/lib/tests/test-mu-msg.cc @@ -31,6 +31,7 @@ #include "test-mu-common.hh" #include "mu-msg.hh" #include "utils/mu-str.h" +#include "utils/mu-utils.hh" using namespace Mu; @@ -57,30 +58,11 @@ get_msg(const char* path) return msg; } -static gboolean -check_contact_01(MuMsgContact* contact, int* idx) -{ - switch (*idx) { - case 0: - g_assert_cmpstr(mu_msg_contact_name(contact), ==, "Mickey Mouse"); - g_assert_cmpstr(mu_msg_contact_email(contact), ==, "anon@example.com"); - break; - case 1: - g_assert_cmpstr(mu_msg_contact_name(contact), ==, "Donald Duck"); - g_assert_cmpstr(mu_msg_contact_email(contact), ==, "gcc-help@gcc.gnu.org"); - break; - default: g_assert_not_reached(); - } - ++(*idx); - - return TRUE; -} static void test_mu_msg_01(void) { MuMsg* msg; - gint i; msg = get_msg(MU_TESTMAILDIR4 "/1220863042.12663_1.mindcrime!2,S"); @@ -96,38 +78,21 @@ test_mu_msg_01(void) "contact gcc-help-help@gcc.gnu.org; run by ezmlm"); g_assert_true(mu_msg_get_prio(msg) == Mu::MessagePriority::Normal); g_assert_cmpuint(mu_msg_get_date(msg), ==, 1217530645); - - i = 0; - mu_msg_contact_foreach(msg, (MuMsgContactForeachFunc)check_contact_01, &i); - g_assert_cmpint(i, ==, 2); - + + const auto contacts{mu_msg_get_contacts(msg)}; + g_assert_cmpuint(contacts.size(), == , 2); + g_assert_true(contacts[0].name == "Mickey Mouse"); + g_assert_true(contacts[0].email == "anon@example.com"); + g_assert_true(contacts[1].name == "Donald Duck"); + g_assert_true(contacts[1].email == "gcc-help@gcc.gnu.org"); + mu_msg_unref(msg); } -static gboolean -check_contact_02(MuMsgContact* contact, int* idx) -{ - switch (*idx) { - case 0: - g_assert_cmpstr(mu_msg_contact_name(contact), ==, NULL); - g_assert_cmpstr(mu_msg_contact_email(contact), ==, "anon@example.com"); - break; - case 1: - g_assert_cmpstr(mu_msg_contact_name(contact), ==, NULL); - g_assert_cmpstr(mu_msg_contact_email(contact), ==, "help-gnu-emacs@gnu.org"); - break; - default: g_assert_not_reached(); - } - ++(*idx); - - return TRUE; -} - static void test_mu_msg_02(void) { MuMsg* msg; - int i; msg = get_msg(MU_TESTMAILDIR4 "/1220863087.12663_19.mindcrime!2,S"); @@ -142,9 +107,13 @@ test_mu_msg_02(void) == Mu::MessagePriority::Low); g_assert_cmpuint(mu_msg_get_date(msg), ==, 1218051515); - i = 0; - mu_msg_contact_foreach(msg, (MuMsgContactForeachFunc)check_contact_02, &i); - g_assert_cmpint(i, ==, 2); + const auto contacts{mu_msg_get_contacts(msg)}; + g_assert_cmpuint(contacts.size(), == , 2); + g_assert_true(contacts[0].name.empty()); + g_assert_true(contacts[0].email == "anon@example.com"); + g_assert_true(contacts[1].name.empty()); + g_assert_true(contacts[1].email == "help-gnu-emacs@gnu.org"); + g_print("flags: %s\n", Mu::message_flags_to_string(mu_msg_get_flags(msg)).c_str()); g_assert_true(mu_msg_get_flags(msg) == (MessageFlags::Seen|MessageFlags::MailingList));