diff --git a/lib/mu-config.hh b/lib/mu-config.hh index 380bfb4c..9e81bbe2 100644 --- a/lib/mu-config.hh +++ b/lib/mu-config.hh @@ -429,6 +429,21 @@ public: return Ok(); } + /** + * Set a new value for some property; unlike set(), merely log a + * warning if that fails. For callers that cannot meaningfully + * handle the error. + * + * @param prop_id property-id + * @param val the new value (of the correct type) + */ + template + void checked_set(const T& val) { + if (const auto res{set(val)}; !res) + mu_warning("failed to set config property '{}': {}", + property().name, res.error()); + } + /** * Is this a read-only Config? * diff --git a/lib/mu-contacts-cache.cc b/lib/mu-contacts-cache.cc index 3525d2d0..c0831877 100644 --- a/lib/mu-contacts-cache.cc +++ b/lib/mu-contacts-cache.cc @@ -162,8 +162,11 @@ ContactsCache::Private::serialize() const ci.message_date, SepaChar2, ci.frequency); } - config_db_.set(s); - dirty_ = 0; + if (const auto res{config_db_.set(s)}; res) + dirty_ = 0; + else + mu_error("failed to serialize contacts: {}", + res.error()); } ContactsCache::ContactsCache(Config& config_db) diff --git a/lib/mu-indexer.cc b/lib/mu-indexer.cc index 24d2e478..eff0ee42 100644 --- a/lib/mu-indexer.cc +++ b/lib/mu-indexer.cc @@ -427,7 +427,7 @@ Indexer::Private::scan_worker() if (!aborted && conf_.scan) { // Store started time, not ending time, so that next time we run we know to scan // anything that appeared during our scan. - store_.config().set(started_.value()); + store_.config().checked_set(started_.value()); } completed_ = ::time({}); diff --git a/lib/mu-labels-cache.cc b/lib/mu-labels-cache.cc index 0e4fa835..6446e961 100644 --- a/lib/mu-labels-cache.cc +++ b/lib/mu-labels-cache.cc @@ -30,9 +30,12 @@ Mu::LabelsCache::serialize() const for (const auto&[label, n]: label_map_) s += mu_format("{}{}{}\n", label, SepaChar2, n); - config_.set(s); - mu_debug("labels: serialized {} change(s)", dirty_); - dirty_ = 0; + if (const auto res{config_.set(s)}; res) { + mu_debug("labels: serialized {} change(s)", dirty_); + dirty_ = 0; + } else + mu_warning("labels: fail to store labels: {}", + res.error()); } Mu::LabelsCache::Map diff --git a/lib/mu-scanner.cc b/lib/mu-scanner.cc index 2d646dcc..24fc3db0 100644 --- a/lib/mu-scanner.cc +++ b/lib/mu-scanner.cc @@ -429,7 +429,10 @@ main (int argc, char *argv[]) Scanner scanner{argv[1], on_path, Mode::MaildirsOnly}; - scanner.start(); + if (const auto res{scanner.start()}; !res) { + mu_printerrln("scan failed: {}", res.error()); + return 1; + } return 0; } diff --git a/lib/mu-server.cc b/lib/mu-server.cc index c60fea00..1652811e 100644 --- a/lib/mu-server.cc +++ b/lib/mu-server.cc @@ -141,7 +141,9 @@ struct Server::Private { if (have_indexer_) indexer().stop(); if (!tmp_dir_.empty()) - remove_directory(tmp_dir_); + if (const auto res{remove_directory(tmp_dir_)}; !res) + mu_warning("failed to remove '{}': {}", + tmp_dir_, res.error()); } // // construction helpers @@ -552,13 +554,15 @@ Server::Private::contacts_handler(const Command& cmd) auto n{0}; auto&& out{make_output_stream()}; mu_print(out, "("); - store().contacts_cache().for_each([&](const Contact& ci) { + const auto res = store().contacts_cache().for_each([&](const Contact& ci) { if (!match_contact(ci)) return true; // continue mu_println(out.out(), "{}", quote(ci.display_name())); ++n; return maxnum == 0 || n < maxnum; }); + if (!res) + mu_warning("contacts: {}", res.error()); mu_print(out, ")"); output(mu_format("(:contacts {}\n:tstamp \"{}\")", out.to_string(), g_get_monotonic_time())); @@ -896,7 +900,8 @@ Server::Private::index_handler(const Command& cmd) throw Error{Error::Code::Xapian, "indexer is already running"}; do_index(conf); - store().serialize(); + if (const auto res{store().serialize()}; !res) + mu_warning("failed to serialize store: {}", res.error()); } void diff --git a/lib/mu-store.cc b/lib/mu-store.cc index cec5b089..855abe68 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -103,8 +103,10 @@ struct Store::Private { if (conf) config.import_configurable(*conf); - config.set(remove_slash(root_maildir)); - config.set(ExpectedSchemaVersion); + if (const auto res{config.set(remove_slash(root_maildir))}; !res) + throw res.error(); + if (const auto res{config.set(ExpectedSchemaVersion)}; !res) + throw res.error(); return config; } @@ -420,8 +422,11 @@ Store::remove_message(const std::string& path) for (auto&& label : doc.string_vec_value(Field::Id::Labels)) priv_->labels_cache_.decrease(label); - xapian_db().delete_document(term); - mu_debug("deleted message @ {} from store", path); + if (const auto res{xapian_db().delete_document(term)}; res) + mu_debug("deleted message @ {} from store", path); + else + mu_warning("failed to delete message @ {}: {}", + path, res.error()); return true; } @@ -432,7 +437,8 @@ Store::remove_messages(const std::vector& ids) { std::lock_guard guard{priv_->lock_}; - xapian_db().request_transaction(); + if (const auto res{xapian_db().request_transaction()}; !res) + mu_warning("failed to request transaction: {}", res.error()); for (auto&& id : ids) priv_->remove_message_by_id_unlocked(id); @@ -464,7 +470,10 @@ Store::remove_messages_by_term(std::span terms, std::vector qvec; std::vector ids_to_remove; - xapian_db().request_transaction(); + if (const auto res{xapian_db().request_transaction()}; !res) { + mu_error("failed to request transaction: {}", res.error()); + return 0; + } while (!terms.empty()) { auto chunk = terms.subspan(0, std::min(terms.size(), 1024)); @@ -810,7 +819,11 @@ Store::maildirs() const }; Scanner scanner{root_maildir(), handler, Scanner::Mode::MaildirsOnly}; - scanner.start(); + if (const auto res{scanner.start()}; !res) { + mu_warning("failed to start scanner: {}", res.error()); + return {}; + } + std::ranges::sort(mdirs); return mdirs; diff --git a/lib/utils/mu-error.hh b/lib/utils/mu-error.hh index 23fb3c84..e644069a 100644 --- a/lib/utils/mu-error.hh +++ b/lib/utils/mu-error.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2019-2025 Dirk-Jan C. Binnema +** Copyright (C) 2019-2026 Dirk-Jan C. Binnema ** ** 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 @@ -107,7 +107,7 @@ struct Error final : public std::exception { * * @return */ - virtual const char* what() const noexcept override { return what_.c_str(); } + const char* what() const noexcept override { return what_.c_str(); } /** * Get the error-code for this error @@ -183,7 +183,10 @@ private: std::string hint_; }; -inline auto +/** + * Make Error formattable with libfmt + */ +inline std::string format_as(const Error& err) { return mu_format("<{} ({}:{})>", err.what(), diff --git a/mu/mu-cmd-init.cc b/mu/mu-cmd-init.cc index b36f3235..d0b48809 100644 --- a/mu/mu-cmd-init.cc +++ b/mu/mu-cmd-init.cc @@ -53,15 +53,15 @@ Mu::mu_cmd_init(const Options& opts) Config conf{mdb}; if (opts.init.max_msg_size) - conf.set(*opts.init.max_msg_size); + conf.checked_set(*opts.init.max_msg_size); if (opts.init.batch_size && *opts.init.batch_size != 0) - conf.set(*opts.init.batch_size); + conf.checked_set(*opts.init.batch_size); if (!opts.init.personal_addresses.empty()) - conf.set(opts.init.personal_addresses); + conf.checked_set(opts.init.personal_addresses); if (!opts.init.ignored_addresses.empty()) - conf.set(opts.init.ignored_addresses); + conf.checked_set(opts.init.ignored_addresses); if (opts.init.support_ngrams) - conf.set(true); + conf.checked_set(true); return Store::make_new(opts.runtime_path(RuntimePath::XapianDb), opts.init.maildir, conf); @@ -77,7 +77,9 @@ Mu::mu_cmd_init(const Options& opts) // mildly hacky Options opts_copy{opts}; opts_copy.info.topic = "store"; - mu_cmd_info(*store, opts_copy); + if (const auto res{mu_cmd_info(*store, opts_copy)}; !res) + mu_warning("failed to show store info: {}", + res.error()); mu_println("Database is empty. You can use 'mu index' to fill it."); } diff --git a/mu/mu-cmd-labels.cc b/mu/mu-cmd-labels.cc index 251ab9a6..531d83f4 100644 --- a/mu/mu-cmd-labels.cc +++ b/mu/mu-cmd-labels.cc @@ -67,7 +67,9 @@ label_update(Mu::Store& store, const Options& opts) mu_println("labels: apply {} to {}", labelstr, msg->path()); if (!opts.labels.dry_run) { - store.update_labels(*msg, deltas); + if (const auto res{store.update_labels(*msg, deltas)}; !res) + mu_warning("failed to update labels for {}: {}", + msg->path(), res.error()); } ++n; } @@ -101,7 +103,9 @@ label_clear(Mu::Store& store, const Options& opts) mu_println("labels: clear all from {}", msg->path()); if (!opts.labels.dry_run) { - store.clear_labels(*msg); + if (const auto res{store.clear_labels(*msg)}; !res) + mu_warning("failed to clear labels for {}: {}", + msg->path(), res.error()); } ++n; } diff --git a/scm/mu-scm-store.cc b/scm/mu-scm-store.cc index 3f5a2540..3c74c781 100644 --- a/scm/mu-scm-store.cc +++ b/scm/mu-scm-store.cc @@ -104,11 +104,13 @@ subr_cc_store_cfind(SCM store_scm, SCM pattern_scm, SCM personal_scm, SCM after_ // 0 means "unlimited" const size_t maxnum = from_scm_with_default(max_results_scm, 0U, func, 5); - to_store(store_scm, func, 1).contacts_cache().for_each( + const auto res = to_store(store_scm, func, 1).contacts_cache().for_each( [&](const auto& contact)->bool { contacts = scm_cons(to_scm(contact), contacts); return true; }, pattern, personal, after, maxnum); + if (!res) + throw ScmError{ScmError::Id::WrongArg, func, 2, pattern_scm, "pattern"}; return scm_reverse_x(contacts, SCM_EOL); } catch (const ScmError& scm_err) {