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 {
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 */
/* <private> */
_count_ /* Number of Ids */
_count_ /* Number of Ids */
};
static constexpr size_t id_size = static_cast<size_t>(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,

View File

@ -49,8 +49,10 @@ struct ContactsCache::Private {
Private(Config& config_db)
:config_db_{config_db},
contacts_{deserialize(config_db_.get<Config::Id::Contacts>())},
personal_plain_{make_personal_plain(config_db_.get<Config::Id::PersonalAddresses>())},
personal_rx_{make_personal_rx(config_db_.get<Config::Id::PersonalAddresses>())},
personal_plain_{make_plain(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}
{}
@ -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<Regex> personal_rx_;
const StringVec ignored_plain_;
const std::vector<Regex> 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<Regex> make_personal_rx(const StringVec& personal) const {
std::vector<Regex> make_rx(const StringVec& personal) const {
std::vector<Regex> 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<std::mutex> 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<Regex>& 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<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
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);

View File

@ -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.