From 5dc41ed8112b6542bf5d7d2430bc4d55bcbaf566 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 1 Jul 2023 19:03:10 +0300 Subject: [PATCH] lib: support ignoring addresses for contacts-cache Skip annoying 'noreply' & friends. --- lib/mu-config.hh | 20 ++++++++--- lib/mu-contacts-cache.cc | 77 ++++++++++++++++++++++++++++++++++------ lib/mu-contacts-cache.hh | 17 +++++++-- 3 files changed, 96 insertions(+), 18 deletions(-) diff --git a/lib/mu-config.hh b/lib/mu-config.hh index abeb5272..df61106d 100644 --- a/lib/mu-config.hh +++ b/lib/mu-config.hh @@ -43,15 +43,16 @@ struct Property { enum struct Id { BatchSize, /**< Xapian batch-size */ Contacts, /**< Cache of contact information */ - Created, /** Time of creation */ + Created, /**< Time of creation */ + IgnoredAddresses,/**< Email addresses ignored for the contacts-cache */ LastChange, /**< Time of last change */ LastIndex, /**< Time of last index */ MaxMessageSize, /**< Maximum message size (in bytes) */ - PersonalAddresses, /**< List of personal e-mail addresses */ + PersonalAddresses, /**< List of personal e-mail addresses */ RootMaildir, /**< Root maildir path */ SchemaVersion, /**< Xapian DB schema version */ /* */ - _count_ /* Number of Ids */ + _count_ /* Number of Ids */ }; static constexpr size_t id_size = static_cast(Id::_count_); @@ -118,6 +119,15 @@ public: {}, "Database creation time" }, + { + Id::IgnoredAddresses, + Type::StringList, + Flags::Configurable, + "ignored-addresses", + {}, + "E-mail addresses ignored for the contacts-cache, " + "literal or /regexp/" + }, { Id::LastChange, Type::Timestamp, @@ -147,8 +157,8 @@ public: Type::StringList, Flags::Configurable, "personal-addresses", - "", - "List of personal e-mail addresses, literal or /regexp/" + {}, + "Personal e-mail addresses, literal or /regexp/" }, { Id::RootMaildir, diff --git a/lib/mu-contacts-cache.cc b/lib/mu-contacts-cache.cc index 853709c8..532a336b 100644 --- a/lib/mu-contacts-cache.cc +++ b/lib/mu-contacts-cache.cc @@ -49,8 +49,10 @@ struct ContactsCache::Private { Private(Config& config_db) :config_db_{config_db}, contacts_{deserialize(config_db_.get())}, - personal_plain_{make_personal_plain(config_db_.get())}, - personal_rx_{make_personal_rx(config_db_.get())}, + personal_plain_{make_plain(config_db_.get())}, + personal_rx_{make_rx(config_db_.get())}, + ignored_plain_{make_plain(config_db_.get())}, + ignored_rx_{make_rx(config_db_.get())}, dirty_{0} {} @@ -61,12 +63,16 @@ struct ContactsCache::Private { ContactUMap deserialize(const std::string&) const; void serialize() const; - Config& config_db_; + Config& config_db_; ContactUMap contacts_; mutable std::mutex mtx_; const StringVec personal_plain_; const std::vector personal_rx_; + + const StringVec ignored_plain_; + const std::vector ignored_rx_; + mutable size_t dirty_; private: @@ -77,7 +83,7 @@ private: * * @return */ - StringVec make_personal_plain(const StringVec& personal) const { + StringVec make_plain(const StringVec& personal) const { StringVec svec; std::copy_if(personal.begin(), personal.end(), std::back_inserter(svec), [&](auto&& p) { @@ -94,7 +100,7 @@ private: * * @return */ - std::vector make_personal_rx(const StringVec& personal) const { + std::vector make_rx(const StringVec& personal) const { std::vector rxvec; for(auto&& p: personal) { if (p.size() < 2 || p[0] != '/' || p[p.length()- 1] != '/') @@ -209,6 +215,11 @@ ContactsCache::add(Contact&& contact) return; } + if (is_ignored(contact.email)) { + /* ignored this address, e.g. 'noreply@example.com */ + return; + } + std::lock_guard l_{priv_->mtx_}; ++priv_->dirty_; @@ -257,7 +268,6 @@ ContactsCache::add(Contacts&& contacts, bool& personal) } } - const Contact* ContactsCache::_find(const std::string& email) const { @@ -343,14 +353,14 @@ ContactsCache::for_each(const EachContactFunc& each_contact) const } } -bool -ContactsCache::is_personal(const std::string& addr) const +static bool +address_matches(const std::string& addr, const StringVec& plain, const std::vector& regexes) { - for (auto&& p : priv_->personal_plain_) + for (auto&& p : plain) if (g_ascii_strcasecmp(addr.c_str(), p.c_str()) == 0) return true; - for (auto&& rx : priv_->personal_rx_) { + for (auto&& rx : regexes) { if (rx.matches(addr)) return true; } @@ -358,6 +368,22 @@ ContactsCache::is_personal(const std::string& addr) const return false; } + +bool +ContactsCache::is_personal(const std::string& addr) const +{ + return address_matches(addr, priv_->personal_plain_, priv_->personal_rx_); +} + +bool +ContactsCache::is_ignored(const std::string& addr) const +{ + return address_matches(addr, priv_->ignored_plain_, priv_->ignored_rx_); +} + + + + #ifdef BUILD_TESTS /* * Tests. @@ -432,6 +458,36 @@ test_mu_contacts_cache_personal() g_assert_false(contacts.is_personal("bar-zzz@fnorb.xr")); } +static void +test_mu_contacts_cache_ignored() +{ + MemDb xdb{}; + Config cdb{xdb}; + cdb.set + (StringVec{{"foo@example.com", "bar@cuux.org", "/bar-.*@fnorb.f./"}}); + ContactsCache contacts{cdb}; + + g_assert_true(contacts.is_ignored("foo@example.com")); + g_assert_true(contacts.is_ignored("Bar@CuuX.orG")); + g_assert_true(contacts.is_ignored("bar-123abc@fnorb.fi")); + g_assert_true(contacts.is_ignored("bar-zzz@fnorb.fr")); + + g_assert_false(contacts.is_ignored("foo@bar.com")); + g_assert_false(contacts.is_ignored("BÂr@CuuX.orG")); + g_assert_false(contacts.is_ignored("bar@fnorb.fi")); + g_assert_false(contacts.is_ignored("bar-zzz@fnorb.xr")); + + g_assert_cmpuint(contacts.size(),==,0); + contacts.add(Mu::Contact{"a@example.com", "a", 123, true, 1000, 0}); + g_assert_cmpuint(contacts.size(),==,1); + contacts.add(Mu::Contact{"foo@example.com", "b", 123, true, 1000, 0}); // ignored + contacts.add(Mu::Contact{"bar-123abc@fnorb.fi", "c", 123, true, 1000, 0}); // ignored + g_assert_cmpuint(contacts.size(),==,1); + contacts.add(Mu::Contact{"b@example.com", "d", 123, true, 1000, 0}); + g_assert_cmpuint(contacts.size(),==,2); +} + + static void test_mu_contacts_cache_foreach() @@ -544,6 +600,7 @@ main(int argc, char* argv[]) g_test_add_func("/lib/contacts-cache/base", test_mu_contacts_cache_base); g_test_add_func("/lib/contacts-cache/personal", test_mu_contacts_cache_personal); + g_test_add_func("/lib/contacts-cache/ignored", test_mu_contacts_cache_ignored); g_test_add_func("/lib/contacts-cache/for-each", test_mu_contacts_cache_foreach); g_test_add_func("/lib/contacts-cache/sort", test_mu_contacts_cache_sort); diff --git a/lib/mu-contacts-cache.hh b/lib/mu-contacts-cache.hh index 063dc6e0..090195b5 100644 --- a/lib/mu-contacts-cache.hh +++ b/lib/mu-contacts-cache.hh @@ -53,10 +53,10 @@ public: /** * Add a contact * - * Invalid email address are not cached (but we log a warning) + * Invalid email address are not cached (but we log a warning); neither + * are "ignored" addresses (see --ignored-address in mu-init(1)) * * @param contact a Contact object - * */ void add(Contact&& contact); @@ -67,7 +67,8 @@ public: * if any of the contacts matches one of the personal addresses, * any of the senders/recipients are considered "personal" * - * Invalid email address are not cached (but we log a warning) + * Invalid email address are not cached (but we log a warning); neither + * are "ignored" addresses (see --ignored-address in mu-init(1)) * * @param contacts a Contact object sequence * @param is_personal receives true if any of the contacts was personal; @@ -115,6 +116,16 @@ public: */ bool is_personal(const std::string& addr) const; + + /** + * Does this look like an email-address that should be ignored? + * + * @param addr some e-mail address + * + * @return true or false + */ + bool is_ignored(const std::string& addr) const; + /** * Find a contact based on the email address. This is not safe, since * the returned ptr can be invalidated at any time; only for unit-tests.