diff --git a/lib/message/mu-message.cc b/lib/message/mu-message.cc index e03a7a84..fe20284f 100644 --- a/lib/message/mu-message.cc +++ b/lib/message/mu-message.cc @@ -24,8 +24,8 @@ #include "mu-maildir.hh" #include -#include #include +#include #include #include #include @@ -43,8 +43,7 @@ using namespace Mu; struct Message::Private { - Private(Message::Options options=Message::Options::None): - opts{options} {} + Private(Message::Options options): opts{options} {} Message::Options opts; Document doc; @@ -52,7 +51,6 @@ struct Message::Private { Flags flags{}; Option mailing_list; - std::vector references; std::vector parts; ::time_t mtime{}; @@ -92,11 +90,9 @@ get_statbuf(const std::string& path) } -Message::Message(Message::Options opts, const std::string& path, - const std::string& mdir): +Message::Message(const std::string& path, Message::Options opts): priv_{std::make_unique(opts)} { - const auto statbuf{get_statbuf(path)}; if (!statbuf) throw statbuf.error(); @@ -113,17 +109,14 @@ Message::Message(Message::Options opts, const std::string& path, if (xpath) priv_->doc.add(Field::Id::Path, std::move(xpath.value())); - if (!mdir.empty()) - priv_->doc.add(Field::Id::Maildir, mdir); - priv_->doc.add(Field::Id::Size, static_cast(statbuf->st_size)); // rest of the fields fill_document(*priv_); } -Message::Message(Message::Options opts, const std::string& text, const std::string& path, - const std::string& mdir): +Message::Message(const std::string& text, const std::string& path, + Message::Options opts): priv_{std::make_unique(opts)} { if (!path.empty()) { @@ -132,9 +125,6 @@ Message::Message(Message::Options opts, const std::string& text, const std::stri priv_->doc.add(Field::Id::Path, std::move(xpath.value())); } - if (!mdir.empty()) - priv_->doc.add(Field::Id::Maildir, mdir); - priv_->doc.add(Field::Id::Size, static_cast(text.size())); init_gmime(); @@ -162,7 +152,7 @@ Message::operator=(Message&& other) noexcept } Message::Message(Document&& doc): - priv_{std::make_unique()} + priv_{std::make_unique(Message::Options::None)} { priv_->doc = std::move(doc); } @@ -176,6 +166,28 @@ Message::document() const return priv_->doc; } +Result +Message::set_maildir(const std::string& maildir) +{ + /* sanity check a little bit */ + + if (maildir.empty() || + maildir.at(0) != '/' || + (maildir.size() > 1 && maildir.at(maildir.length()-1) == '/')) + return Err(Error::Code::Message, +"'%s' is not a valid maildir", maildir.c_str()); + + const auto path{document().string_value(Field::Id::Path)}; + if (path == maildir || path.find(maildir) == std::string::npos) + return Err(Error::Code::Message, + "'%s' is not a valid maildir for message @ %s", + maildir.c_str(), path.c_str()); + + priv_->doc.add(Field::Id::Maildir, maildir); + + return Ok(); +} + bool Message::load_mime_message(bool reload) const { @@ -242,19 +254,20 @@ get_priority(const MimeMessage& mime_msg) /* see: http://does-not-exist.org/mail-archives/mutt-dev/msg08249.html */ static std::vector -get_tags(const MimeMessage& mime_msg) +extract_tags(const MimeMessage& mime_msg) { constexpr std::array, 3> tag_headers = {{ {"X-Label", ' '}, {"X-Keywords", ','}, {"Keywords", ' '} }}; + static const auto strip_rx{std::regex("^\\s+| +$|( )\\s+")}; std::vector tags; seq_for_each(tag_headers, [&](auto&& item) { - if (auto&& hdr{mime_msg.header(item.first)}; hdr) { - - auto lst = split(*hdr, item.second); - tags.reserve(tags.size() + lst.size()); - tags.insert(tags.end(), lst.begin(), lst.end()); + if (auto&& hdr = mime_msg.header(item.first); hdr) { + for (auto&& item : split(*hdr, item.second)) { + tags.emplace_back( + std::regex_replace(item, strip_rx, "$1")); + } } }); @@ -589,7 +602,8 @@ fill_document(Message::Private& priv) doc.add(get_priority(mime_msg)); break; case Field::Id::References: - doc.add(field.id, refs); + if (!refs.empty()) + doc.add(field.id, refs); break; case Field::Id::Size: /* already */ break; @@ -597,7 +611,8 @@ fill_document(Message::Private& priv) doc.add(field.id, mime_msg.subject()); break; case Field::Id::Tags: - doc.add(field.id, get_tags(mime_msg)); + if (auto&& tags{extract_tags(mime_msg)}; !tags.empty()) + doc.add(field.id, tags); break; case Field::Id::ThreadId: // either the oldest reference, or otherwise the message id @@ -606,9 +621,6 @@ fill_document(Message::Private& priv) case Field::Id::To: doc.add(field.id, mime_msg.addresses(AddrType::To)); break; - case Field::Id::Uid: - doc.add(field.id, path); // just a synonym for now. - break; case Field::Id::_count_: break; } @@ -686,9 +698,6 @@ Message::mtime() const return priv_->mtime; } - - - Result Message::update_after_move(const std::string& new_path, const std::string& new_maildir, @@ -699,8 +708,10 @@ Message::update_after_move(const std::string& new_path, return Err(statbuf.error()); priv_->doc.add(Field::Id::Path, new_path); - priv_->doc.add(Field::Id::Maildir, new_maildir); priv_->doc.add(new_flags); + if (const auto res = set_maildir(new_maildir); !res) + return res; + return Ok(); } diff --git a/lib/message/mu-message.hh b/lib/message/mu-message.hh index 3ddb56b5..d1fa5aa3 100644 --- a/lib/message/mu-message.hh +++ b/lib/message/mu-message.hh @@ -47,7 +47,6 @@ public: * access) */ }; - /** * Move CTOR * @@ -66,22 +65,16 @@ public: Message& operator=(Message&& other) noexcept; /** - * Construct a message based on a path. The maildir is optional; however - * messages without maildir cannot be stored in the database + * Construct a message based on a path * - * @param opts options * @param path path to message - * @param mdir the maildir for this message; i.e, if the path is - * ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar; you can - * pass Nothing for this parameter, in which case some maildir-specific - * information is not available. + * @param opts options * * @return a message or an error */ - static Result make_from_path(Options opts, - const std::string& path, - const std::string& mdir={}) try { - return Ok(Message{opts, path, mdir}); + static Result make_from_path(const std::string& path, + Options opts={}) try { + return Ok(Message{path,opts}); } catch (Error& err) { return Err(err); } catch (...) { @@ -103,23 +96,19 @@ public: return Err(Mu::Error(Error::Code::Message, "failed to create message")); } - /** * Construct a message from a string. This is mostly useful for testing. - * - * @param opts options + * @param text message text * @param path path to message - optional; path does not have to exist. - * this is useful for testing. - * @param mdir the maildir for this message; optional, useful for testing. + * @param opts options * * @return a message or an error */ - static Result make_from_text(Options opts, - const std::string& text, + static Result make_from_text(const std::string& text, const std::string& path={}, - const std::string& mdir={}) try { - return Ok(Message{opts, text, path, mdir}); + Options opts={}) try { + return Ok(Message{text, path, opts}); } catch (Error& err) { return Err(err); } catch (...) { @@ -177,12 +166,27 @@ public: Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); } /** - * Get the maildir this message lives in; ie, if the path is - * ~/Maildir/foo/bar/cur/msg, the maildir would be foo/bar + * Get the maildir this message lives in; i.e., if the path is + * ~/Maildir/foo/bar/cur/msg, the maildir would typically be foo/bar + * + * Note that that only messages that live in the store (i.e., are + * constructed use make_from_document() have a non-empty value for + * this.) until set_maildir() is used. * * @return the maildir requested or empty */ std::string maildir() const { return document().string_value(Field::Id::Maildir); } + /** + * Set the maildir for this message. This is for use by the _store_ when + * it has determined the maildir for this message from the message's path and + * the root-maildir known by the store. + * + * @param maildir the maildir for this message + * + * @return Ok() or some error if the maildir is invalid + */ + Result set_maildir(const std::string& maildir); + /** * Get the subject of this message * @@ -262,9 +266,8 @@ public: * @return a list with the tags for this msg. Don't modify/free */ std::vector tags() const { - return document().string_vec_value(Field::Id::References); + return document().string_vec_value(Field::Id::Tags); } - /* * Convert to Sexp */ @@ -288,8 +291,6 @@ public: Result update_after_move(const std::string& new_path, const std::string& new_maildir, Flags new_flags); - - /* * Below require a file-backed message, which is a relatively slow * if there isn't one already; see load_mime_message() @@ -369,11 +370,12 @@ public: bool has_mime_message() const; struct Private; + + Message(Document&& doc); // XXX: make private + private: - Message(Options opts, const std::string& path, const std::string& mdir); - Message(Options opts, const std::string& str, const std::string& path, - const std::string& mdir); - Message(Document&& doc); + Message(const std::string& path, Options opts); + Message(const std::string& str, const std::string& path, Options opt); std::unique_ptr priv_; }; // Message diff --git a/lib/message/mu-mime-object.cc b/lib/message/mu-mime-object.cc index fdb07fdb..07a4ef3d 100644 --- a/lib/message/mu-mime-object.cc +++ b/lib/message/mu-mime-object.cc @@ -22,6 +22,7 @@ #include "gmime/gmime-message.h" #include "utils/mu-utils.hh" #include +#include #include #include @@ -71,13 +72,12 @@ Mu::init_gmime(void) Option MimeObject::header(const std::string& hdr) const noexcept { - const char *val{g_mime_object_get_header(self(), hdr.c_str())}; - if (!val) + if (auto val{g_mime_object_get_header(self(), hdr.c_str())}; !val) return Nothing; - if (!g_utf8_validate(val, -1, {})) - return utf8_clean(hdr); + else if (!g_utf8_validate(val, -1, {})) + return utf8_clean(val); else - return val; + return std::string{val}; } @@ -328,7 +328,7 @@ MimeMessage::references() const noexcept // is ref already in the list? auto is_dup = [](auto&& seq, const std::string& ref) { return seq_find_if(seq, [&](auto&& str) { return ref == str; }) - == seq.cend(); + != seq.cend(); }; std::vector refs; diff --git a/lib/message/test-mu-message.cc b/lib/message/test-mu-message.cc index 4e0e5fa7..823a067d 100644 --- a/lib/message/test-mu-message.cc +++ b/lib/message/test-mu-message.cc @@ -62,14 +62,13 @@ statement you can use something like: goto * instructions[pOp->opcode]; )"; auto message{Message::make_from_text( - {}, test_message_1, - "/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S", - "/inbox")}; + test_message_1, + "/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S")}; g_assert_true(!!message); assert_equal(message->path(), "/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S"); - assert_equal(message->maildir(), "/inbox"); + g_assert_true(message->maildir().empty()); g_assert_true(message->bcc().empty()); @@ -176,7 +175,7 @@ World! --=-=-=-- )"; - auto message{Message::make_from_text({}, msg_text)}; + auto message{Message::make_from_text(msg_text)}; g_assert_true(!!message); g_assert_true(message->path().empty()); @@ -330,10 +329,8 @@ Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg== g_assert_cmpuint(*imported, ==, 1); auto message{Message::make_from_text( - {}, msgtext, - "/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS", - "/inbox")}; + "/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS")}; g_assert_true(!!message); g_assert_true(message->bcc().empty()); @@ -423,10 +420,8 @@ C0bdoCx44QVU8HaZ2x91h3GoM/0q5bqM/rvCauwbokiJgAUrznecNPY= } auto message{Message::make_from_text( - {}, msgtext, - "/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS", - "/archive")}; + "/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS")}; g_assert_true(!!message); g_assert_true(message->flags() == (Flags::Encrypted|Flags::Seen|Flags::Flagged));