mu-cfind/contacts-cache: refactor matching in for_each

Move some of the code in from the command-line tool to contacts-cache, for
possible re-use. Clean up a bit while doing so.
This commit is contained in:
Dirk-Jan C. Binnema
2025-06-05 19:31:53 +03:00
parent 155725ff74
commit 1527976729
3 changed files with 75 additions and 44 deletions

View File

@ -285,7 +285,7 @@ struct ContactLessThan {
}; };
using ContactSet = std::set<std::reference_wrapper<const Contact>, ContactLessThan>; using ContactSet = std::set<std::reference_wrapper<const Contact>, ContactLessThan>;
void Result<size_t>
ContactsCache::for_each(const EachContactFunc& each_contact) const ContactsCache::for_each(const EachContactFunc& each_contact) const
{ {
std::lock_guard<std::mutex> l_{priv_->mtx_}; std::lock_guard<std::mutex> l_{priv_->mtx_};
@ -296,10 +296,52 @@ ContactsCache::for_each(const EachContactFunc& each_contact) const
sorted.emplace(item.second); sorted.emplace(item.second);
// return in _reverse_ order, so we get the most relevant ones first. // return in _reverse_ order, so we get the most relevant ones first.
size_t n{};
for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) { for (auto it = sorted.rbegin(); it != sorted.rend(); ++it) {
++n;
if (!each_contact(*it)) if (!each_contact(*it))
break; break;
} }
return Ok(std::move(n));
}
Result<size_t>
ContactsCache::for_each(const EachContactFunc& each_contact,
const std::string match_rx,
bool personal,
int64_t after,
size_t maxnum) const
{
// get the pattern regex, if any.
const Result<Regex> rxres = match_rx.empty() ? Ok(Regex{}) :
Regex::make(match_rx, static_cast<GRegexCompileFlags>
(G_REGEX_OPTIMIZE|G_REGEX_CASELESS));
if (!rxres)
return Err(rxres.error());
const Regex rx{*rxres};
size_t n{};
const auto res = for_each([&](const Contact& contact)->bool {
// filter for personal & "after"
if ((personal && !contact.personal) ||
(after > contact.message_date))
return true; /* skip this contact */
// filter for regex, if any.
if (rx && !rx.matches(contact.name) && !rx.matches(contact.email))
return true; /* next */
// do we have enough matches?
if (maxnum != 0 && n >= maxnum)
return false; // terminate
++n;
return each_contact(contact);
});
if (res)
return Ok(std::move(n));
else
return Err(res.error());
} }
static bool static bool

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2020-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2020-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** 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 ** under the terms of the GNU General Public License as published by the
@ -158,9 +158,30 @@ public:
* highest ranked contacts come first). * highest ranked contacts come first).
* *
* @param each_contact function invoked for each contact * @param each_contact function invoked for each contact
*
* @return The number of times the callback was invoked or some error
*/ */
void for_each(const EachContactFunc& each_contact) const; Result<size_t> for_each(const EachContactFunc& each_contact) const;
/**
* Invoke some callable for each contact, in _descending_ order of rank
* (i.e., the highest ranked contacts come first).
*
* @param each_contact function invoked for each contact
* @param match_rx string with regular expression or "" for none
* @param personal if true, only include personal contacts
* (i.e., contacts seen in message in which a personal address
* was involved)
* @param after only consider messages after this time_t (seconds since epoch)
* @param maxnum maximum number of times the callback invoked
*
* @return The number of times the callback was invoked or some error
*/
Result<size_t> for_each(const EachContactFunc& each_contact,
const std::string match_rx,
bool personal = false,
int64_t after = 0,
size_t maxnum = 0) const;
private: private:
struct Private; struct Private;
std::unique_ptr<Private> priv_; std::unique_ptr<Private> priv_;

View File

@ -1,5 +1,5 @@
/* /*
** Copyright (C) 2022-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ** Copyright (C) 2022-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
** **
** This program is free software; you can redistribute it and/or modify it ** 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 ** under the terms of the GNU General Public License as published by the
@ -16,8 +16,6 @@
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
** **
*/ */
#include "config.h"
#include "mu-cmd.hh" #include "mu-cmd.hh"
#include <cstdint> #include <cstdint>
@ -260,64 +258,34 @@ Mu::mu_cmd_cfind(const Mu::Store& store, const Mu::Options& opts)
if (!output) if (!output)
return Err(Error::Code::Internal, return Err(Error::Code::Internal,
"missing output function"); "missing output function");
// get the pattern regex, if any.
Regex rx{};
if (!opts.cfind.rx_pattern.empty()) {
if (auto&& res = Regex::make(opts.cfind.rx_pattern,
static_cast<GRegexCompileFlags>
(G_REGEX_OPTIMIZE|G_REGEX_CASELESS)); !res)
return Err(std::move(res.error()));
else
rx = res.value();
}
nicks.clear(); nicks.clear();
store.contacts_cache().for_each([&](const Contact& contact)->bool {
if (opts.cfind.maxnum && num > *opts.cfind.maxnum) const auto res = store.contacts_cache().for_each([&](const Contact& contact)->bool {
return false; /* stop the loop */
if (!store.contacts_cache().is_valid(contact.email))
return true; /* next */
// filter for maxnum, personal & "after"
if ((opts.cfind.personal && !contact.personal) ||
(opts.cfind.after.value_or(0) > contact.message_date))
return true; /* next */
// filter for regex, if any.
if (rx) {
if (!rx.matches(contact.name) && !rx.matches(contact.email))
return true; /* next */
}
/* seems we have a match! display it. */
const auto itype{num == 0 ? ItemType::Header : ItemType::Normal}; const auto itype{num == 0 ? ItemType::Header : ItemType::Normal};
output(itype, contact, opts); output(itype, contact, opts);
++num; ++num;
return true; return true;
}); }, opts.cfind.rx_pattern, opts.cfind.personal,
opts.cfind.after.value_or(0),
opts.cfind.maxnum.value_or(0U));
if (num == 0) if (!res)
return Err(res.error());
else if (*res == 0)
return Err(Error::Code::NoMatches, "no matching contacts found"); return Err(Error::Code::NoMatches, "no matching contacts found");
output(ItemType::Footer, Nothing, opts); output(ItemType::Footer, Nothing, opts);
return Ok(); return Ok();
} }
#ifdef BUILD_TESTS #ifdef BUILD_TESTS
/* /*
* Tests. * Tests.
* *
*/ */
#include "config.h"
#include "utils/mu-test-utils.hh" #include "utils/mu-test-utils.hh"
static std::string test_mu_home; static std::string test_mu_home;
static void static void