From 87c3ceb7b19abebc88136c16332c421a8dfc8a72 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 3 Dec 2022 16:42:19 +0200 Subject: [PATCH] store: update move_message API Update the move_message API so to allow for updating duplicate messages too (not implemented yet), and return all updated messages. --- lib/mu-store.cc | 142 +++++++++++++++++++++++++++++++++++++++++------- lib/mu-store.hh | 31 ++++++++--- 2 files changed, 147 insertions(+), 26 deletions(-) diff --git a/lib/mu-store.cc b/lib/mu-store.cc index 2ae1a1e8..d135febf 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -244,6 +244,10 @@ struct Store::Private { Option find_message_unlocked(Store::Id docid) const; Result update_message_unlocked(Message& msg, Store::Id docid); Result update_message_unlocked(Message& msg, const std::string& old_path); + Result move_message_unlocked(Message&& msg, + Option target_mdir, + Option new_flags, + MoveOptions opts); /* metadata to write as part of a transaction commit */ std::unordered_map metadata_cache_; @@ -503,44 +507,144 @@ Store::find_message(Store::Id docid) const return priv_->find_message_unlocked(docid); } - +/** + * Move a message in store and filesystem. + * + * Lock is assumed taken already + * + * @param id message id + * @param target_mdir target_midr (or Nothing for current) + * @param new_flags new flags (or Notthing) +'; * @param opts move_optionss + * + * @return the Message after the moving, or an Error + */ Result -Store::move_message(Store::Id id, - Option target_mdir, - Option new_flags, bool change_name) +Store::Private::move_message_unlocked(Message&& msg, + Option target_mdir, + Option new_flags, + MoveOptions opts) { - std::lock_guard guard{priv_->lock_}; - - auto msg = priv_->find_message_unlocked(id); - if (!msg) - return Err(Error::Code::Store, "cannot find message <%u>", id); - - const auto old_path = msg->path(); - const auto target_flags = new_flags.value_or(msg->flags()); - const auto target_maildir = target_mdir.value_or(msg->maildir()); + const auto old_path = msg.path(); + const auto target_flags = new_flags.value_or(msg.flags()); + const auto target_maildir = target_mdir.value_or(msg.maildir()); /* 1. first determine the file system path of the target */ const auto target_path = - maildir_determine_target(msg->path(), properties().root_maildir, - target_maildir,target_flags, change_name); + maildir_determine_target(msg.path(), properties_.root_maildir, + target_maildir, target_flags, + any_of(opts & MoveOptions::ChangeName)); if (!target_path) return Err(target_path.error()); /* 2. let's move it */ - if (const auto res = maildir_move_message(msg->path(), target_path.value()); !res) + if (const auto res = maildir_move_message(msg.path(), target_path.value()); !res) return Err(res.error()); /* 3. file move worked, now update the message with the new info.*/ - if (auto&& res = msg->update_after_move( + if (auto&& res = msg.update_after_move( target_path.value(), target_maildir, target_flags); !res) return Err(res.error()); /* 4. update message worked; re-store it */ - if (auto&& res = priv_->update_message_unlocked(*msg, old_path); !res) + if (auto&& res = update_message_unlocked(msg, old_path); !res) return Err(res.error()); /* 6. Profit! */ - return Ok(std::move(msg.value())); + return Ok(std::move(msg)); +} + + + +/* get a vec of all messages with the given message id */ +static Store::IdMessageVec +messages_with_msgid(const Store& store, const std::string& msgid, size_t max=100) +{ + if (msgid.size() > MaxTermLength) { + g_warning("invalid message-id '%s'", msgid.c_str()); + return {}; + } else if (msgid.empty()) + return {}; + + const auto xprefix{field_from_id(Field::Id::MessageId).shortcut}; + /*XXX this is a bit dodgy */ + auto tmp{g_ascii_strdown(msgid.c_str(), -1)}; + auto expr{g_strdup_printf("%c:%s", xprefix, tmp)}; + g_free(tmp); + + const auto res{store.run_query(expr, {}, QueryFlags::None, max)}; + g_free(expr); + if (!res) { + g_warning("failed to run message-id-query: %s", res.error().what()); + return {}; + } + if (res->empty()) { + g_warning("could not find message(s) for msgid %s", msgid.c_str()); + return {}; + } + + Store::IdMessageVec imvec; + for (auto&& mi : *res) + imvec.emplace_back(std::make_pair(mi.doc_id(), mi.message().value())); + + return imvec; +} + + +static Flags +filter_dup_flags(Flags old_flags, Flags new_flags) +{ + new_flags = flags_keep_unmutable(old_flags, new_flags, Flags::Draft); + new_flags = flags_keep_unmutable(old_flags, new_flags, Flags::Flagged); + new_flags = flags_keep_unmutable(old_flags, new_flags, Flags::Trashed); + + return new_flags; +} + +Result +Store::move_message(Store::Id id, + Option target_mdir, + Option new_flags, + MoveOptions opts) +{ + std::lock_guard guard{priv_->lock_}; + + auto msg{priv_->find_message_unlocked(id)}; + if (!msg) + return Err(Error::Code::Store, "cannot find message <%u>", id); + + auto res{priv_->move_message_unlocked(std::move(*msg), target_mdir, new_flags, opts)}; + if (!res) + return Err(res.error()); + + IdMessageVec imvec; + imvec.emplace_back(std::make_pair(id, std::move(*res))); + if (none_of(opts & Store::MoveOptions::DupFlags) || !new_flags) + return Ok(std::move(imvec)); + + /* handle the dupflags case; i.e. apply (a subset of) the flags to + * all messages with the same message-id as well */ + for (auto&& [docid, msg]: messages_with_msgid(*this, imvec.at(0).second.message_id())) { + + if (docid == id) + continue; // already + + /* For now, don't change Draft/Flagged/Trashed */ + Flags dup_flags = filter_dup_flags(msg.flags(), *new_flags); + + /* use the updated new_flags and default MoveOptions (so we don't recurse, nor do we + * change the base-name of moved messages) */ + auto dup_res = priv_->move_message_unlocked(std::move(msg), Nothing, + dup_flags, + Store::MoveOptions::None); + // just log a warning if it fails, but continue. + if (dup_res) + imvec.emplace_back(docid, std::move(*dup_res)); + else + g_warning("failed to move dup: %s", dup_res.error().what()); + } + + return Ok(std::move(imvec)); } std::string diff --git a/lib/mu-store.hh b/lib/mu-store.hh index 0a745a6f..11dc0c64 100644 --- a/lib/mu-store.hh +++ b/lib/mu-store.hh @@ -307,21 +307,37 @@ public: */ bool contains_message(const std::string& path) const; + /** - * Move a message both in the filesystem and in the store. - * After a successful move, the message is updated. + * Options for moving + * + */ + enum struct MoveOptions { + None = 0, /**< Defaults */ + ChangeName = 1 << 0, /**< Change the name when moving */ + DupFlags = 1 << 1, /**< Update flags for duplicate messages too*/ + }; + + + /** + * Move a message both in the filesystem and in the store. After a + * successful move, the message is updated. * * @param id the id for some message * @param target_mdir the target maildir (if any) * @param new_flags new flags (if any) * @param change_name whether to change the name * - * @return Result, either the moved message or some error. + * @return Result, either a vec of for the moved + * message(s) or some error. Note that in case of success at least one + * message is returned, and only with MoveOptions::DupFlags can it be + * more than one. */ - Result move_message(Store::Id id, - Option target_mdir = Nothing, - Option new_flags = Nothing, - bool change_name = false); + using IdMessageVec = std::vector>; + Result move_message(Store::Id id, + Option target_mdir = Nothing, + Option new_flags = Nothing, + MoveOptions opts = MoveOptions::None); /** * Prototype for the ForEachMessageFunc @@ -466,6 +482,7 @@ private: }; MU_ENABLE_BITOPS(Store::Options); +MU_ENABLE_BITOPS(Store::MoveOptions); } // namespace Mu