lib: support ignoring addresses for contacts-cache

Skip annoying 'noreply' & friends.
This commit is contained in:
Dirk-Jan C. Binnema 2023-07-01 19:03:10 +03:00
parent 960a436e77
commit 5dc41ed811
3 changed files with 96 additions and 18 deletions

View File

@ -43,15 +43,16 @@ struct Property {
enum struct Id { enum struct Id {
BatchSize, /**< Xapian batch-size */ BatchSize, /**< Xapian batch-size */
Contacts, /**< Cache of contact information */ 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 */ LastChange, /**< Time of last change */
LastIndex, /**< Time of last index */ LastIndex, /**< Time of last index */
MaxMessageSize, /**< Maximum message size (in bytes) */ MaxMessageSize, /**< Maximum message size (in bytes) */
PersonalAddresses, /**< List of personal e-mail addresses */ PersonalAddresses, /**< List of personal e-mail addresses */
RootMaildir, /**< Root maildir path */ RootMaildir, /**< Root maildir path */
SchemaVersion, /**< Xapian DB schema version */ SchemaVersion, /**< Xapian DB schema version */
/* <private> */ /* <private> */
_count_ /* Number of Ids */ _count_ /* Number of Ids */
}; };
static constexpr size_t id_size = static_cast<size_t>(Id::_count_); static constexpr size_t id_size = static_cast<size_t>(Id::_count_);
@ -118,6 +119,15 @@ public:
{}, {},
"Database creation time" "Database creation time"
}, },
{
Id::IgnoredAddresses,
Type::StringList,
Flags::Configurable,
"ignored-addresses",
{},
"E-mail addresses ignored for the contacts-cache, "
"literal or /regexp/"
},
{ {
Id::LastChange, Id::LastChange,
Type::Timestamp, Type::Timestamp,
@ -147,8 +157,8 @@ public:
Type::StringList, Type::StringList,
Flags::Configurable, Flags::Configurable,
"personal-addresses", "personal-addresses",
"", {},
"List of personal e-mail addresses, literal or /regexp/" "Personal e-mail addresses, literal or /regexp/"
}, },
{ {
Id::RootMaildir, Id::RootMaildir,

View File

@ -49,8 +49,10 @@ struct ContactsCache::Private {
Private(Config& config_db) Private(Config& config_db)
:config_db_{config_db}, :config_db_{config_db},
contacts_{deserialize(config_db_.get<Config::Id::Contacts>())}, contacts_{deserialize(config_db_.get<Config::Id::Contacts>())},
personal_plain_{make_personal_plain(config_db_.get<Config::Id::PersonalAddresses>())}, personal_plain_{make_plain(config_db_.get<Config::Id::PersonalAddresses>())},
personal_rx_{make_personal_rx(config_db_.get<Config::Id::PersonalAddresses>())}, personal_rx_{make_rx(config_db_.get<Config::Id::PersonalAddresses>())},
ignored_plain_{make_plain(config_db_.get<Config::Id::IgnoredAddresses>())},
ignored_rx_{make_rx(config_db_.get<Config::Id::IgnoredAddresses>())},
dirty_{0} dirty_{0}
{} {}
@ -61,12 +63,16 @@ struct ContactsCache::Private {
ContactUMap deserialize(const std::string&) const; ContactUMap deserialize(const std::string&) const;
void serialize() const; void serialize() const;
Config& config_db_; Config& config_db_;
ContactUMap contacts_; ContactUMap contacts_;
mutable std::mutex mtx_; mutable std::mutex mtx_;
const StringVec personal_plain_; const StringVec personal_plain_;
const std::vector<Regex> personal_rx_; const std::vector<Regex> personal_rx_;
const StringVec ignored_plain_;
const std::vector<Regex> ignored_rx_;
mutable size_t dirty_; mutable size_t dirty_;
private: private:
@ -77,7 +83,7 @@ private:
* *
* @return * @return
*/ */
StringVec make_personal_plain(const StringVec& personal) const { StringVec make_plain(const StringVec& personal) const {
StringVec svec; StringVec svec;
std::copy_if(personal.begin(), personal.end(), std::copy_if(personal.begin(), personal.end(),
std::back_inserter(svec), [&](auto&& p) { std::back_inserter(svec), [&](auto&& p) {
@ -94,7 +100,7 @@ private:
* *
* @return * @return
*/ */
std::vector<Regex> make_personal_rx(const StringVec& personal) const { std::vector<Regex> make_rx(const StringVec& personal) const {
std::vector<Regex> rxvec; std::vector<Regex> rxvec;
for(auto&& p: personal) { for(auto&& p: personal) {
if (p.size() < 2 || p[0] != '/' || p[p.length()- 1] != '/') if (p.size() < 2 || p[0] != '/' || p[p.length()- 1] != '/')
@ -209,6 +215,11 @@ ContactsCache::add(Contact&& contact)
return; return;
} }
if (is_ignored(contact.email)) {
/* ignored this address, e.g. 'noreply@example.com */
return;
}
std::lock_guard<std::mutex> l_{priv_->mtx_}; std::lock_guard<std::mutex> l_{priv_->mtx_};
++priv_->dirty_; ++priv_->dirty_;
@ -257,7 +268,6 @@ ContactsCache::add(Contacts&& contacts, bool& personal)
} }
} }
const Contact* const Contact*
ContactsCache::_find(const std::string& email) const ContactsCache::_find(const std::string& email) const
{ {
@ -343,14 +353,14 @@ ContactsCache::for_each(const EachContactFunc& each_contact) const
} }
} }
bool static bool
ContactsCache::is_personal(const std::string& addr) const address_matches(const std::string& addr, const StringVec& plain, const std::vector<Regex>& regexes)
{ {
for (auto&& p : priv_->personal_plain_) for (auto&& p : plain)
if (g_ascii_strcasecmp(addr.c_str(), p.c_str()) == 0) if (g_ascii_strcasecmp(addr.c_str(), p.c_str()) == 0)
return true; return true;
for (auto&& rx : priv_->personal_rx_) { for (auto&& rx : regexes) {
if (rx.matches(addr)) if (rx.matches(addr))
return true; return true;
} }
@ -358,6 +368,22 @@ ContactsCache::is_personal(const std::string& addr) const
return false; 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 #ifdef BUILD_TESTS
/* /*
* Tests. * Tests.
@ -432,6 +458,36 @@ test_mu_contacts_cache_personal()
g_assert_false(contacts.is_personal("bar-zzz@fnorb.xr")); g_assert_false(contacts.is_personal("bar-zzz@fnorb.xr"));
} }
static void
test_mu_contacts_cache_ignored()
{
MemDb xdb{};
Config cdb{xdb};
cdb.set<Config::Id::IgnoredAddresses>
(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 static void
test_mu_contacts_cache_foreach() 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/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/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/for-each", test_mu_contacts_cache_foreach);
g_test_add_func("/lib/contacts-cache/sort", test_mu_contacts_cache_sort); g_test_add_func("/lib/contacts-cache/sort", test_mu_contacts_cache_sort);

View File

@ -53,10 +53,10 @@ public:
/** /**
* Add a contact * 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 * @param contact a Contact object
*
*/ */
void add(Contact&& contact); void add(Contact&& contact);
@ -67,7 +67,8 @@ public:
* if any of the contacts matches one of the personal addresses, * if any of the contacts matches one of the personal addresses,
* any of the senders/recipients are considered "personal" * 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 contacts a Contact object sequence
* @param is_personal receives true if any of the contacts was personal; * @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; 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 * 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. * the returned ptr can be invalidated at any time; only for unit-tests.