diff --git a/lib/mu-index.c b/lib/mu-index.c index 670588f0..3f16434e 100644 --- a/lib/mu-index.c +++ b/lib/mu-index.c @@ -343,7 +343,7 @@ mu_index_run (MuIndex *index, gboolean reindex, gboolean lazycheck, g_return_val_if_fail (index && index->_store, MU_ERROR); g_return_val_if_fail (msg_cb, MU_ERROR); - path = mu_store_maildir (index->_store); + path = mu_store_root_maildir (index->_store); if (!check_path (path)) return MU_ERROR; @@ -401,7 +401,7 @@ mu_index_stats (MuIndex *index, g_return_val_if_fail (index, MU_ERROR); g_return_val_if_fail (cb_msg, MU_ERROR); - path = mu_store_maildir (index->_store); + path = mu_store_root_maildir (index->_store); if (!check_path (path)) return MU_ERROR; diff --git a/lib/mu-store.cc b/lib/mu-store.cc index 0f6bd804..b12ab042 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2019 Dirk-Jan C. Binnema +** Copyright (C) 2020 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 @@ -43,8 +43,6 @@ constexpr auto CreatedKey = "created"; constexpr auto ExpectedSchemaVersion = MU_STORE_SCHEMA_VERSION; -using Addresses = Store::Addresses; - extern "C" { static unsigned add_or_update_msg (MuStore *store, unsigned docid, MuMsg *msg, GError **err); } @@ -99,8 +97,8 @@ struct Store::Private { Private (const std::string& path, bool readonly): db_path_{path}, db_{readonly? - std::make_shared(db_path_) : - std::make_shared(db_path_, Xapian::DB_OPEN)}, + std::make_shared(db_path_) : + std::make_shared(db_path_, Xapian::DB_OPEN)}, root_maildir_{db()->get_metadata(RootMaildirKey)}, created_{atoll(db()->get_metadata(CreatedKey).c_str())}, schema_version_{db()->get_metadata(SchemaVersionKey)}, @@ -108,17 +106,29 @@ struct Store::Private { contacts_{db()->get_metadata(ContactsKey)} { } - Private (const std::string& path, const std::string& root_maildir): + Private (const std::string& path, const std::string& root_maildir, + const StringVec& personal_addresses): db_path_{path}, db_{std::make_shared( db_path_, Xapian::DB_CREATE_OR_OVERWRITE)}, root_maildir_{root_maildir}, created_{time({})}, - schema_version_{MU_STORE_SCHEMA_VERSION} { + schema_version_{MU_STORE_SCHEMA_VERSION}, + personal_addresses_{personal_addresses} { writable_db()->set_metadata(SchemaVersionKey, schema_version_); writable_db()->set_metadata(RootMaildirKey, root_maildir_); writable_db()->set_metadata(CreatedKey, Mu::format("%" PRId64, (int64_t)created_)); + + std::string addrs; + for (const auto& addr : personal_addresses_) { // _very_ minimal check. + if (addr.find(",") != std::string::npos) + throw Mu::Error(Error::Code::InvalidArgument, + "e-mail address '%s' contains comma", addr.c_str()); + addrs += (addrs.empty() ? "": ",") + addr; + } + writable_db()->set_metadata (PersonalAddressesKey, addrs); + } ~Private() { @@ -144,25 +154,6 @@ struct Store::Private { return w_db; } - void set_personal_addresses (const Addresses& addresses) { - - std::string all_addresses; - personal_addresses_.clear(); - - for (const auto& addr : addresses) { - // very basic check; just ensure there's no ',' in the address. - // we don't insist on full RFC5322 - if (addr.find(",") != std::string::npos) - throw Mu::Error(Error::Code::InvalidArgument, - "e-mail address '%s' contains comma", addr.c_str()); - if (!all_addresses.empty()) - all_addresses += ','; - all_addresses += addr; - personal_addresses_.emplace_back(addr); - } - writable_db()->set_metadata (PersonalAddressesKey, all_addresses); - } - void add_synonyms () { mu_flags_foreach ((MuFlagsForeachFunc)add_synonym_for_flag, writable_db().get()); @@ -181,10 +172,10 @@ struct Store::Private { const std::string root_maildir_; const time_t created_{}; const std::string schema_version_; - Addresses personal_addresses_; + const StringVec personal_addresses_; Contacts contacts_; - bool in_transaction_{}; + std::atomic in_transaction_{}; std::mutex lock_; mutable std::atomic ref_count_{1}; @@ -218,20 +209,24 @@ Store::Store (const std::string& path, bool readonly): if (ExpectedSchemaVersion == schema_version()) return; // All is good; nothing further to do - if (readonly || root_maildir().empty()) - throw Mu::Error(Error::Code::SchemaMismatch, "database needs reindexing"); + g_warning ("expected schema-version %s, but got %s", + ExpectedSchemaVersion, schema_version().c_str()); + + if (readonly) // this requires user-action. + throw Mu::Error(Error::Code::SchemaMismatch, + "database needs reindexing"); g_debug ("upgrading database"); const auto addresses{personal_addresses()}; const auto root_mdir{root_maildir()}; priv_.reset(); - priv_ = std::make_unique (path, root_mdir); - set_personal_addresses (addresses); + priv_ = std::make_unique (path, root_mdir, addresses); } -Store::Store (const std::string& path, const std::string& maildir): - priv_{std::make_unique(path, maildir)} +Store::Store (const std::string& path, const std::string& maildir, + const StringVec& personal_addresses): + priv_{std::make_unique(path, maildir, personal_addresses)} {} Store::~Store() = default; @@ -239,35 +234,24 @@ Store::~Store() = default; bool Store::read_only() const { - LOCKED; return !priv_->wdb(); } const std::string& Store::root_maildir () const { - LOCKED; return priv_->root_maildir_; } -void -Store::set_personal_addresses(const Store::Addresses& addresses) -{ - LOCKED; - priv_->set_personal_addresses (addresses); -} - -const Store::Addresses& +const StringVec& Store::personal_addresses(void) const { - LOCKED; return priv_->personal_addresses_; } const std::string& Store::database_path() const { - LOCKED; return priv_->db_path_; } @@ -281,8 +265,6 @@ Store::contacts() const std::size_t Store::size() const { - LOCKED; - return priv_->db()->get_doccount(); } @@ -296,16 +278,12 @@ Store::empty() const const std::string& Store::schema_version() const { - LOCKED; - return priv_->schema_version_; } time_t Store::created() const { - LOCKED; - return priv_->created_; } @@ -344,7 +322,7 @@ Store::add_message (const std::string& path) LOCKED; GError *gerr{}; - const auto maildir{ maildir_from_path(root_maildir(), path)}; + const auto maildir{maildir_from_path(root_maildir(), path)}; auto msg{mu_msg_new_from_file (path.c_str(), maildir.c_str(), &gerr)}; if (G_UNLIKELY(!msg)) throw Error{Error::Code::Message, "failed to create message: %s", @@ -503,7 +481,7 @@ mu_store_new_readable (const char* xpath, GError **err) } catch (const Mu::Error& me) { if (me.code() == Mu::Error::Code::SchemaMismatch) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_NEEDS_REINDEX, - "database @ %s needs (re)indexing", xpath); + "read-only database @ %s needs (re)indexing", xpath); else g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN, "error opening database @ %s: %s", xpath, me.what()); @@ -533,13 +511,10 @@ mu_store_new_writable (const char* xpath, GError **err) } catch (const Mu::Error& me) { if (me.code() == Mu::Error::Code::SchemaMismatch) g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_NEEDS_REINDEX, - "database @ %s needs (re)indexing", xpath); + "read/write database @ %s needs (re)indexing", xpath); else g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN, "error opening database @ %s: %s", xpath, me.what()); - // } catch (const Xapian::DatabaseNotFoundError& dbe) { // Xapian 1.4.10 - // g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_NEEDS_REINDEX, - // "database @ %s not found", xpath); } catch (const Xapian::DatabaseOpeningError& dbe) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_NEEDS_REINDEX, "failed to open database @ %s", xpath); @@ -555,16 +530,21 @@ mu_store_new_writable (const char* xpath, GError **err) } MuStore* -mu_store_new_create (const char* xpath, const char *maildir, GError **err) +mu_store_new_create (const char* xpath, const char *root_maildir, + const char **personal_addresses, GError **err) { g_return_val_if_fail (xpath, NULL); - g_return_val_if_fail (maildir, NULL); + g_return_val_if_fail (root_maildir, NULL); - g_debug ("create database at %s (maildir=%s)", xpath, maildir); + g_debug ("create database at %s (root-maildir=%s)", xpath, root_maildir); try { + StringVec addrs; + for (auto i = 0; personal_addresses && personal_addresses[i]; ++i) + addrs.emplace_back(personal_addresses[i]); + return reinterpret_cast( - new Store (xpath, std::string{maildir})); + new Store (xpath, std::string{root_maildir}, addrs)); } catch (const Xapian::DatabaseLockError& dle) { g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN_CANNOT_GET_WRITELOCK, @@ -648,7 +628,6 @@ mu_store_count (const MuStore *store, GError **err) (unsigned)-1); } - const char* mu_store_schema_version (const MuStore *store) { @@ -760,7 +739,7 @@ mu_store_database_path (const MuStore *store) const char* -mu_store_maildir (const MuStore *store) +mu_store_root_maildir (const MuStore *store) { g_return_val_if_fail (store, NULL); @@ -776,24 +755,6 @@ mu_store_created (const MuStore *store) return self(store)->created(); } - - -void -mu_store_set_personal_addresses (MuStore *store, const char **my_addresses) -{ - g_return_if_fail (store); - - if (!my_addresses) - return; - - Store::Addresses addrs; - for (auto i = 0; my_addresses[i]; ++i) - addrs.emplace_back(my_addresses[i]); - - mutable_self(store)->set_personal_addresses (addrs); -} - - char** mu_store_personal_addresses (const MuStore *store) { @@ -807,7 +768,6 @@ mu_store_personal_addresses (const MuStore *store) return addrs; } - void mu_store_flush (MuStore *store) try { @@ -1088,7 +1048,7 @@ struct MsgDoc { Store *_store; /* callback data, to determine whether this message is 'personal' */ gboolean _personal; - const Addresses *_my_addresses; + const StringVec *_my_addresses; }; @@ -1360,26 +1320,34 @@ mu_store_update_msg (MuStore *store, unsigned docid, MuMsg *msg, GError **err) } unsigned -mu_store_add_path (MuStore *store, const char *path, const char *maildir, - GError **err) -{ - MuMsg *msg; - unsigned docid; +mu_store_add_path (MuStore *store, const char *path, GError **err) try { - g_return_val_if_fail (store, FALSE); - g_return_val_if_fail (path, FALSE); + MuMsg *msg; + unsigned docid; - msg = mu_msg_new_from_file (path, maildir, err); - if (!msg) - return MU_STORE_INVALID_DOCID; + g_return_val_if_fail (store, FALSE); + g_return_val_if_fail (path, FALSE); - docid = add_or_update_msg (store, 0, msg, err); - mu_msg_unref (msg); + const auto maildir{maildir_from_path(self(store)->root_maildir(), path)}; + msg = mu_msg_new_from_file (path, maildir.c_str(), err); + if (!msg) + return MU_STORE_INVALID_DOCID; - return docid; + docid = add_or_update_msg (store, 0, msg, err); + mu_msg_unref (msg); + + return docid; + +} catch (const Mu::Error& me) { + g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_XAPIAN, + "%s", me.what()); + return MU_STORE_INVALID_DOCID; +} catch (...) { + g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR_INTERNAL, + "caught exception"); + return MU_STORE_INVALID_DOCID; } - XapianWritableDatabase* mu_store_get_writable_database (MuStore *store) { diff --git a/lib/mu-store.hh b/lib/mu-store.hh index 72a28257..aeadfd19 100644 --- a/lib/mu-store.hh +++ b/lib/mu-store.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2019 Dirk-Jan C. Binnema +** Copyright (C) 2020 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 @@ -30,6 +30,8 @@ #include #include +#include + namespace Mu { class Store { @@ -47,8 +49,11 @@ public: * * @param path path to the database * @param maildir maildir to use for this store + * @param personal_addressesaddresses that should be recognized as + * 'personal' for identifying personal messages. */ - Store (const std::string& path, const std::string& maildir); + Store (const std::string& path, const std::string& maildir, + const StringVec& personal_addresses); /** * DTOR @@ -92,23 +97,12 @@ public: */ std::time_t created() const; - using Addresses = std::vector; - /**< A vec of email addresses (of the type foo@example.com, RFC-5322)*/ - - /** - * Set addresses that should be recognized as 'personal' - * - * @param addresses - */ - void set_personal_addresses (const Addresses& addresses); - - /** * Get a vec with the personal addresses * * @return personal addresses */ - const Addresses& personal_addresses() const; + const StringVec& personal_addresses() const; /** * Get the Contacts object for this store @@ -263,13 +257,15 @@ MuStore* mu_store_new_writable (const char *xpath, GError **err) * * @param path the path to the database * @param path to the maildir + * @param personal_addressesaddresses that should be recognized as + * 'personal' for identifying personal messages. * @param err to receive error info or NULL. err->code is MuError value * * @return a new MuStore object with ref count == 1, or NULL in case * of error; free with mu_store_unref */ MuStore* mu_store_new_create (const char *xpath, const char *maildir, - GError **err) + const char **personal_addresses, GError **err) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; /** @@ -346,13 +342,13 @@ const char *mu_store_database_path (const MuStore *store); /** - * Get the maildir for this message store. + * Get the root-maildir for this message store. * * @param store the store * * @return the maildir. */ -const char *mu_store_maildir(const MuStore *store); +const char *mu_store_root_maildir(const MuStore *store); /** @@ -364,20 +360,6 @@ const char *mu_store_maildir(const MuStore *store); */ time_t mu_store_created(const MuStore *store); - -/** - * register a char** of email addresses as 'my' addresses, ie. mark - * message that have these addresses in one of the address fields as - * 'personal' (e.g., in mu-contacts). calling this function overrides - * any 'my addresses' that were set before, using this function or - * through mu_store_new_writable - * - * @param store a valid store object - * @param my_addresses a char** of email addresses - */ -void mu_store_set_personal_addresses (MuStore *store, - const char **my_addresses); - /** * Get the list of personal addresses from the store * @@ -455,15 +437,12 @@ unsigned mu_store_update_msg (MuStore *store, unsigned docid, MuMsg *msg, * * @param store a valid store * @param path full filesystem path to a valid message - * @param maildir set the maildir (e.g. "/drafts") for this message, or NULL - * note that you cannot mu_msg_move_msg_to_maildir unless maildir is set. * @param err receives error information, if any, or NULL * * @return the docid of the stored message, or 0 * (MU_STORE_INVALID_DOCID) in case of error */ -unsigned mu_store_add_path (MuStore *store, const char *path, - const char* maildir, GError **err); +unsigned mu_store_add_path (MuStore *store, const char *path, GError **err); /** * remove a message from the database based on its path diff --git a/lib/test-mu-store.c b/lib/test-mu-store.c index ae7bfb06..3276d575 100644 --- a/lib/test-mu-store.c +++ b/lib/test-mu-store.c @@ -44,7 +44,7 @@ test_mu_store_new_destroy (void) g_assert (tmpdir); err = NULL; - store = mu_store_new_create (tmpdir, "/tmp", &err); + store = mu_store_new_create (tmpdir, "/tmp", NULL, &err); g_assert_no_error (err); g_assert (store); @@ -68,7 +68,7 @@ test_mu_store_version (void) g_assert (tmpdir); err = NULL; - store = mu_store_new_create (tmpdir, "/tmp", &err); + store = mu_store_new_create (tmpdir, "/tmp", NULL, &err); g_assert (store); mu_store_unref (store); store = mu_store_new_readable (tmpdir, &err); @@ -95,7 +95,7 @@ test_mu_store_store_msg_and_count (void) tmpdir = test_mu_common_get_random_tmpdir(); g_assert (tmpdir); - store = mu_store_new_create (tmpdir, MU_TESTMAILDIR, NULL); + store = mu_store_new_create (tmpdir, MU_TESTMAILDIR, NULL, NULL); g_assert (store); g_free (tmpdir); @@ -152,7 +152,7 @@ test_mu_store_store_msg_remove_and_count (void) tmpdir = test_mu_common_get_random_tmpdir(); g_assert (tmpdir); - store = mu_store_new_create (tmpdir, MU_TESTMAILDIR, NULL); + store = mu_store_new_create (tmpdir, MU_TESTMAILDIR, NULL, NULL); g_assert (store); g_assert_cmpuint (0,==,mu_store_count (store, NULL)); diff --git a/lib/utils/mu-utils.hh b/lib/utils/mu-utils.hh index 30cd1186..c5787d05 100644 --- a/lib/utils/mu-utils.hh +++ b/lib/utils/mu-utils.hh @@ -174,15 +174,6 @@ static inline std::string to_string (const T& val) * */ -#define MU_STORE_CATCH_BLOCK_RETURN(GE,R) \ - catch (const MuStoreError& merr) { \ - mu_util_g_set_error ((GE), \ - merr.mu_error(), "%s", \ - merr.what().c_str()); \ - return (R); \ - } \ - - #define MU_XAPIAN_CATCH_BLOCK \ catch (const Xapian::Error &xerr) { \ g_critical ("%s: xapian error '%s'", \ diff --git a/mu/mu-cmd-index.c b/mu/mu-cmd-index.c index ea881e0a..592cd719 100644 --- a/mu/mu-cmd-index.c +++ b/mu/mu-cmd-index.c @@ -279,7 +279,7 @@ index_title (MuStore *store, MuConfig *opts) #pragma GCC diagnostic pop g_print ("created : %s%s%s\n", green, tbuf, def); g_print ("maildir : %s%s%s\n", - green, mu_store_maildir (store), def); + green, mu_store_root_maildir (store), def); g_print ("personal-addresses : "); diff --git a/mu/mu-cmd-server.cc b/mu/mu-cmd-server.cc index b7360504..1ecef7f5 100644 --- a/mu/mu-cmd-server.cc +++ b/mu/mu-cmd-server.cc @@ -222,8 +222,6 @@ message_options (const Parameters& params) return (MuMsgOptions)opts; } - - /* 'add' adds a message to the database, and takes two parameters: 'path', which * is the full path to the message, and 'maildir', which is the maildir this * message lives in (e.g. "/inbox"). response with an (:info ...) message with @@ -233,10 +231,9 @@ static void add_handler (Context& context, const Parameters& params) { const auto path{get_string_or(params, "path")}; - const auto maildir{get_string_or(params, "maildir")}; GError *gerr{}; - const auto docid{mu_store_add_path (context.store, path.c_str(), maildir.c_str(), &gerr)}; + const auto docid{mu_store_add_path (context.store, path.c_str(), &gerr)}; if (docid == MU_STORE_INVALID_DOCID) throw Error(Error::Code::Store, &gerr, "failed to add message at %s", path.c_str()); @@ -1048,9 +1045,7 @@ sent_handler (Context& context, const Parameters& params) { GError *gerr{}; const auto path{get_string_or(params, "path")}; - const auto docid{mu_store_add_path(context.store, path.c_str(), - get_string_or(params, "maildir").c_str(), - &gerr)}; + const auto docid{mu_store_add_path(context.store, path.c_str(), &gerr)}; if (docid == MU_STORE_INVALID_DOCID) throw Error{Error::Code::Store, &gerr, "failed to add path"}; @@ -1092,8 +1087,7 @@ make_command_map (Context& context) cmap.emplace("add", CommandInfo{ - ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}, - {"maildir", ArgInfo{Type::String, true, "the maildir the where the message lives" }}}, + ArgMap{ {"path", ArgInfo{Type::String, true, "file system path to the message" }}}, "add a message to the store", [&](const auto& params){add_handler(context, params);}}); diff --git a/mu/mu-cmd.c b/mu/mu-cmd.c index 703db945..428bd592 100644 --- a/mu/mu-cmd.c +++ b/mu/mu-cmd.c @@ -374,7 +374,7 @@ foreach_msg_file (MuStore *store, MuConfig *opts, static gboolean add_path_func (MuStore *store, const char *path, GError **err) { - return mu_store_add_path (store, path, NULL, err); + return mu_store_add_path (store, path, err); } @@ -569,17 +569,37 @@ show_usage (void) typedef MuError (*store_func) (MuStore *, MuConfig *, GError **err); -static gboolean -needs_rebuild (MuStore *store, MuConfig *opts, GError **err) +static MuStore* +get_store (MuConfig *opts, gboolean read_only, GError **err) { - if (store) - return mu_store_count(store, NULL) == 0; - else - return err && - (*err)->code == MU_ERROR_XAPIAN_NEEDS_REINDEX && - opts->maildir; + if (opts->rebuild && read_only) { + g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR, + "cannot rebuild a read-only database"); + return NULL; + } + + if (read_only) + return mu_store_new_readable ( + mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), err); + + if (!opts->rebuild) + return mu_store_new_writable + (mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), err); + + if (!opts->maildir) { + g_set_error (err, MU_ERROR_DOMAIN, + MU_ERROR_IN_PARAMETERS, + "missing --maildir parameter"); + return NULL; + } + + return mu_store_new_create (mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), + opts->maildir, + (const char**)opts->my_addresses, + err); } + static MuError with_store (store_func func, MuConfig *opts, gboolean read_only, GError **err) @@ -587,42 +607,10 @@ with_store (store_func func, MuConfig *opts, gboolean read_only, MuStore *store; MuError merr; - if (opts->rebuild && read_only) { - g_set_error (err, MU_ERROR_DOMAIN, MU_ERROR, - "cannot rebuild a read-only database"); - return MU_G_ERROR_CODE(err); - } - - if (read_only) { - store = mu_store_new_readable ( - mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), err); - } else if (!opts->rebuild) { - store = mu_store_new_writable - (mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), err); - if (needs_rebuild (store, opts, err)) { - if (store) - mu_store_unref(store); - opts->rebuild = TRUE; - g_clear_error (err); - return with_store(func, opts, read_only, err); - } - } else { /* rebuilding */ - if (!opts->maildir) { - g_set_error (err, MU_ERROR_DOMAIN, - MU_ERROR_IN_PARAMETERS, - "missing --maildir parameter"); - } - store = mu_store_new_create - (mu_runtime_path(MU_RUNTIME_PATH_XAPIANDB), - opts->maildir, err); - } - + store = get_store (opts, read_only, err); if (!store) return MU_G_ERROR_CODE(err); - if (!read_only && opts->my_addresses) - mu_store_set_personal_addresses ( - store, (const char**)opts->my_addresses); merr = func (store, opts, err); mu_store_unref (store);