diff --git a/lib/mu-message-fields.cc b/lib/mu-message-fields.cc index 5931dd35..5e1e5880 100644 --- a/lib/mu-message-fields.cc +++ b/lib/mu-message-fields.cc @@ -18,6 +18,7 @@ */ #include "mu-message-fields.hh" +#include "mu-message-flags.hh" using namespace Mu; @@ -66,6 +67,35 @@ validate_message_field_shortcuts() } return true; } + + +constexpr /*static*/ bool +validate_message_field_flags() +{ + for (auto&& field: MessageFields) { + /* - A field has at most one of Indexable, HasTerms, IsXapianBoolean and + IsContact. */ + size_t flagnum{}; + if (field.is_indexable_term()) + ++flagnum; + if (field.is_boolean_term()) + ++flagnum; + if (field.is_normal_term()) + ++flagnum; + if (field.is_contact()) + ++flagnum; + + if (flagnum > 1) { + //g_warning("invalid field %*s", STR_V(field.name)); + return false; + } + } + + return true; +} + + + /* * tests... also build as runtime-tests, so we can get coverage info */ @@ -96,6 +126,13 @@ test_prefix() static_assert(message_field(MessageField::Id::BodyHtml).xapian_prefix() == 0); } +[[maybe_unused]] +static void +test_field_flags() +{ + static_assert(validate_message_field_flags()); +} + #ifdef BUILD_TESTS static void @@ -120,6 +157,7 @@ main(int argc, char* argv[]) g_test_add_func("/message/fields/shortcuts", test_shortcuts); g_test_add_func("/message/fields/prefix", test_prefix); g_test_add_func("/message/fields/xapian-term", test_xapian_term); + g_test_add_func("/message/fields/flags", test_field_flags); return g_test_run(); } diff --git a/lib/mu-message-fields.hh b/lib/mu-message-fields.hh index b08b7dfe..cc7d1df5 100644 --- a/lib/mu-message-fields.hh +++ b/lib/mu-message-fields.hh @@ -55,7 +55,6 @@ struct MessageField { Path, /**< File-system Path */ Subject, /**< Message subject */ To, /**< To: recipient */ - Uid, /**< Unique id for message (based on path) */ /* * string list items... */ @@ -89,7 +88,6 @@ struct MessageField { return static_cast(Id::_count_); } - constexpr Xapian::valueno value_no() const { return static_cast(id); } @@ -121,39 +119,58 @@ struct MessageField { * can be multiple terms, each containing e.g. one of the To: * addresses - searching uses terms, but to display some field, it * must be in the value (at least when using MuMsgIter) + * + * Rules (build-time enforced): + * - A field has at most one of Indexable, HasTerms, IsXapianBoolean and IsContact. */ enum struct Flag { - GMime = 1 << 0, + GMime = 1 << 0, /**< Field retrieved through gmime */ - FullText = 1 << 1, - /**< Field-text is indexed in xapian (i.e., the text is processed */ - Searchable = 1 << 2, + + /* + * Different kind of terms; at most one is true, + * and cannot be combined with IsContact. Compile-time enforced. + */ + NormalTerm = 1 << 2, /**< Field is a searchable term */ - Value = 1 << 3, + BooleanTerm = 1 << 5, + /**< Field is a boolean search-term; wildcards do not work */ + IndexableTerm = 1 << 1, + /**< Field has indexable text as term */ + + /* + * Contact flag cannot be combined with any of the term flags. + * This is compile-time enforced. + */ + Contact = 1 << 4, + /**< field contains one or more e-mail-addresses */ + + Value = 1 << 3, /**< Field value is stored (so the literal value can be retrieved) */ - Contact = 1 << 4, - /**< field contains one or more * e-mail-addresses */ - XapianBoolean = 1 << 5, - /**< use 'add_boolean_prefix' for Xapian queries; wildcards do NOT WORK for such - * fields */ - DoNotCache = 1 << 6, + + DoNotCache = 1 << 6, /**< don't cache this field in * the MuMsg cache */ - Range = 1 << 7 /**< whether this is a range field (e.g., date, size)*/ + Range = 1 << 7 + /**< whether this is a range field (e.g., date, size)*/ }; constexpr bool any_of(Flag some_flag) const{ return (static_cast(some_flag) & static_cast(flags)) != 0; } - constexpr bool is_gmime() const { return any_of(Flag::Value); } - constexpr bool is_full_text() const { return any_of(Flag::FullText); } - constexpr bool is_searchable() const { return any_of(Flag::Searchable); } - constexpr bool is_value() const { return any_of(Flag::Value); } - constexpr bool is_contact() const { return any_of(Flag::Contact); } - constexpr bool is_xapian_boolean() const { return any_of(Flag::XapianBoolean); } - constexpr bool is_range() const { return any_of(Flag::Range); } - constexpr bool do_not_cache() const { return any_of(Flag::DoNotCache); } + constexpr bool is_gmime() const { return any_of(Flag::GMime); } + + constexpr bool is_indexable_term() const { return any_of(Flag::IndexableTerm); } + constexpr bool is_boolean_term() const { return any_of(Flag::BooleanTerm); } + constexpr bool is_normal_term() const { return any_of(Flag::NormalTerm); } + + constexpr bool is_value() const { return any_of(Flag::Value); } + + constexpr bool is_contact() const { return any_of(Flag::Contact); } + constexpr bool is_range() const { return any_of(Flag::Range); } + constexpr bool do_not_cache() const { return any_of(Flag::DoNotCache); } + /** * Field members @@ -190,7 +207,6 @@ MU_ENABLE_BITOPS(MessageField::Flag); static constexpr std::array MessageFields = { { - // Bcc { MessageField::Id::Bcc, @@ -199,8 +215,10 @@ static constexpr std::array "Blind carbon-copy recipient", "bcc:foo@example.com", 'h', - MessageField::Flag::GMime | MessageField::Flag::Contact | - MessageField::Flag::Value}, + MessageField::Flag::GMime | + MessageField::Flag::Contact | + MessageField::Flag::Value + }, // HTML Body { MessageField::Id::BodyHtml, @@ -209,7 +227,9 @@ static constexpr std::array "Message html body", {}, {}, - MessageField::Flag::GMime | MessageField::Flag::DoNotCache}, + MessageField::Flag::GMime | + MessageField::Flag::DoNotCache + }, // Body { MessageField::Id::BodyText, @@ -218,9 +238,10 @@ static constexpr std::array "Message plain-text body", "body:capybara", // example 'b', - MessageField::Flag::GMime | MessageField::Flag::FullText | - MessageField::Flag::DoNotCache}, - + MessageField::Flag::GMime | + MessageField::Flag::IndexableTerm | + MessageField::Flag::DoNotCache + }, // Cc { MessageField::Id::Cc, @@ -229,7 +250,8 @@ static constexpr std::array "Carbon-copy recipient", "cc:quinn@example.com", 'c', - MessageField::Flag::GMime | MessageField::Flag::Contact | + MessageField::Flag::GMime | + MessageField::Flag::Contact | MessageField::Flag::Value}, // Embed @@ -240,7 +262,8 @@ static constexpr std::array "Embedded text", "embed:war OR embed:peace", 'e', - MessageField::Flag::GMime | MessageField::Flag::FullText | + MessageField::Flag::GMime | + MessageField::Flag::IndexableTerm | MessageField::Flag::DoNotCache}, // File { @@ -250,7 +273,8 @@ static constexpr std::array "Attachment file name", "file:/image\\.*.jpg/", 'j', - MessageField::Flag::GMime | MessageField::Flag::Searchable | + MessageField::Flag::GMime | + MessageField::Flag::NormalTerm | MessageField::Flag::DoNotCache}, // From @@ -261,7 +285,8 @@ static constexpr std::array "Message sender", "from:jimbo", 'f', - MessageField::Flag::GMime | MessageField::Flag::Contact | + MessageField::Flag::GMime | + MessageField::Flag::Contact | MessageField::Flag::Value}, // Maildir { @@ -271,7 +296,8 @@ static constexpr std::array "Maildir path for message", "maildir:/private/archive", 'm', - MessageField::Flag::GMime | MessageField::Flag::Searchable | + MessageField::Flag::GMime | + MessageField::Flag::NormalTerm | MessageField::Flag::Value}, // MIME { @@ -281,7 +307,7 @@ static constexpr std::array "Attachment MIME-type", "mime:image/jpeg", 'y', - MessageField::Flag::Searchable}, + MessageField::Flag::NormalTerm}, // Message-ID { MessageField::Id::MessageId, @@ -291,7 +317,7 @@ static constexpr std::array "mime:image/jpeg", 'i', MessageField::Flag::GMime | - MessageField::Flag::Searchable | + MessageField::Flag::NormalTerm | MessageField::Flag::Value}, // Path { @@ -302,7 +328,7 @@ static constexpr std::array {}, 'p', MessageField::Flag::GMime | - MessageField::Flag::XapianBoolean | + MessageField::Flag::BooleanTerm | MessageField::Flag::Value}, // Subject @@ -314,9 +340,8 @@ static constexpr std::array "subject:wombat", 's', MessageField::Flag::GMime | - MessageField::Flag::Searchable | MessageField::Flag::Value | - MessageField::Flag::FullText}, + MessageField::Flag::IndexableTerm}, // To { @@ -326,19 +351,10 @@ static constexpr std::array "Message recipient", "to:flimflam@example.com", 't', - MessageField::Flag::GMime | MessageField::Flag::Contact | - MessageField::Flag::Value}, - - // UID (internal) - { - MessageField::Id::Uid, - MessageField::Type::String, - "uid", - "Message recipient", - {}, - 'u', - MessageField::Flag::Searchable}, - + MessageField::Flag::GMime | + MessageField::Flag::Contact | + MessageField::Flag::Value + }, // References { MessageField::Id::References, @@ -347,8 +363,9 @@ static constexpr std::array "Message references to other messages", {}, 'r', - MessageField::Flag::GMime | MessageField::Flag::Value}, - + MessageField::Flag::GMime | + MessageField::Flag::Value + }, // Tags { MessageField::Id::Tags, @@ -357,9 +374,10 @@ static constexpr std::array "Message tags", "tag:projectx", 'x', - MessageField::Flag::GMime | MessageField::Flag::Searchable | - MessageField::Flag::Value}, - + MessageField::Flag::GMime | + MessageField::Flag::NormalTerm | + MessageField::Flag::Value + }, // Date { MessageField::Id::Date, @@ -368,10 +386,10 @@ static constexpr std::array "Message date", "date:20220101..20220505", 'd', - MessageField::Flag::GMime | MessageField::Flag::Searchable | - MessageField::Flag::Value | MessageField::Flag::XapianBoolean | - MessageField::Flag::Range}, - + MessageField::Flag::GMime | + MessageField::Flag::Value | + MessageField::Flag::Range + }, // Flags { MessageField::Id::Flags, @@ -380,8 +398,10 @@ static constexpr std::array "Message properties", "flag:unread", 'g', - MessageField::Flag::GMime | MessageField::Flag::Searchable | - MessageField::Flag::Value}, + MessageField::Flag::GMime | + MessageField::Flag::NormalTerm | + MessageField::Flag::Value + }, // Priority { MessageField::Id::Priority, @@ -391,9 +411,9 @@ static constexpr std::array "prio:high", 'p', MessageField::Flag::GMime | - MessageField::Flag::Searchable | - MessageField::Flag::Value}, - + MessageField::Flag::NormalTerm | + MessageField::Flag::Value + }, // Size { MessageField::Id::Size, @@ -403,10 +423,9 @@ static constexpr std::array "size:1M..5M", 'z', MessageField::Flag::GMime | - MessageField::Flag::Searchable | MessageField::Flag::Value | - MessageField::Flag::Range}, - + MessageField::Flag::Range + }, // Mailing List { MessageField::Id::MailingList, @@ -415,9 +434,10 @@ static constexpr std::array "Mailing list (List-Id:)", "list:mu-discuss.googlegroups.com", 'v', - MessageField::Flag::GMime | MessageField::Flag::Searchable | - MessageField::Flag::Value}, - + MessageField::Flag::GMime | + MessageField::Flag::NormalTerm | + MessageField::Flag::Value + }, // ThreadId { MessageField::Id::ThreadId, @@ -426,7 +446,8 @@ static constexpr std::array "Thread a message belongs to", {}, 'w', - MessageField::Flag::Searchable}, + MessageField::Flag::NormalTerm + }, }}; /* diff --git a/lib/mu-parser.cc b/lib/mu-parser.cc index 35254e35..9db1b70c 100644 --- a/lib/mu-parser.cc +++ b/lib/mu-parser.cc @@ -122,7 +122,7 @@ add_field(std::vector& fields, Field::Id field_id) return; // can't be searched fields.emplace_back(FieldInfo{std::string{field.name}, field.xapian_term(), - field.is_full_text(), field_id}); + field.is_indexable_term(), field_id}); } static std::vector diff --git a/lib/mu-store.cc b/lib/mu-store.cc index 9066efe1..cf13c7af 100644 --- a/lib/mu-store.cc +++ b/lib/mu-store.cc @@ -36,6 +36,7 @@ #include #include "mu-message-flags.hh" +#include "mu-message.hh" #include "mu-msg.hh" #include "mu-store.hh" #include "mu-query.hh" @@ -716,13 +717,13 @@ static void add_terms_values_str(Xapian::Document& doc, const char* val, const Field& field) { const auto flat = Mu::utf8_flatten(val); - if (field.is_full_text()) { + if (field.is_indexable_term()) { Xapian::TermGenerator termgen; termgen.set_document(doc); termgen.index_text(flat, 1, field.xapian_term()); } - if (field.is_searchable()) + if (field.is_normal_term()) add_term(doc, field.xapian_term(flat)); } @@ -758,7 +759,7 @@ add_terms_values_string_list(Xapian::Document& doc, MuMsg* msg, const Field& fie g_free(str); } - if (field.is_searchable()) { + if (field.is_normal_term()) { for (; lst; lst = g_slist_next((GSList*)lst)) add_terms_values_str(doc, (const gchar*)lst->data, field); }