From c28fde9155e5523e93d7f6eff8b6e3ed7b3f0bb5 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Fri, 30 Jun 2023 23:16:41 +0300 Subject: [PATCH] lib: create mu-xapian-db, mu-config XapianDb is a fairly thing wrapper around Xapian, which handles locking, exception handling and some tracking. On top of that, Config add a configuration database for type / introspectable configuration info. --- lib/mu-config.cc | 88 ++++++++ lib/mu-config.hh | 293 +++++++++++++++++++++++++ lib/mu-xapian-db.cc | 136 ++++++++++++ lib/mu-xapian-db.hh | 404 +++++++++++++++++++++++++++++++++++ lib/utils/mu-xapian-utils.hh | 85 -------- 5 files changed, 921 insertions(+), 85 deletions(-) create mode 100644 lib/mu-config.cc create mode 100644 lib/mu-config.hh create mode 100644 lib/mu-xapian-db.cc create mode 100644 lib/mu-xapian-db.hh delete mode 100644 lib/utils/mu-xapian-utils.hh diff --git a/lib/mu-config.cc b/lib/mu-config.cc new file mode 100644 index 00000000..61ed54fa --- /dev/null +++ b/lib/mu-config.cc @@ -0,0 +1,88 @@ +/* +** Copyright (C) 2023 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 +** 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-config.hh" + +using namespace Mu; + +constexpr /*static*/ bool +validate_props() +{ + size_t id{0}; + for (auto&& prop: Config::properties) { + + // ids must match + if (static_cast(prop.id) != id) + return false; + ++id; + } + + return true; +} + +#ifdef BUILD_TESTS +#define static_assert g_assert_true +#endif /*BUILD_TESTS*/ + +[[maybe_unused]] +static void +test_props() +{ + static_assert(validate_props()); +} + +#ifdef BUILD_TESTS +/* + * Tests. + * + */ + +#include "utils/mu-test-utils.hh" + +static void +test_basic() +{ + MemDb db; + Config conf_db{db}; + + using Id = Config::Id; + + { + const auto rmd = conf_db.get(); + g_assert_true(rmd.empty()); + } + + { + conf_db.set("/home/djcb/Maildir"); + const auto rmd = conf_db.get(); + assert_equal(rmd, "/home/djcb/Maildir"); + } +} + +int +main(int argc, char* argv[]) +{ + mu_test_init(&argc, &argv); + + g_test_add_func("/config-db/props", test_props); + g_test_add_func("/config-db/basic", test_basic); + + return g_test_run(); +} +#endif /*BUILD_TESTS*/ diff --git a/lib/mu-config.hh b/lib/mu-config.hh new file mode 100644 index 00000000..abeb5272 --- /dev/null +++ b/lib/mu-config.hh @@ -0,0 +1,293 @@ +/* +** Copyright (C) 2023 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 +** 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_CONFIG_HH__ +#define MU_CONFIG_HH__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mu-xapian-db.hh" + +#include +#include +#include + +namespace Mu { + +struct Property { + enum struct Id { + BatchSize, /**< Xapian batch-size */ + Contacts, /**< Cache of contact information */ + Created, /** Time of creation */ + LastChange, /**< Time of last change */ + LastIndex, /**< Time of last index */ + MaxMessageSize, /**< Maximum message size (in bytes) */ + PersonalAddresses, /**< List of personal e-mail addresses */ + RootMaildir, /**< Root maildir path */ + SchemaVersion, /**< Xapian DB schema version */ + /* */ + _count_ /* Number of Ids */ + }; + + static constexpr size_t id_size = static_cast(Id::_count_); + /**< Number of Property::Ids */ + + enum struct Flags { + None = 0, /**< Nothing in particular */ + ReadOnly = 1 << 0, /**< Property is read-only for external use + * (but can change from within the store) */ + Configurable = 1 << 1, /**< A user-configurable parameter; name + * starts with 'conf-' */ + Internal = 1 << 2, /**< Mu-internal field */ + }; + enum struct Type { + Number, /**< Some number */ + Timestamp, /**< Timestamp number */ + Path, /**< Path string */ + String, /**< A string */ + StringList, /**< A list of strings */ + }; + + using Value = std::variant >; + + Id id; + Type type; + Flags flags; + std::string_view name; + std::string_view default_val; + std::string_view description; +}; + +MU_ENABLE_BITOPS(Property::Flags); + +class Config { +public: + using Id = Property::Id; + using Type = Property::Type; + using Flags = Property::Flags; + using Value = Property::Value; + + static constexpr std::array + properties = {{ + { + Id::BatchSize, + Type::Number, + Flags::Configurable, + "batch-size", + "250000", + "Version of the Xapian database schema" + }, + { + Id::Contacts, + Type::String, + Flags::Internal, + "contacts", + {}, + "Serialized contact information" + }, + { + Id::Created, + Type::Timestamp, + Flags::ReadOnly, + MetadataIface::created_key, + {}, + "Database creation time" + }, + { + Id::LastChange, + Type::Timestamp, + Flags::ReadOnly, + MetadataIface::last_change_key, + {}, + "Time when last change occurred" + }, + { + Id::LastIndex, + Type::Timestamp, + Flags::ReadOnly, + "last-index", + {}, + "Time when last indexing operation was completed" + }, + { + Id::MaxMessageSize, + Type::Number, + Flags::Configurable, + "max-message-size", + "100000000", // default max: 100M bytes + "Maximum message size (in bytes); bigger messages are skipped" + }, + { + Id::PersonalAddresses, + Type::StringList, + Flags::Configurable, + "personal-addresses", + "", + "List of personal e-mail addresses, literal or /regexp/" + }, + { + Id::RootMaildir, + Type::Path, + Flags::ReadOnly, + "root-maildir", + {}, + "Absolute path of the top of the Maildir tree" + }, + { + Id::SchemaVersion, + Type::Number, + Flags::ReadOnly, + "schema-version", + {}, + "Version of the Xapian database schema" + }, + }}; + + /** + * Construct a new Config object. + * + * @param db The config-store (database); must stay valid for the + * lifetime of this config. + */ + Config(MetadataIface& cstore): cstore_{cstore}{} + + /** + * Get the property by its id + * + * @param id a property id (!= Id::_count_) + * + * @return the property + */ + template + constexpr static const Property& property() { + return properties[static_cast(ID)]; + } + + /** + * Get a Property by its name. + * + * @param name The name + * + * @return the property or Nothing if not found + */ + static Option property(const std::string& name) { + const auto pname{std::string_view(name.data(), name.size())}; + for(auto&& prop: properties) + if (prop.name == pname) + return prop; + return Nothing; + } + + /** + * Get the property value of the correct type + * + * @param prop_id a property id + * + * @return the value or Nothing + */ + template + auto get() const { + constexpr auto&& prop{property()}; + const auto str = std::invoke([&]()->std::string { + const auto str = cstore_.metadata(std::string{prop.name}); + return str.empty() ? std::string{prop.default_val} : str; + }); + if constexpr (prop.type == Type::Number) + return static_cast(str.empty() ? 0 : std::atoll(str.c_str())); + else if constexpr (prop.type == Type::Timestamp) + return static_cast(str.empty() ? 0 : std::atoll(str.c_str())); + else if constexpr (prop.type == Type::Path || prop.type == Type::String) + return str; + else if constexpr (prop.type == Type::StringList) + return split(str, SepaChar1); + + throw std::logic_error("invalid prop " + std::string{prop.name}); + } + + /** + * Set a new value for some property + * + * @param prop_id property-id + * @param val the new value (of the correct type) + * + * @return Ok() or some error + */ + template + Result set(const T& val) { + constexpr auto&& prop{property()}; + if (read_only()) + return Err(Error::Code::AccessDenied, + "cannot write to read-only db"); + + const auto strval = std::invoke([&]{ + if constexpr (prop.type == Type::Number || prop.type == Type::Timestamp) + return format("%" PRIi64, static_cast(val)); + else if constexpr (prop.type == Type::Path || prop.type == Type::String) + return std::string{val}; + else if constexpr (prop.type == Type::StringList) + return join(val, SepaChar1); + + throw std::logic_error("invalid prop " + std::string{prop.name}); + }); + + cstore_.set_metadata(std::string{prop.name}, strval); + return Ok(); + } + + /** + * Is this a read-only Config? + * + * + * @return true or false + */ + bool read_only() const { return cstore_.read_only();}; + + /** + * Import configurable settings to some other MetadataIface + * + * @param target some other metadata interface + */ + void import_configurable(const Config& src) const { + for (auto&& prop: properties) { + if (any_of(prop.flags & Flags::Configurable)) { + const auto&& key{std::string{prop.name}}; + if (auto&& val{src.cstore_.metadata(key)}; !val.empty()) + cstore_.set_metadata(key, std::string{val}); + } + } + } + +private: + static constexpr uint8_t SepaChar1 = 0xfe; + MetadataIface& cstore_; +}; + + +} // namespace Mu + +#endif /* MU_CONFIG_DB_HH__ */ diff --git a/lib/mu-xapian-db.cc b/lib/mu-xapian-db.cc new file mode 100644 index 00000000..68c25920 --- /dev/null +++ b/lib/mu-xapian-db.cc @@ -0,0 +1,136 @@ +/* +** Copyright (C) 2023 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 +** 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-xapian-db.hh" +#include "utils/mu-utils.hh" +#include + +#include + +using namespace Mu; + +struct XapianDb::Private { + using DbType = std::variant; + + Private(Xapian::Database&& db, const std::string& path): + db_{std::move(db)}, path_{path} {} + Private(Xapian::WritableDatabase&& wdb, const std::string& path): + db_{std::move(wdb)}, path_{path} {} + + DbType db_; + const std::string path_; + mutable std::mutex lock_; +}; + + +XapianDb::XapianDb(Xapian::Database&& db, const std::string& path): + priv_{std::make_unique(std::move(db), path)} +{} +XapianDb::XapianDb(Xapian::WritableDatabase&& wdb, const std::string& path): + priv_{std::make_unique(std::move(wdb), path)} +{} + +XapianDb::XapianDb(XapianDb&& rhs) = default; +XapianDb::~XapianDb() = default; + + +const Xapian::Database& +XapianDb::db() const +{ + if (std::holds_alternative(priv_->db_)) + return std::get(priv_->db_); + else + return std::get(priv_->db_); +} + +Xapian::WritableDatabase& +XapianDb::wdb() +{ + if (read_only()) + throw std::runtime_error("database is read-only"); + return std::get(priv_->db_); +} + +bool +XapianDb::read_only() const +{ + return !std::holds_alternative(priv_->db_); +} + +const std::string& +XapianDb::path() const +{ + return priv_->path_; +} + +std::mutex& +XapianDb::lock() const +{ + return priv_->lock_; +} + +void +XapianDb::set_timestamp(const std::string_view key) +{ + wdb().set_metadata(std::string{key}, + format("%" PRIi64, static_cast(::time({})))); +} + +Result +XapianDb::make(const std::string& db_path, Flavor flavor) noexcept try { + + if (flavor != Flavor::ReadOnly) { + /* we do our own flushing, set Xapian's internal one as + * the backstop*/ + g_setenv("XAPIAN_FLUSH_THRESHOLD", "500000", 1); + /* create path if needed */ + if (g_mkdir_with_parents(db_path.c_str(), 0700) != 0) + return Err(Error::Code::File, "failed to create database dir %s: %s", + db_path.c_str(), ::strerror(errno)); + } + + switch (flavor) { + + case Flavor::ReadOnly: + return Ok(XapianDb(Xapian::Database(db_path), db_path)); + case Flavor::Open: + return Ok(XapianDb(Xapian::WritableDatabase( + db_path, Xapian::DB_OPEN), db_path)); + case Flavor::CreateOverwrite: { + auto&& xdb{XapianDb(Xapian::WritableDatabase( + db_path, + Xapian::DB_CREATE_OR_OVERWRITE), db_path)}; + xdb.set_timestamp(MetadataIface::created_key); + return Ok(std::move(xdb)); + } + default: + return Err(Error::Code::Internal, "invalid xapian options"); + } + +} catch (const Xapian::DatabaseLockError& xde) { + return Err(Error::Code::StoreLock, "%s", xde.get_msg().c_str()); +} catch (const Xapian::DatabaseError& xde) { + return Err(Error::Code::Store, "%s", xde.get_msg().c_str()); +} catch (const Mu::Error& me) { + return Err(me); +} catch (...) { + return Err(Error::Code::Internal, + "something went wrong when opening store @ %s", db_path.c_str()); +} diff --git a/lib/mu-xapian-db.hh b/lib/mu-xapian-db.hh new file mode 100644 index 00000000..5a691c47 --- /dev/null +++ b/lib/mu-xapian-db.hh @@ -0,0 +1,404 @@ +/* +** Copyright (C) 2023 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 +** 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_XAPIAN_DB_HH__ +#define MU_XAPIAN_DB_HH__ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace Mu { + +// LCOV_EXCL_START + +// avoid exception-handling boilerplate. +template void +xapian_try(Func&& func) noexcept +try { + func(); +} catch (const Xapian::Error& xerr) { + g_critical("%s: xapian error '%s'", __func__, xerr.get_msg().c_str()); +} catch (const std::runtime_error& re) { + g_critical("%s: runtime error: %s", __func__, re.what()); +} catch (const std::exception& e) { + g_critical("%s: caught std::exception: %s", __func__, e.what()); +} catch (...) { + g_critical("%s: caught exception", __func__); +} + +template > auto +xapian_try(Func&& func, Default&& def) noexcept -> std::decay_t +try { + return func(); +} catch (const Xapian::DocNotFoundError& xerr) { + return static_cast(def); +} catch (const Xapian::Error& xerr) { + g_warning("%s: xapian error '%s'", __func__, xerr.get_msg().c_str()); + return static_cast(def); +} catch (const std::runtime_error& re) { + g_critical("%s: runtime error: %s", __func__, re.what()); + return static_cast(def); +} catch (const std::exception& e) { + g_critical("%s: caught std::exception: %s", __func__, e.what()); + return static_cast(def); +} catch (...) { + g_critical("%s: caught exception", __func__); + return static_cast(def); +} + + +template auto +xapian_try_result(Func&& func) noexcept -> std::decay_t +try { + return func(); +} catch (const Xapian::Error& xerr) { + return Err(Error::Code::Xapian, "%s", xerr.get_error_string()); +} catch (const std::runtime_error& re) { + return Err(Error::Code::Internal, "runtime error: %s", re.what()); +} catch (const std::exception& e) { + return Err(Error::Code::Internal, "caught std::exception: %s", e.what()); +} catch (...) { + return Err(Error::Code::Internal, "caught exception"); +} + +// LCOV_EXCL_STOP + +/// abstract base +struct MetadataIface { + virtual ~MetadataIface(){} + virtual void set_metadata(const std::string& name, const std::string& val) = 0; + virtual std::string metadata(const std::string& name) const = 0; + virtual bool read_only() const = 0; + + using each_func = std::function; + virtual void for_each(each_func&& func) const =0; + + /* + * These are special: handled on the Xapian db level + * rather than Config + */ + static inline constexpr std::string_view created_key = "created"; + static inline constexpr std::string_view last_change_key = "last-change"; +}; + + +/// In-memory db +struct MemDb: public MetadataIface { + /** + * Set some metadata + * + * @param name key name + * @param val value + */ + void set_metadata(const std::string& name, const std::string& val) override { + map_.erase(name); + map_[name] = val; + } + + /** + * Get metadata for given key, empty if not found + * + * @param name key name + * + * @return string + */ + std::string metadata(const std::string& name) const override { + if (auto&& it = map_.find(name); it != map_.end()) + return it->second; + else + return {}; + } + + /** + * Is this db read-only? + * + * @return true or false + */ + bool read_only() const override { return false; } + + + /** + * Invoke function for each key/value pair. Do not call + * @this from each_func(). + * + * @param func a function + */ + void for_each(MetadataIface::each_func&& func) const override { + for (const auto& [key, value] : map_) + func(key, value); + } + +private: + std::unordered_map map_; +}; + +/** + * Fairly thin wrapper around Xapian::Database and Xapian::WritableDatabase + * with just the things we need + locking + exception handling + */ +class XapianDb: public MetadataIface { +#define DB_LOCKED std::unique_lock l{lock()} +public: + /** + * Type of database to create. + * + */ + enum struct Flavor { + ReadOnly, /**< Read-only database */ + Open, /**< Open existing read-write */ + CreateOverwrite, /**< Create new or overwrite existing */ + }; + + /** + * Make a new Xapian Database wrapper object + * + * @param db_path path to the database + * @param flavor type of database to created + * + * @return a result; either a XapianDb or an Error. + */ + static Result make(const std::string& db_path, Flavor flavor) noexcept; + + /** + * Is the database read-only? + * + * @return true or false + */ + bool read_only() const override; + + /** + * Path to the database; empty for in-memory database + * + * @return path to database + */ + const std::string& path() const; + + /** + * Get the number of documents (messages) in the database + * + * @return number + */ + size_t size() const noexcept { + return xapian_try([this]{ + DB_LOCKED; return db().get_doccount(); }, 0); + } + + /** + * Is the the base empty? + * + * @return true or false + */ + size_t empty() const noexcept { return size() == 0; } + + /** + * Get a database enquire object for queries. + * + * @return an enquire object + */ + Xapian::Enquire enquire() const { + DB_LOCKED; return Xapian::Enquire(db()); + } + + /** + * Get a document from the database if there is one + * + * @param id id of the document + * + * @return the document or an error + */ + Result document(Xapian::docid id) const { + return xapian_try_result([&]{ + DB_LOCKED; return Ok(db().get_document(id)); }); + } + + /** + * Get metadata for the given key + * + * @param key key (non-empty) + * + * @return the value or empty + */ + std::string metadata(const std::string& key) const override { + return xapian_try([&]{ + DB_LOCKED; return db().get_metadata(key);}, ""); + } + + /** + * Set metadata for the given key + * + * @param key key (non-empty) + * @param val new value for key + */ + void set_metadata(const std::string& key, const std::string& val) override { + xapian_try([&] { DB_LOCKED; wdb().set_metadata(key, val); }); + } + + /** + * Invoke function for each key/value pair. This is called with the lock + * held, so do not call functions on @this is each_func(). + * + * @param each_func a function + */ + //using each_func = MetadataIface::each_func; + void for_each(MetadataIface::each_func&& func) const override { + xapian_try([&]{ + DB_LOCKED; + for (auto&& it = db().metadata_keys_begin(); + it != db().metadata_keys_end(); ++it) + func(*it, db().get_metadata(*it)); + }); + } + + + /** + * Does the given term exist in the database? + * + * @param term some term + * + * @return true or false + */ + bool term_exists(const std::string& term) const { + return xapian_try([&]{ + DB_LOCKED; return db().term_exists(term);}, false); + } + + /** + * Replace document in database + * + * @param term unique term + * @param id docid + * @param doc replacement document + * + * @return new docid or nothing. + */ + Xapian::docid replace_document(const std::string& term, const Xapian::Document& doc) { + return xapian_try([&]{ + DB_LOCKED; + auto&& id= wdb().replace_document(term, doc); + set_timestamp(MetadataIface::last_change_key); + return id; + }, 0); + } + void replace_document(const Xapian::docid id, const Xapian::Document& doc) { + xapian_try([&]{ + DB_LOCKED; + wdb().replace_document(id, doc); + set_timestamp(MetadataIface::last_change_key); + }); + } + + /** + * Delete document(s) for the given term or id + * + * @param term a term + */ + void delete_document(const std::string& term) { + xapian_try([&]{ + DB_LOCKED; + wdb().delete_document(term); + set_timestamp(MetadataIface::last_change_key); + }); + } + + void delete_document(Xapian::docid id) { + xapian_try([&]{ + DB_LOCKED; + wdb().delete_document(id); + set_timestamp(MetadataIface::last_change_key); + }); + } + + template + size_t all_terms(const std::string& prefix, Func&& func) const { + DB_LOCKED; + size_t n{}; + for (auto it = db().allterms_begin(prefix); it != db().allterms_end(prefix); ++it) { + if (!func(*it)) + break; + ++n; + } + return n; + } + + /* + * transactions + */ + + /** + * Start a transaction + * + * @param flushed + */ + void begin_transaction(bool flushed=true) { + xapian_try([&]{ DB_LOCKED; return wdb().begin_transaction(flushed);}); + } + /** + * Commit a transaction + */ + void commit_transaction() { + xapian_try([&]{ DB_LOCKED; return wdb().commit_transaction();}); + } + + /** + * Get a reference to the underlying database + * + * @return db database reference + */ + const Xapian::Database& db() const; + /** + * Get a reference to the underlying writable database. It is + * an error to call this on a read-only database. + * + * @return db writable database reference + */ + Xapian::WritableDatabase& wdb(); + + /** + * DTOR + */ + ~XapianDb(); + + /** + * Move CTOR + * + * @param rhs + */ + XapianDb(XapianDb&& rhs); + +private: + XapianDb(Xapian::Database&& db, const std::string& path); + XapianDb(Xapian::WritableDatabase&& wdb, const std::string& path); + void set_timestamp(const std::string_view key); + std::mutex& lock() const; + + struct Private; + std::unique_ptr priv_; +}; + +} // namespace Mu + +#endif /* MU_XAPIAN_DB_HH__ */ diff --git a/lib/utils/mu-xapian-utils.hh b/lib/utils/mu-xapian-utils.hh deleted file mode 100644 index 5712d050..00000000 --- a/lib/utils/mu-xapian-utils.hh +++ /dev/null @@ -1,85 +0,0 @@ -/* -** Copyright (C) 2021-2023 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 -** 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_XAPIAN_UTILS_HH__ -#define MU_XAPIAN_UTILS_HH__ - -#include -#include -#include "mu-result.hh" - -namespace Mu { - -// LCOV_EXCL_START - -// avoid exception-handling boilerplate. -template void -xapian_try(Func&& func) noexcept -try { - func(); -} catch (const Xapian::Error& xerr) { - g_critical("%s: xapian error '%s'", __func__, xerr.get_msg().c_str()); -} catch (const std::runtime_error& re) { - g_critical("%s: runtime error: %s", __func__, re.what()); -} catch (const std::exception& e) { - g_critical("%s: caught std::exception: %s", __func__, e.what()); -} catch (...) { - g_critical("%s: caught exception", __func__); -} - -template > auto -xapian_try(Func&& func, Default&& def) noexcept -> std::decay_t -try { - return func(); -} catch (const Xapian::DocNotFoundError& xerr) { - return static_cast(def); -} catch (const Xapian::Error& xerr) { - g_warning("%s: xapian error '%s'", __func__, xerr.get_msg().c_str()); - return static_cast(def); -} catch (const std::runtime_error& re) { - g_critical("%s: runtime error: %s", __func__, re.what()); - return static_cast(def); -} catch (const std::exception& e) { - g_critical("%s: caught std::exception: %s", __func__, e.what()); - return static_cast(def); -} catch (...) { - g_critical("%s: caught exception", __func__); - return static_cast(def); -} - - -template auto -xapian_try_result(Func&& func) noexcept -> std::decay_t -try { - return func(); -} catch (const Xapian::Error& xerr) { - return Err(Error::Code::Xapian, "%s", xerr.get_error_string()); -} catch (const std::runtime_error& re) { - return Err(Error::Code::Internal, "runtime error: %s", re.what()); -} catch (const std::exception& e) { - return Err(Error::Code::Internal, "caught std::exception: %s", e.what()); -} catch (...) { - return Err(Error::Code::Internal, "caught exception"); -} - -// LCOV_EXCL_STOP - -} // namespace Mu - -#endif /* MU_ XAPIAN_UTILS_HH__ */