message/field: cache the message's sexp

Keep it in the store; much faster than generating on the fly.
This commit is contained in:
Dirk-Jan C. Binnema 2022-05-01 01:13:17 +03:00
parent 263e122a13
commit fea596ae3b
7 changed files with 90 additions and 49 deletions

View File

@ -142,7 +142,7 @@ static void
test_prefix() test_prefix()
{ {
static_assert(field_from_id(Field::Id::Subject).xapian_prefix() == 'S'); static_assert(field_from_id(Field::Id::Subject).xapian_prefix() == 'S');
static_assert(field_from_id(Field::Id::BodyHtml).xapian_prefix() == 0); static_assert(field_from_id(Field::Id::XBodyHtml).xapian_prefix() == 0);
} }
[[maybe_unused]] [[maybe_unused]]

View File

@ -40,7 +40,6 @@ struct Field {
*/ */
enum struct Id { enum struct Id {
Bcc = 0, /**< Blind Carbon-Copy */ Bcc = 0, /**< Blind Carbon-Copy */
BodyHtml, /**< HTML Body */
BodyText, /**< Text body */ BodyText, /**< Text body */
Cc, /**< Carbon-Copy */ Cc, /**< Carbon-Copy */
Date, /**< Message date */ Date, /**< Message date */
@ -64,6 +63,9 @@ struct Field {
/* /*
* <private> * <private>
*/ */
XBodyHtml, /**< HTML Body */
XCachedSexp, /**< Cached message s-expression */
_count_ /**< Number of FieldIds */ _count_ /**< Number of FieldIds */
}; };
@ -214,15 +216,6 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
{
Field::Id::BodyHtml,
Field::Type::String,
"body",
"Message html body",
{},
{},
Field::Flag::Internal
},
{ {
Field::Id::BodyText, Field::Id::BodyText,
Field::Type::String, Field::Type::String,
@ -314,7 +307,7 @@ static constexpr std::array<Field, Field::id_size()>
Field::Id::MessageId, Field::Id::MessageId,
Field::Type::String, Field::Type::String,
"msgid", "msgid",
"Attachment MIME-type", "Message-Id",
"msgid:abc@123", "msgid:abc@123",
'i', 'i',
Field::Flag::BooleanTerm | Field::Flag::BooleanTerm |
@ -418,6 +411,30 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
/* internal */
{
Field::Id::XBodyHtml,
Field::Type::String,
"htmlbody",
"Message html body",
{},
{},
Field::Flag::Internal
},
{
Field::Id::XCachedSexp,
Field::Type::String,
"sexp",
"Cached message s-expression",
{},
{},
Field::Flag::Internal |
Field::Flag::Value
},
}}; }};
/* /*

View File

@ -140,13 +140,10 @@ add_date_and_size(Sexp::List& items, const Message& message)
} }
Mu::Sexp::List Mu::Sexp::List
Message::to_sexp_list(unsigned docid) const Message::to_sexp_list() const
{ {
Sexp::List items; Sexp::List items;
if (docid != 0)
items.add_prop(":docid", Sexp::make_number(docid));
add_prop_nonempty(items, ":subject", subject()); add_prop_nonempty(items, ":subject", subject());
add_prop_nonempty(items, ":message-id", message_id()); add_prop_nonempty(items, ":message-id", message_id());
add_prop_nonempty(items, ":mailing-list", mailing_list()); add_prop_nonempty(items, ":mailing-list", mailing_list());
@ -169,7 +166,7 @@ Message::to_sexp_list(unsigned docid) const
} }
Mu::Sexp Mu::Sexp
Message::to_sexp(unsigned docid) const Message::to_sexp() const
{ {
return Sexp::make_list(to_sexp_list()); return Sexp::make_list(to_sexp_list());
} }

View File

@ -134,6 +134,9 @@ Message::Message(const std::string& text, const std::string& path,
priv_->mime_msg = std::move(msg.value()); priv_->mime_msg = std::move(msg.value());
fill_document(*priv_); fill_document(*priv_);
/* cache the sexp */
priv_->doc.add(Field::Id::XCachedSexp, to_sexp().to_sexp_string());
} }
@ -166,6 +169,13 @@ Message::document() const
return priv_->doc; return priv_->doc;
} }
void
Message::update_cached_sexp()
{
priv_->doc.add(Field::Id::XCachedSexp, to_sexp().to_sexp_string());
}
Result<void> Result<void>
Message::set_maildir(const std::string& maildir) Message::set_maildir(const std::string& maildir)
{ {
@ -175,7 +185,7 @@ Message::set_maildir(const std::string& maildir)
maildir.at(0) != '/' || maildir.at(0) != '/' ||
(maildir.size() > 1 && maildir.at(maildir.length()-1) == '/')) (maildir.size() > 1 && maildir.at(maildir.length()-1) == '/'))
return Err(Error::Code::Message, return Err(Error::Code::Message,
"'%s' is not a valid maildir", maildir.c_str()); "'%s' is not a valid maildir", maildir.c_str());
const auto path{document().string_value(Field::Id::Path)}; const auto path{document().string_value(Field::Id::Path)};
if (path == maildir || path.find(maildir) == std::string::npos) if (path == maildir || path.find(maildir) == std::string::npos)
@ -554,19 +564,15 @@ fill_document(Message::Private& priv)
/* insist on expliclity handling each */ /* insist on expliclity handling each */
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic error "-Wswitch" #pragma GCC diagnostic error "-Wswitch"
using AddrType = MimeMessage::AddressType;
switch(field.id) { switch(field.id) {
case Field::Id::Bcc: case Field::Id::Bcc:
doc.add(field.id, mime_msg.addresses(AddrType::Bcc)); doc.add(field.id, mime_msg.contacts(Contact::Type::Bcc));
break;
case Field::Id::BodyHtml:
doc.add(field.id, priv.body_html);
break; break;
case Field::Id::BodyText: case Field::Id::BodyText:
doc.add(field.id, priv.body_txt); doc.add(field.id, priv.body_txt);
break; break;
case Field::Id::Cc: case Field::Id::Cc:
doc.add(field.id, mime_msg.addresses(AddrType::Cc)); doc.add(field.id, mime_msg.contacts(Contact::Type::Cc));
break; break;
case Field::Id::Date: case Field::Id::Date:
doc.add(field.id, mime_msg.date()); doc.add(field.id, mime_msg.date());
@ -582,7 +588,7 @@ fill_document(Message::Private& priv)
doc.add(priv.flags); doc.add(priv.flags);
break; break;
case Field::Id::From: case Field::Id::From:
doc.add(field.id, mime_msg.addresses(AddrType::From)); doc.add(field.id, mime_msg.contacts(Contact::Type::From));
break; break;
case Field::Id::Maildir: /* already */ case Field::Id::Maildir: /* already */
break; break;
@ -622,8 +628,16 @@ fill_document(Message::Private& priv)
doc.add(field.id, refs.empty() ? message_id : refs.at(0)); doc.add(field.id, refs.empty() ? message_id : refs.at(0));
break; break;
case Field::Id::To: case Field::Id::To:
doc.add(field.id, mime_msg.addresses(AddrType::To)); doc.add(field.id, mime_msg.contacts(Contact::Type::To));
break; break;
/* internal fields */
case Field::Id::XBodyHtml:
doc.add(field.id, priv.body_html);
break;
case Field::Id::XCachedSexp:
break;
/* ignore */
case Field::Id::_count_: case Field::Id::_count_:
break; break;
} }
@ -667,18 +681,7 @@ Message::all_contacts() const
if (!load_mime_message()) if (!load_mime_message())
return contacts; /* empty */ return contacts; /* empty */
for (auto&& ctype: { return priv_->mime_msg->contacts(Contact::Type::None); /* get all types */
MimeMessage::AddressType::Sender,
MimeMessage::AddressType::From,
MimeMessage::AddressType::ReplyTo,
MimeMessage::AddressType::To,
MimeMessage::AddressType::Cc,
MimeMessage::AddressType::Bcc}) {
auto addrs{priv_->mime_msg->addresses(ctype)};
std::move(addrs.begin(), addrs.end(), std::back_inserter(contacts));
}
return contacts;
} }
const std::vector<Message::Part>& const std::vector<Message::Part>&
@ -714,9 +717,10 @@ Message::update_after_move(const std::string& new_path,
priv_->doc.add(Field::Id::Modified, statbuf->st_mtime); priv_->doc.add(Field::Id::Modified, statbuf->st_mtime);
priv_->doc.add(new_flags); priv_->doc.add(new_flags);
if (const auto res = set_maildir(new_maildir); !res) if (const auto res = set_maildir(new_maildir); !res)
return res; return res;
priv_->doc.add(Field::Id::XCachedSexp, to_sexp().to_sexp_string());
return Ok(); return Ok();
} }

View File

@ -177,12 +177,11 @@ public:
Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); } Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); }
/** /**
* Get the maildir this message lives in; i.e., if the path is * Get the maildir this message resides in; i.e., if the path is
* ~/Maildir/foo/bar/cur/msg, the maildir would typically be foo/bar * ~/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 * This is determined when _storing_ the message (which uses
* constructed use make_from_document() have a non-empty value for * set_maildir())
* this.) until set_maildir() is used.
* *
* @return the maildir requested or empty */ * @return the maildir requested or empty */
std::string maildir() const { return document().string_value(Field::Id::Maildir); } std::string maildir() const { return document().string_value(Field::Id::Maildir); }
@ -245,7 +244,6 @@ public:
return static_cast<::time_t>(document().integer_value(Field::Id::Modified)); return static_cast<::time_t>(document().integer_value(Field::Id::Modified));
} }
/** /**
* get the flags for this message. * get the flags for this message.
* *
@ -291,6 +289,16 @@ public:
std::vector<std::string> tags() const { std::vector<std::string> tags() const {
return document().string_vec_value(Field::Id::Tags); return document().string_vec_value(Field::Id::Tags);
} }
/**
* Get the cached s-expression for this message, or {} if not available.
*
* @return sexp or empty.
*/
std::string cached_sexp() const {
return document().string_value(Field::Id::XCachedSexp);
}
/* /*
* Convert to Sexp * Convert to Sexp
*/ */
@ -299,11 +307,19 @@ public:
* convert the message to a Lisp symbolic expression (for further * convert the message to a Lisp symbolic expression (for further
* processing in e.g. emacs) * processing in e.g. emacs)
* *
*
* @return a Mu::Sexp or a Mu::Sexp::List representing the message. * @return a Mu::Sexp or a Mu::Sexp::List representing the message.
*/ */
Mu::Sexp::List to_sexp_list(unsigned docid=0) const; Mu::Sexp::List to_sexp_list() const;
Mu::Sexp to_sexp(unsigned docid=0) const; Mu::Sexp to_sexp() const;
/**
* Update the cached sexp for this message which is stored in the
* document.This should be done when the document is complete,
* i.e., immediately before storing it in the database.
*
*/
void update_cached_sexp();
/* /*
* And some non-const message, for updating an existing * And some non-const message, for updating an existing

View File

@ -344,6 +344,9 @@ Store::add_message(Message& msg, bool use_transaction)
if (auto&& res = msg.set_maildir(mdir.value()); !res) if (auto&& res = msg.set_maildir(mdir.value()); !res)
return Err(res.error()); return Err(res.error());
/* now, we're done with all the fields; generate the sexp string for this
* message */
msg.update_cached_sexp();
std::lock_guard guard{priv_->lock_}; std::lock_guard guard{priv_->lock_};
@ -374,8 +377,12 @@ Store::add_message(Message& msg, bool use_transaction)
bool bool
Store::update_message(const Message& msg, unsigned docid) Store::update_message(Message& msg, unsigned docid)
{ {
msg.update_cached_sexp();
std::lock_guard guard{priv_->lock_};
return xapian_try( return xapian_try(
[&]{ [&]{
priv_->writable_db().replace_document( priv_->writable_db().replace_document(

View File

@ -207,7 +207,7 @@ public:
* *
* @return false in case of failure; true otherwise. * @return false in case of failure; true otherwise.
*/ */
bool update_message(const Message& msg, Id id); bool update_message(Message& msg, Id id);
/** /**
* Remove a message from the store. It will _not_ remove the message * Remove a message from the store. It will _not_ remove the message