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:
@ -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
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user