mu: add "modified" fields

Add a new "modified" field for checking the last modification time of the
message.
This commit is contained in:
Dirk-Jan C. Binnema 2022-04-30 01:17:31 +03:00
parent 9e0173f387
commit a864616110
4 changed files with 185 additions and 178 deletions

View File

@ -39,39 +39,28 @@ struct Field {
* *
*/ */
enum struct Id { enum struct Id {
/* Bcc = 0, /**< Blind Carbon-Copy */
* first all the string-based ones BodyHtml, /**< HTML Body */
*/ BodyText, /**< Text body */
Bcc = 0, /**< Blind Carbon-Copy */ Cc, /**< Carbon-Copy */
BodyHtml, /**< HTML Body */ Date, /**< Message date */
BodyText, /**< Text body */ EmbeddedText, /**< Embedded text in message */
Cc, /**< Carbon-Copy */ File, /**< Filename */
EmbeddedText, /**< Embedded text in message */ Flags, /**< Message flags */
File, /**< Filename */ From, /**< Message sender */
From, /**< Message sender */ Maildir, /**< Maildir path */
Maildir, /**< Maildir path */ MailingList, /**< Mailing list */
Mime, /**< MIME-Type */ MessageId, /**< Message Id */
MessageId, /**< Message Id */ Mime, /**< MIME-Type */
Path, /**< File-system Path */ Modified, /**< Last modification time */
Subject, /**< Message subject */ Path, /**< File-system Path */
To, /**< To: recipient */ Priority, /**< Message priority */
/* References, /**< All references (incl. Reply-To:) */
* string list items... Size, /**< Message size (in bytes) */
*/ Subject, /**< Message subject */
References, /**< All references (incl. Reply-To:) */ Tags, /**< Message Tags */
Tags, /**< Message Tags */ ThreadId, /**< Thread Id */
/* To, /**< To: recipient */
* then the numerical ones
*/
Date, /**< Message date */
Flags, /**< Message flags */
Priority, /**< Message priority */
Size, /**< Message size (in bytes) */
/* add new ones here... */
MailingList, /**< Mailing list */
ThreadId, /**< Thread Id */
/* /*
* <private> * <private>
*/ */
@ -146,10 +135,9 @@ struct Field {
Value = 1 << 11, Value = 1 << 11,
/**< Field value is stored (so the literal value can be retrieved) */ /**< Field value is stored (so the literal value can be retrieved) */
DoNotCache = 1 << 20, Range = 1 << 21,
/**< don't cache this field in * the MuMsg cache */
Range = 1 << 21
/**< whether this is a range field (e.g., date, size)*/ /**< whether this is a range field (e.g., date, size)*/
Internal = 1 << 26
}; };
constexpr bool any_of(Flag some_flag) const{ constexpr bool any_of(Flag some_flag) const{
@ -164,6 +152,7 @@ struct Field {
is_normal_term(); } is_normal_term(); }
constexpr bool is_value() const { return any_of(Flag::Value); } constexpr bool is_value() const { return any_of(Flag::Value); }
constexpr bool is_internal() const { return any_of(Flag::Internal); }
constexpr bool is_contact() const { return any_of(Flag::Contact); } constexpr bool is_contact() const { return any_of(Flag::Contact); }
constexpr bool is_range() const { return any_of(Flag::Range); } constexpr bool is_range() const { return any_of(Flag::Range); }
@ -215,7 +204,6 @@ MU_ENABLE_BITOPS(Field::Flag);
static constexpr std::array<Field, Field::id_size()> static constexpr std::array<Field, Field::id_size()>
Fields = { Fields = {
{ {
// Bcc
{ {
Field::Id::Bcc, Field::Id::Bcc,
Field::Type::ContactList, Field::Type::ContactList,
@ -226,7 +214,6 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
// HTML Body
{ {
Field::Id::BodyHtml, Field::Id::BodyHtml,
Field::Type::String, Field::Type::String,
@ -234,19 +221,17 @@ static constexpr std::array<Field, Field::id_size()>
"Message html body", "Message html body",
{}, {},
{}, {},
{} Field::Flag::Internal
}, },
// Body
{ {
Field::Id::BodyText, Field::Id::BodyText,
Field::Type::String, Field::Type::String,
"body", "body",
"Message plain-text body", "Message plain-text body",
"body:capybara", // example "body:capybara",
'b', 'b',
Field::Flag::IndexableTerm, Field::Flag::IndexableTerm,
}, },
// Cc
{ {
Field::Id::Cc, Field::Id::Cc,
Field::Type::ContactList, Field::Type::ContactList,
@ -257,124 +242,6 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Contact | Field::Flag::Contact |
Field::Flag::Value Field::Flag::Value
}, },
// Embed
{
Field::Id::EmbeddedText,
Field::Type::String,
"embed",
"Embedded text",
"embed:war OR embed:peace",
'e',
Field::Flag::IndexableTerm
},
// File
{
Field::Id::File,
Field::Type::String,
"file",
"Attachment file name",
"file:/image\\.*.jpg/",
'j',
Field::Flag::NormalTerm
},
// From
{
Field::Id::From,
Field::Type::ContactList,
"from",
"Message sender",
"from:jimbo",
'f',
Field::Flag::Contact |
Field::Flag::Value
},
// Maildir
{
Field::Id::Maildir,
Field::Type::String,
"maildir",
"Maildir path for message",
"maildir:/private/archive",
'm',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
// MIME
{
Field::Id::Mime,
Field::Type::String,
"mime",
"Attachment MIME-type",
"mime:image/jpeg",
'y',
Field::Flag::NormalTerm
},
// Message-ID
{
Field::Id::MessageId,
Field::Type::String,
"msgid",
"Attachment MIME-type",
"msgid:abc@123",
'i',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
// Path
{
Field::Id::Path,
Field::Type::String,
"path",
"File system path to message",
{},
'l',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
// Subject
{
Field::Id::Subject,
Field::Type::String,
"subject",
"Message subject",
"subject:wombat",
's',
Field::Flag::Value |
Field::Flag::IndexableTerm
},
// To
{
Field::Id::To,
Field::Type::ContactList,
"to",
"Message recipient",
"to:flimflam@example.com",
't',
Field::Flag::Contact |
Field::Flag::Value
},
// References
{
Field::Id::References,
Field::Type::StringList,
"refs",
"Message references to other messages",
{},
'r',
Field::Flag::Value
},
// Tags
{
Field::Id::Tags,
Field::Type::StringList,
"tag",
"Message tags",
"tag:projectx",
'x',
Field::Flag::NormalTerm |
Field::Flag::Value
},
// Date
{ {
Field::Id::Date, Field::Id::Date,
Field::Type::TimeT, Field::Type::TimeT,
@ -385,7 +252,24 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Value | Field::Flag::Value |
Field::Flag::Range Field::Flag::Range
}, },
// Flags {
Field::Id::EmbeddedText,
Field::Type::String,
"embed",
"Embedded text",
"embed:war OR embed:peace",
'e',
Field::Flag::IndexableTerm
},
{
Field::Id::File,
Field::Type::String,
"file",
"Attachment file name",
"file:/image\\.*.jpg/",
'j',
Field::Flag::NormalTerm
},
{ {
Field::Id::Flags, Field::Id::Flags,
Field::Type::Integer, Field::Type::Integer,
@ -396,7 +280,75 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::NormalTerm | Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
// Priority {
Field::Id::From,
Field::Type::ContactList,
"from",
"Message sender",
"from:jimbo",
'f',
Field::Flag::Contact |
Field::Flag::Value
},
{
Field::Id::Maildir,
Field::Type::String,
"maildir",
"Maildir path for message",
"maildir:/private/archive",
'm',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
{
Field::Id::MailingList,
Field::Type::String,
"list",
"Mailing list (List-Id:)",
"list:mu-discuss.example.com",
'v',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
{
Field::Id::MessageId,
Field::Type::String,
"msgid",
"Attachment MIME-type",
"msgid:abc@123",
'i',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
{
Field::Id::Mime,
Field::Type::String,
"mime",
"Attachment MIME-type",
"mime:image/jpeg",
'y',
Field::Flag::NormalTerm
},
{
Field::Id::Modified,
Field::Type::TimeT,
"modified",
"Last modification time",
"modified:30m..now",
'k',
Field::Flag::Value |
Field::Flag::Range
},
{
Field::Id::Path,
Field::Type::String,
"path",
"File system path to message",
"path:/a/b/Maildir/cur/msg:2,S",
'l',
Field::Flag::BooleanTerm |
Field::Flag::Value
},
{ {
Field::Id::Priority, Field::Id::Priority,
Field::Type::Integer, Field::Type::Integer,
@ -407,7 +359,15 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::BooleanTerm | Field::Flag::BooleanTerm |
Field::Flag::Value Field::Flag::Value
}, },
// Size {
Field::Id::References,
Field::Type::StringList,
"refs",
"References to related messages",
{},
'r',
Field::Flag::Value
},
{ {
Field::Id::Size, Field::Id::Size,
Field::Type::ByteSize, Field::Type::ByteSize,
@ -418,18 +378,26 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::Value | Field::Flag::Value |
Field::Flag::Range Field::Flag::Range
}, },
// Mailing List
{ {
Field::Id::MailingList, Field::Id::Subject,
Field::Type::String, Field::Type::String,
"list", "subject",
"Mailing list (List-Id:)", "Message subject",
"list:mu-discuss.googlegroups.com", "subject:wombat",
'v', 's',
Field::Flag::BooleanTerm | Field::Flag::Value |
Field::Flag::IndexableTerm
},
{
Field::Id::Tags,
Field::Type::StringList,
"tag",
"Message tags",
"tag:projectx",
'x',
Field::Flag::NormalTerm |
Field::Flag::Value Field::Flag::Value
}, },
// ThreadId
{ {
Field::Id::ThreadId, Field::Id::ThreadId,
Field::Type::String, Field::Type::String,
@ -440,6 +408,16 @@ static constexpr std::array<Field, Field::id_size()>
Field::Flag::BooleanTerm | Field::Flag::BooleanTerm |
Field::Flag::Value Field::Flag::Value
}, },
{
Field::Id::To,
Field::Type::ContactList,
"to",
"Message recipient",
"to:flimflam@example.com",
't',
Field::Flag::Contact |
Field::Flag::Value
},
}}; }};
/* /*

View File

@ -596,6 +596,9 @@ fill_document(Message::Private& priv)
for (auto&& part: priv.parts) for (auto&& part: priv.parts)
doc.add(field.id, part.mime_type()); doc.add(field.id, part.mime_type());
break; break;
case Field::Id::Modified:
doc.add(field.id, priv.mtime);
break;
case Field::Id::Path: /* already */ case Field::Id::Path: /* already */
break; break;
case Field::Id::Priority: case Field::Id::Priority:
@ -708,8 +711,10 @@ Message::update_after_move(const std::string& new_path,
return Err(statbuf.error()); return Err(statbuf.error());
priv_->doc.add(Field::Id::Path, new_path); priv_->doc.add(Field::Id::Path, new_path);
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;

View File

@ -84,18 +84,29 @@ public:
/** /**
* Construct a message based on a Message::Document * Construct a message based on a Message::Document
* *
* @param doc * @param doc a Mu Document
* *
* @return a message or an error * @return a message or an error
*/ */
static Result<Message> make_from_document(Xapian::Document&& doc) try { static Result<Message> make_from_document(Mu::Document&& doc) try {
return Ok(Message{Mu::Document{std::move(doc)}}); return Ok(Message{std::move(doc)});
} catch (Error& err) { } catch (Error& err) {
return Err(err); return Err(err);
} catch (...) { } catch (...) {
return Err(Mu::Error(Error::Code::Message, "failed to create message")); return Err(Mu::Error(Error::Code::Message, "failed to create message"));
} }
/**
* Construct a message based on a Xapian::Document
*
* @param doc a xapian document
*
* @return a message or an error
*/
static Result<Message> make_from_document(Xapian::Document&& doc) noexcept {
return make_from_document(Mu::Document{std::move(doc)});
}
/** /**
* Construct a message from a string. This is mostly useful for testing. * Construct a message from a string. This is mostly useful for testing.
@ -216,12 +227,24 @@ public:
std::string mailing_list() const { return document().string_value(Field::Id::MailingList);} std::string mailing_list() const { return document().string_value(Field::Id::MailingList);}
/** /**
* get the message date/time (the Date: field) as time_t, using UTC * get the message date/time (the Date: field) as time_t
* *
* @return message date/time or 0 in case of error or if there * @return message date/time or 0 in case of error or if there
* is no such header. * is no such header.
*/ */
::time_t date() const { return static_cast<time_t>(document().integer_value(Field::Id::Date)); } ::time_t date() const {
return static_cast<::time_t>(document().integer_value(Field::Id::Date));
}
/**
* get the last modification or creation time for this message
*
* @return message date/time or 0 if unknown
*/
::time_t modified() const {
return static_cast<::time_t>(document().integer_value(Field::Id::Modified));
}
/** /**
* get the flags for this message. * get the flags for this message.

View File

@ -177,7 +177,7 @@ process_range(const std::string& field_str,
std::string u2 = upper; std::string u2 = upper;
constexpr auto upper_limit = std::numeric_limits<int64_t>::max(); constexpr auto upper_limit = std::numeric_limits<int64_t>::max();
if (field_opt->id == Field::Id::Date) { if (field_opt->id == Field::Id::Date || field_opt->id == Field::Id::Modified) {
l2 = to_lexnum(parse_date_time(lower, true).value_or(0)); l2 = to_lexnum(parse_date_time(lower, true).value_or(0));
u2 = to_lexnum(parse_date_time(upper, false).value_or(upper_limit)); u2 = to_lexnum(parse_date_time(upper, false).value_or(upper_limit));
} else if (field_opt->id == Field::Id::Size) { } else if (field_opt->id == Field::Id::Size) {
@ -420,7 +420,8 @@ Parser::Private::factor_2(Mu::Tokens& tokens, Node::Type& op, WarningVec& warnin
op = Node::Type::OpAnd; // implicit AND op = Node::Type::OpAnd; // implicit AND
break; break;
default: return empty(); default:
return empty();
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop