diff options
| author | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-09-02 23:19:54 +0300 |
|---|---|---|
| committer | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-09-02 23:19:54 +0300 |
| commit | da69dd6815d75e3bc046ac74f5dc04f2babb5bd3 (patch) | |
| tree | 7b4264cf19e50bf318b95ffb1541e6c87d456ffb /lib | |
| parent | fa65ac02fc6b7ef32d2a2cefeec89b237191087f (diff) | |
mu: refactor labels code
Split labels-cache and store-labels.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/meson.build | 3 | ||||
| -rw-r--r-- | lib/mu-contacts-cache.cc | 1 | ||||
| -rw-r--r-- | lib/mu-labels-cache.cc | 79 | ||||
| -rw-r--r-- | lib/mu-labels-cache.hh | 148 | ||||
| -rw-r--r-- | lib/mu-store-labels.cc | 24 | ||||
| -rw-r--r-- | lib/mu-store-labels.hh | 160 | ||||
| -rw-r--r-- | lib/mu-store.cc | 17 | ||||
| -rw-r--r-- | lib/mu-store.hh | 3 | ||||
| -rw-r--r-- | lib/mu-xapian-db.hh | 2 |
9 files changed, 251 insertions, 186 deletions
diff --git a/lib/meson.build b/lib/meson.build index 35b61be..eb772ef 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,4 +1,4 @@ -## Copyright (C) 2021-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> +## Copyright (C) 2021-2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> ## ## 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 @@ -24,6 +24,7 @@ lib_mu=static_library( # db 'mu-config.cc', 'mu-contacts-cache.cc', + 'mu-labels-cache.cc', 'mu-store.cc', 'mu-store-labels.cc', 'mu-xapian-db.cc', diff --git a/lib/mu-contacts-cache.cc b/lib/mu-contacts-cache.cc index b422ed9..66cb4d7 100644 --- a/lib/mu-contacts-cache.cc +++ b/lib/mu-contacts-cache.cc @@ -273,7 +273,6 @@ ContactsCache::size() const return priv_->contacts_.size(); } - /** * Wrapper to allow for Contact sorting * (needed due to reference_wrapper) diff --git a/lib/mu-labels-cache.cc b/lib/mu-labels-cache.cc new file mode 100644 index 0000000..0e4fa83 --- /dev/null +++ b/lib/mu-labels-cache.cc @@ -0,0 +1,79 @@ +/* +** Copyright (C) 2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> +** +** 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 +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#include "mu-labels-cache.hh" +#include "mu-store.hh" +#include "message/mu-labels.hh" + +using namespace Mu; + +void +Mu::LabelsCache::serialize() const +{ + std::string s; + for (const auto&[label, n]: label_map_) + s += mu_format("{}{}{}\n", label, SepaChar2, n); + + config_.set<Config::Id::Labels>(s); + mu_debug("labels: serialized {} change(s)", dirty_); + dirty_ = 0; +} + +Mu::LabelsCache::Map +Mu::LabelsCache::deserialize(const std::string& serialized) const +{ + Map map; + + std::stringstream ss{serialized, std::ios_base::in}; + std::string line; + + while (std::getline(ss, line)) { + if (const auto parts = + Mu::split(line, SepaChar2); parts.size() != 2) + mu_warning("error: '{}'", line); + else + map.emplace(std::move(parts[0]), + static_cast<std::size_t>( + g_ascii_strtoll(parts[1].c_str(),{}, 10))); + } + return map; +} + +Result<void> +Mu::LabelsCache::restore(const Store& store) +{ + const auto res{store.run_query("")}; + if (!res) + return Err(Error{Error::Code::Query, + "failed to run query: {}", + *res.error().what()}); + label_map_.clear(); + ++dirty_; + + for (auto&& item: *res) { + if (auto &&msg{item.message()}; msg) { + for (const auto& label: msg->labels()) + increase(label); + } + } + + serialize(); + + return Ok(); +} diff --git a/lib/mu-labels-cache.hh b/lib/mu-labels-cache.hh new file mode 100644 index 0000000..9f95d09 --- /dev/null +++ b/lib/mu-labels-cache.hh @@ -0,0 +1,148 @@ +/* +** Copyright (C) 2025 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> +** +** 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 +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#ifndef MU_LABELS_CACHE_HH +#define MU_LABELS_CACHE_HH + +#include <set> +#include <string> +#include <unordered_map> + +#include "utils/mu-utils.hh" +#include "utils/mu-option.hh" +#include "message/mu-labels.hh" + +#include "mu-config.hh" + +namespace Mu { + +class Store; // fwd declaration + +/** + * The cache keeps track of what labels are being used. This can be used + * for completion etc. and `mu label list` + */ +class LabelsCache { +public: + // maps a label to a number of occurrences + using Map = std::unordered_map<std::string, size_t>; + + /** + * CTOR + * + * Deserialize the map from the configuration. + * + * @param serialized serialization string + */ + LabelsCache(Config& config): config_{config}, + label_map_{deserialize(config_.get<Config::Id::Labels>())}, + dirty_{false}{} + + ~LabelsCache() { + if (dirty_) + serialize(); + }; + + /** + * Add a label occurrence to the cache + * + * @param label some label + */ + void increase(const std::string& label) { + if (auto it = label_map_.find(label); it == label_map_.end()) + label_map_.insert({label, 1}); + else + ++it->second; + ++dirty_; + } + /** + * Remove a label occurrence from the cache + * + * Removes the label completely if this was the _last_ occurence. + * + * @param label some label + */ + void decrease(const std::string& label) { + if (auto it = label_map_.find(label); it != label_map_.end()) { + if (it->second == 1) + label_map_.erase(it); + else + --it->second; + ++dirty_; + } + } + + /** + * Update the cache with the label changes + * + * @param updates a vector of delta-labels + */ + void update(const Labels::DeltaLabelVec& updates) { + for(const auto& [delta, label]: updates) { + switch(delta) { + case Labels::Delta::Add: + increase(label); + break; + case Labels::Delta::Remove: + decrease(label); + break; + } + } + } + + /** + * Return a copy of the label-map + * + * @return the label-map + */ + Map label_map() const { return label_map_; } + + // serialization/deserialization could be optimized, but is not super + // time-critical + + /** + * Serialize the cache into Config if there are changes. + * + */ + void serialize() const; + + /** + * Restore the labels-cache from the labels seen in the store. + * + * @param store a store + * + * @return Ok() or some error + */ + Result<void> restore(const Store& store); + +private: + /** + * Deserialize the cache into a Map + * + * @return serialized cache + */ + Map deserialize(const std::string& serialized) const; + + Config& config_; + Map label_map_; + mutable size_t dirty_{}; +}; + +} // namespace Mux +#endif /*MU_LABELS_CACHE_HH*/ diff --git a/lib/mu-store-labels.cc b/lib/mu-store-labels.cc index 8653c84..a08f3b4 100644 --- a/lib/mu-store-labels.cc +++ b/lib/mu-store-labels.cc @@ -18,30 +18,14 @@ */ #include "mu-store-labels.hh" + +#include <istream> +#include <sstream> + #include "mu-store.hh" -#include "message/mu-labels.hh" using namespace Mu; -Result<void> -Mu::LabelsCache::restore(const Store& store) -{ - const auto res{store.run_query("")}; - if (!res) - return Err(Error{Error::Code::Query, - "failed to run query: {}", - *res.error().what()}); - label_map_.clear(); - - for (auto&& item: *res) { - if (auto &&msg{item.message()}; msg) { - for (const auto& label: msg->labels()) - increase(label); - } - } - return Ok(); -} - namespace { constexpr std::string_view path_key = "path:"; constexpr std::string_view message_id_key = "message-id:"; diff --git a/lib/mu-store-labels.hh b/lib/mu-store-labels.hh index 6104973..919e9de 100644 --- a/lib/mu-store-labels.hh +++ b/lib/mu-store-labels.hh @@ -17,160 +17,16 @@ ** */ -#ifndef MU_LABELS_CACHE_HH -#define MU_LABELS_CACHE_HH - -#include <set> #include <string> -#include <unordered_map> +#include "mu-option.hh" +#include "mu-result.hh" -#include "utils/mu-utils.hh" -#include "utils/mu-option.hh" -#include "message/mu-labels.hh" +#ifndef MU_STORE_LABELS_HH +#define MU_STORE_LABELS_HH namespace Mu { -class Store; - -/** - * The cache keeps track of what labels are being used. This can be used - * for completion etc. and `mu label list` - */ -class LabelsCache { -public: - // maps a label to a number of occurrences - using Map = std::unordered_map<std::string, size_t>; - /** - * CTOR - * - * Deserialize the map from a string - * - * @param serialized serialization string - */ - LabelsCache(const std::string serialized = {}): label_map_{deserialize(serialized)} { - } - - /** - * Add a label occurrence to the cache - * - * @param label some label - */ - void increase(const std::string& label) { - if (auto it = label_map_.find(label); it == label_map_.end()) - label_map_.insert({label, 1}); - else - ++it->second; - dirty_ = true; - } - /** - * Remove a label occurrence from the cache - * - * Removes the label completely if this was the _last_ occurence. - * - * @param label some label - */ - void decrease(const std::string& label) { - if (auto it = label_map_.find(label); it != label_map_.end()) { - if (it->second == 1) - label_map_.erase(it); - else - --it->second; - dirty_ = true; - } - } - - /** - * Update the cache with the label changes - * - * @param updates a vector of delta-labels - */ - void update(const Labels::DeltaLabelVec& updates) { - for(const auto& [delta, label]: updates) { - switch(delta) { - case Labels::Delta::Add: - increase(label); - break; - case Labels::Delta::Remove: - decrease(label); - break; - } - } - } - - /** - * Return a copy of the label-map - * - * @return the label-map - */ - Map label_map() const { return label_map_; } - - // serialization/deserialization could be optimized, but is not super - // time-critical - - /** - * Serialize the cache into a string. - * - * Note: this also marks the cache a _non_ dirty; - * - * @return serialized cache - */ - [[nodiscard]] std::string serialize() const { - std::string s; - for (const auto&[label, n]: label_map_) - s += mu_format("{}{}{}\n", label, SepaChar2, n); - dirty_ = false; - return s; - } - - /** - * Deserialize the cache into a Map - * - * @return serialized cache - */ - Map deserialize(const std::string& serialized) const { - - Map map; - std::stringstream ss{serialized, std::ios_base::in}; - std::string line; - - while (std::getline(ss, line)) { - if (const auto parts = - Mu::split(line, SepaChar2); parts.size() != 2) - mu_warning("error: '{}'", line); - else - map.emplace(std::move(parts[0]), - static_cast<std::size_t>( - g_ascii_strtoll(parts[1].c_str(),{}, 10))); - } - return map; - } - - - /** - * Restore the labels-cache from the labels seen in the store. - * - * @param store a store - * - * @return Ok() or some error - */ - Result<void> restore(const Store& store); - - - /** - * Is the cache "dirty"? - * - * I.e. have there been changes since "serialize()" was called? - * - * @return true or false - */ - bool dirty() const { - return dirty_; - } - -private: - Map label_map_; - mutable bool dirty_{}; -}; +class Store; // fwd declaration /** * Export labels to a file @@ -187,7 +43,6 @@ private: Result<std::string> export_labels(const Store& store, const std::string& query="", Option<std::string> path={}); - /** * Import labels from a file * @@ -203,6 +58,7 @@ Result<std::string> export_labels(const Store& store, */ Result<void> import_labels(Store&, const std::string& path, bool dry_run, bool quiet, bool verbose); +} // Mu + -} // namespace Mux -#endif /*MU_LABELS_CACHE_HH*/ +#endif /*MU_STORE_LABELS_HH*/ diff --git a/lib/mu-store.cc b/lib/mu-store.cc index de148af..e5ba902 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -35,10 +35,10 @@ #include "mu-query.hh" #include "mu-xapian-db.hh" #include "mu-scanner.hh" +#include "mu-store-labels.hh" #include "utils/mu-error.hh" - #include "utils/mu-utils.hh" #include <utils/mu-utils-file.hh> @@ -66,7 +66,7 @@ struct Store::Private { : XapianDb::Flavor::Open)}, config_{xapian_db_}, contacts_cache_{config_}, - labels_cache_{config_.get<Config::Id::Labels>()}, + labels_cache_{config_}, root_maildir_{remove_slash(config_.get<Config::Id::RootMaildir>())}, message_opts_{make_message_options(config_)} {} @@ -76,7 +76,7 @@ struct Store::Private { xapian_db_{XapianDb(path, XapianDb::Flavor::CreateOverwrite)}, config_{make_config(xapian_db_, root_maildir, conf)}, contacts_cache_{config_}, - labels_cache_{config_.get<Config::Id::Labels>()}, + labels_cache_{config_}, root_maildir_{remove_slash(config_.get<Config::Id::RootMaildir>())}, message_opts_{make_message_options(config_)} { // so tell xapian-db to update its internal cached values from @@ -85,10 +85,6 @@ struct Store::Private { } ~Private() try { - if (!xapian_db_.read_only()) { - if (const auto res = serialize(); !res) - mu_critical("failed to serialize: {}", res.error().what()); - } mu_debug("closing store @ {}", xapian_db_.path()); } catch (...) { mu_critical("caught exception in store dtor"); @@ -156,9 +152,7 @@ Store::Private::serialize() mu_debug("serialize data into store @ {}", xapian_db_.path()); contacts_cache_.serialize(); // does the 'dirty' check internally. - - if (labels_cache_.dirty()) - config_.set<Config::Id::Labels>(labels_cache_.serialize()); + labels_cache_.serialize(); return Ok(); } @@ -697,6 +691,9 @@ Store::clear_labels(Message& message) Result<void> Store::restore_label_map() { + if (xapian_db().read_only()) + throw Error{Error::Code::Store, "store is read-only"}; + std::unique_lock lock{priv_->lock_}; return priv_->labels_cache_.restore(*this); diff --git a/lib/mu-store.hh b/lib/mu-store.hh index 9aff22c..8a7f13c 100644 --- a/lib/mu-store.hh +++ b/lib/mu-store.hh @@ -27,6 +27,8 @@ #include <memory> #include "mu-contacts-cache.hh" +#include "mu-labels-cache.hh" + #include "mu-xapian-db.hh" #include "mu-config.hh" #include "mu-indexer.hh" @@ -35,7 +37,6 @@ #include <utils/mu-utils.hh> #include <utils/mu-utils.hh> #include <utils/mu-option.hh> -#include "mu-store-labels.hh" #include <message/mu-message.hh> diff --git a/lib/mu-xapian-db.hh b/lib/mu-xapian-db.hh index e6f6557..1f727a3 100644 --- a/lib/mu-xapian-db.hh +++ b/lib/mu-xapian-db.hh @@ -480,7 +480,7 @@ private: if ((++changes_ < batch_size_) && !force) return; xapian_try([&]{ - mu_debug("committing {} changes; transaction={}; " + mu_debug("committing {} change(s); transaction={}; " "forced={}", changes_, in_transaction() ? "yes" : "no", force ? "yes" : "no"); |
