diff options
| author | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-08-16 11:40:15 +0300 |
|---|---|---|
| committer | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-08-16 12:18:21 +0300 |
| commit | a6b1f47a30c7aea84697553da4848b626c9faaca (patch) | |
| tree | 5d4ae6435a2382d0541102ff50cf52bb75a8f9ab /lib | |
| parent | f504289a021b0296701428467c469cbdaa351923 (diff) | |
labels: refactor import/export to mu-store-labels
Move the import/export code to 'lib'.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/meson.build | 1 | ||||
| -rw-r--r-- | lib/mu-store-labels.cc | 198 | ||||
| -rw-r--r-- | lib/mu-store-labels.hh (renamed from lib/mu-labels-cache.hh) | 42 | ||||
| -rw-r--r-- | lib/mu-store.cc | 2 | ||||
| -rw-r--r-- | lib/mu-store.hh | 2 |
5 files changed, 232 insertions, 13 deletions
diff --git a/lib/meson.build b/lib/meson.build index c3a798d..35b61be 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -25,6 +25,7 @@ lib_mu=static_library( 'mu-config.cc', 'mu-contacts-cache.cc', 'mu-store.cc', + 'mu-store-labels.cc', 'mu-xapian-db.cc', # querying 'mu-query-macros.cc', diff --git a/lib/mu-store-labels.cc b/lib/mu-store-labels.cc new file mode 100644 index 0000000..5aa6c94 --- /dev/null +++ b/lib/mu-store-labels.cc @@ -0,0 +1,198 @@ +/* +** 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-store-labels.hh" +#include "mu-store.hh" +#include "message/mu-labels.hh" + +using namespace Mu; + +namespace { +constexpr std::string_view path_key = "path:"; +constexpr std::string_view message_id_key = "message-id:"; +constexpr std::string_view labels_key = "labels:"; +} + +using OutputPair = std::pair<std::ofstream, std::string>; + +static Result<OutputPair> +export_output(Option<std::string> path) +{ + const auto now_t{::time({})}; + const auto now_tm{::localtime(&now_t)}; + + const auto now{mu_format("{:%F-%T}", *now_tm)}; + auto fname = path.value_or(mu_format("mu-export-{}.txt", now)); + + auto output{std::ofstream{fname, std::ios::out}}; + if (!output.good()) + return Err(Error{Error::Code::File, + "failed pen '{}' for writing", fname}); + + mu_println(output, ";; version:0 @ {}\n", now); + + return Ok(OutputPair{std::move(output), std::move(fname)}); +} + +Result<std::string> +Mu::export_labels(const Store& store, const std::string& query, Option<std::string> path) +{ + const auto results{store.run_query(query)}; + if (!results) + return Err(Error{Error::Code::Query, + "failed to run query '{}': {}", + query, *results.error().what()}); + + auto output_res = export_output(path); + if (!output_res) + return Err(std::move(output_res.error())); + + auto&[output, output_path] = *output_res; + + for (auto&& result : *results) { + if (auto &&msg{result.message()}; msg) { + if (const auto labels{msg->labels()}; !labels.empty()) { + mu_print(output, + "{}{}\n" + "{}{}\n" + "{}{}\n\n", + path_key, msg->path(), + message_id_key, msg->message_id(), + labels_key, join(labels,',')); + } + } + } + + return Ok(std::move(output_path)); +} + +static void +log_import(bool quiet, bool verbose, const std::string& msg, bool is_err=false) +{ + if (is_err) + mu_debug("{}", msg); + else + mu_warning("{}", msg); + + if (is_err && !quiet) + mu_printerrln("{}", msg); + else if (verbose) + mu_println("{}", msg); +} + +static void +log_import_err(bool quiet, bool verbose, const std::string& msg) +{ + log_import(quiet, verbose, msg, true); +} + + +static Result<QueryResults> +log_import_get_matching(Mu::Store& store, const std::string& query, int max=1) +{ + if (auto qres = store.run_query(query, {}, {}, max); !qres) + return Err(std::move(qres.error())); + else if (qres->empty()) + return Err(Error{Error::Code::Query, + "no matching messages for {}", query}); + else + return Ok(std::move(*qres)); +} + + +static void +import_labels_for_message(Mu::Store& store, + bool dry_run, bool quiet, bool verbose, + const std::string& path, const std::string& msgid, + const std::vector<std::string> labels) +{ + using namespace Labels; + + Labels::DeltaLabelVec delta_labels{}; + std::transform(labels.begin(), labels.end(), + std::back_inserter(delta_labels), + [](const auto& label) { + return DeltaLabel{Delta::Add, label}; }); + + const auto qres = [&]()->Result<QueryResults>{ + // plan A: match by path + if (auto qres_a{log_import_get_matching(store, "path:" + path)}; !qres_a) { + log_import_err(quiet, verbose, mu_format("failed to find by path: {}; try with message-id", + qres_a.error().what())); + // plan B: try the message-id + return log_import_get_matching(store, "msgid:" + msgid, -1/*all matching*/); + } else + return qres_a; + }(); + + // neither plan a or b worked? we have to give up... + if (!qres) { + log_import_err(quiet, verbose, qres.error().what()); + return; + } + + // we have match(es)! + for (auto&& item: *qres) { + auto msg{*item.message()}; + if (dry_run ) + mu_println("labels: would apply label '{}' to {}", join(labels, ","), path); + else if (const auto res = store.update_labels(msg, delta_labels); !res) + log_import_err(quiet, verbose, + mu_format("failed to update labels for {}: {}", + msg.path(), res.error().what())); + else + log_import(quiet, verbose, + mu_format("applied labels {} to {}", join(labels, ","), path)); + } +} + +Result<void> +Mu::import_labels(Mu::Store& store, const std::string& path, bool dry_run, bool quiet, bool verbose) +{ + auto input{std::ifstream{path, std::ios::in}}; + if (!input.good()) + return Err(Error{Error::Code::File, + "failed to open '{}' for reading", + path}); + + std::string line; + std::string current_path, current_msgid; + std::vector<std::string> current_labels; + + while (std::getline(input, line)) { + + if (line.find(path_key) == 0) + current_path = line.substr(path_key.length()); + else if (line.find(message_id_key) == 0) + current_msgid = line.substr(message_id_key.length()); + else if (line.find(labels_key) == 0) { + current_labels = split(line.substr(labels_key.length()), ','); + if (!current_labels.empty()) + import_labels_for_message(store, dry_run, quiet, verbose, + current_path, current_msgid, + current_labels); + current_path.clear(); + current_msgid.clear(); + current_labels.clear(); + } + // ignore anything else. + } + + return Ok(); +} diff --git a/lib/mu-labels-cache.hh b/lib/mu-store-labels.hh index eb4cfe6..55b33f4 100644 --- a/lib/mu-labels-cache.hh +++ b/lib/mu-store-labels.hh @@ -26,6 +26,7 @@ #include <unordered_map> #include "utils/mu-utils.hh" +#include "utils/mu-option.hh" #include "message/mu-labels.hh" namespace Mu { @@ -49,17 +50,6 @@ public: } /** - * Construct a new ContactsCache object - * - * @param config db configuration database object - */ - LabelsCache(Config& config) { - - - } - - - /** * Add a label occurrence to the cache * * @param label @@ -152,5 +142,35 @@ private: Map label_map_; }; +class Store; + +/** + * Export labels to a file + * + * If path is not specified, use a file in the current directory + * + * @param store a store object + * @param query for the message whose labels to export + * @param path the path or nothing + * + * @return either the output filename or some error + */ +Result<std::string> export_labels(const Store& store, const std::string& query="", Option<std::string> path); + +/** + * Import labels from a file + * + * If path is not specified, use a file in the current directory + * + * @param store a store object + * @param path the path to the file + * @param dry_run only show what would be imported + * @param quiet suppress output + * @param verbose give verbose output + * + * @return Ok or some error + */ +Result<void> import_labels(Store&, const std::string& path, bool dry_run, bool quiet, bool verbose); + } // namespace Mux #endif /*MU_LABELS_CACHE_HH*/ diff --git a/lib/mu-store.cc b/lib/mu-store.cc index f81d4ad..f0a36ed 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -79,7 +79,7 @@ struct Store::Private { labels_cache_{config_.get<Config::Id::Labels>()}, root_maildir_{remove_slash(config_.get<Config::Id::RootMaildir>())}, message_opts_{make_message_options(config_)} { - // so tell xapian-db to update its internal cacheed values from + // so tell xapian-db to update its internal cached values from // config. In practice: batch-size. xapian_db_.reinit(); } diff --git a/lib/mu-store.hh b/lib/mu-store.hh index afb1d75..341da2a 100644 --- a/lib/mu-store.hh +++ b/lib/mu-store.hh @@ -35,7 +35,7 @@ #include <utils/mu-utils.hh> #include <utils/mu-utils.hh> #include <utils/mu-option.hh> -#include "mu-labels-cache.hh" +#include "mu-store-labels.hh" #include <message/mu-message.hh> |
