mirror of https://github.com/djcb/mu.git
message: improve API; improve extract_tags
This commit is contained in:
parent
9a8741f0dd
commit
55113c6d5c
|
@ -24,8 +24,8 @@
|
||||||
#include "mu-maildir.hh"
|
#include "mu-maildir.hh"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <regex>
|
||||||
#include <utils/mu-util.h>
|
#include <utils/mu-util.h>
|
||||||
#include <utils/mu-utils.hh>
|
#include <utils/mu-utils.hh>
|
||||||
#include <utils/mu-error.hh>
|
#include <utils/mu-error.hh>
|
||||||
|
@ -43,8 +43,7 @@
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
|
|
||||||
struct Message::Private {
|
struct Message::Private {
|
||||||
Private(Message::Options options=Message::Options::None):
|
Private(Message::Options options): opts{options} {}
|
||||||
opts{options} {}
|
|
||||||
|
|
||||||
Message::Options opts;
|
Message::Options opts;
|
||||||
Document doc;
|
Document doc;
|
||||||
|
@ -52,7 +51,6 @@ struct Message::Private {
|
||||||
|
|
||||||
Flags flags{};
|
Flags flags{};
|
||||||
Option<std::string> mailing_list;
|
Option<std::string> mailing_list;
|
||||||
std::vector<std::string> references;
|
|
||||||
std::vector<Part> parts;
|
std::vector<Part> parts;
|
||||||
|
|
||||||
::time_t mtime{};
|
::time_t mtime{};
|
||||||
|
@ -92,11 +90,9 @@ get_statbuf(const std::string& path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Message::Message(Message::Options opts, const std::string& path,
|
Message::Message(const std::string& path, Message::Options opts):
|
||||||
const std::string& mdir):
|
|
||||||
priv_{std::make_unique<Private>(opts)}
|
priv_{std::make_unique<Private>(opts)}
|
||||||
{
|
{
|
||||||
|
|
||||||
const auto statbuf{get_statbuf(path)};
|
const auto statbuf{get_statbuf(path)};
|
||||||
if (!statbuf)
|
if (!statbuf)
|
||||||
throw statbuf.error();
|
throw statbuf.error();
|
||||||
|
@ -113,17 +109,14 @@ Message::Message(Message::Options opts, const std::string& path,
|
||||||
if (xpath)
|
if (xpath)
|
||||||
priv_->doc.add(Field::Id::Path, std::move(xpath.value()));
|
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<int64_t>(statbuf->st_size));
|
priv_->doc.add(Field::Id::Size, static_cast<int64_t>(statbuf->st_size));
|
||||||
|
|
||||||
// rest of the fields
|
// rest of the fields
|
||||||
fill_document(*priv_);
|
fill_document(*priv_);
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Message(Message::Options opts, const std::string& text, const std::string& path,
|
Message::Message(const std::string& text, const std::string& path,
|
||||||
const std::string& mdir):
|
Message::Options opts):
|
||||||
priv_{std::make_unique<Private>(opts)}
|
priv_{std::make_unique<Private>(opts)}
|
||||||
{
|
{
|
||||||
if (!path.empty()) {
|
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()));
|
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<int64_t>(text.size()));
|
priv_->doc.add(Field::Id::Size, static_cast<int64_t>(text.size()));
|
||||||
|
|
||||||
init_gmime();
|
init_gmime();
|
||||||
|
@ -162,7 +152,7 @@ Message::operator=(Message&& other) noexcept
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::Message(Document&& doc):
|
Message::Message(Document&& doc):
|
||||||
priv_{std::make_unique<Private>()}
|
priv_{std::make_unique<Private>(Message::Options::None)}
|
||||||
{
|
{
|
||||||
priv_->doc = std::move(doc);
|
priv_->doc = std::move(doc);
|
||||||
}
|
}
|
||||||
|
@ -176,6 +166,28 @@ Message::document() const
|
||||||
return priv_->doc;
|
return priv_->doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<void>
|
||||||
|
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
|
bool
|
||||||
Message::load_mime_message(bool reload) const
|
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 */
|
/* see: http://does-not-exist.org/mail-archives/mutt-dev/msg08249.html */
|
||||||
static std::vector<std::string>
|
static std::vector<std::string>
|
||||||
get_tags(const MimeMessage& mime_msg)
|
extract_tags(const MimeMessage& mime_msg)
|
||||||
{
|
{
|
||||||
constexpr std::array<std::pair<const char*, char>, 3> tag_headers = {{
|
constexpr std::array<std::pair<const char*, char>, 3> tag_headers = {{
|
||||||
{"X-Label", ' '}, {"X-Keywords", ','}, {"Keywords", ' '}
|
{"X-Label", ' '}, {"X-Keywords", ','}, {"Keywords", ' '}
|
||||||
}};
|
}};
|
||||||
|
static const auto strip_rx{std::regex("^\\s+| +$|( )\\s+")};
|
||||||
|
|
||||||
std::vector<std::string> tags;
|
std::vector<std::string> tags;
|
||||||
seq_for_each(tag_headers, [&](auto&& item) {
|
seq_for_each(tag_headers, [&](auto&& item) {
|
||||||
if (auto&& hdr{mime_msg.header(item.first)}; hdr) {
|
if (auto&& hdr = mime_msg.header(item.first); hdr) {
|
||||||
|
for (auto&& item : split(*hdr, item.second)) {
|
||||||
auto lst = split(*hdr, item.second);
|
tags.emplace_back(
|
||||||
tags.reserve(tags.size() + lst.size());
|
std::regex_replace(item, strip_rx, "$1"));
|
||||||
tags.insert(tags.end(), lst.begin(), lst.end());
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -589,7 +602,8 @@ fill_document(Message::Private& priv)
|
||||||
doc.add(get_priority(mime_msg));
|
doc.add(get_priority(mime_msg));
|
||||||
break;
|
break;
|
||||||
case Field::Id::References:
|
case Field::Id::References:
|
||||||
doc.add(field.id, refs);
|
if (!refs.empty())
|
||||||
|
doc.add(field.id, refs);
|
||||||
break;
|
break;
|
||||||
case Field::Id::Size: /* already */
|
case Field::Id::Size: /* already */
|
||||||
break;
|
break;
|
||||||
|
@ -597,7 +611,8 @@ fill_document(Message::Private& priv)
|
||||||
doc.add(field.id, mime_msg.subject());
|
doc.add(field.id, mime_msg.subject());
|
||||||
break;
|
break;
|
||||||
case Field::Id::Tags:
|
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;
|
break;
|
||||||
case Field::Id::ThreadId:
|
case Field::Id::ThreadId:
|
||||||
// either the oldest reference, or otherwise the message id
|
// either the oldest reference, or otherwise the message id
|
||||||
|
@ -606,9 +621,6 @@ fill_document(Message::Private& priv)
|
||||||
case Field::Id::To:
|
case Field::Id::To:
|
||||||
doc.add(field.id, mime_msg.addresses(AddrType::To));
|
doc.add(field.id, mime_msg.addresses(AddrType::To));
|
||||||
break;
|
break;
|
||||||
case Field::Id::Uid:
|
|
||||||
doc.add(field.id, path); // just a synonym for now.
|
|
||||||
break;
|
|
||||||
case Field::Id::_count_:
|
case Field::Id::_count_:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -686,9 +698,6 @@ Message::mtime() const
|
||||||
return priv_->mtime;
|
return priv_->mtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Result<void>
|
Result<void>
|
||||||
Message::update_after_move(const std::string& new_path,
|
Message::update_after_move(const std::string& new_path,
|
||||||
const std::string& new_maildir,
|
const std::string& new_maildir,
|
||||||
|
@ -699,8 +708,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::Maildir, new_maildir);
|
|
||||||
priv_->doc.add(new_flags);
|
priv_->doc.add(new_flags);
|
||||||
|
|
||||||
|
if (const auto res = set_maildir(new_maildir); !res)
|
||||||
|
return res;
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ public:
|
||||||
* access) */
|
* access) */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move CTOR
|
* Move CTOR
|
||||||
*
|
*
|
||||||
|
@ -66,22 +65,16 @@ public:
|
||||||
Message& operator=(Message&& other) noexcept;
|
Message& operator=(Message&& other) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a message based on a path. The maildir is optional; however
|
* Construct a message based on a path
|
||||||
* messages without maildir cannot be stored in the database
|
|
||||||
*
|
*
|
||||||
* @param opts options
|
|
||||||
* @param path path to message
|
* @param path path to message
|
||||||
* @param mdir the maildir for this message; i.e, if the path is
|
* @param opts options
|
||||||
* ~/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.
|
|
||||||
*
|
*
|
||||||
* @return a message or an error
|
* @return a message or an error
|
||||||
*/
|
*/
|
||||||
static Result<Message> make_from_path(Options opts,
|
static Result<Message> make_from_path(const std::string& path,
|
||||||
const std::string& path,
|
Options opts={}) try {
|
||||||
const std::string& mdir={}) try {
|
return Ok(Message{path,opts});
|
||||||
return Ok(Message{opts, path, mdir});
|
|
||||||
} catch (Error& err) {
|
} catch (Error& err) {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@ -103,23 +96,19 @@ public:
|
||||||
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 from a string. This is mostly useful for testing.
|
* Construct a message from a string. This is mostly useful for testing.
|
||||||
*
|
|
||||||
* @param opts options
|
|
||||||
* @param text message text
|
* @param text message text
|
||||||
* @param path path to message - optional; path does not have to exist.
|
* @param path path to message - optional; path does not have to exist.
|
||||||
* this is useful for testing.
|
* @param opts options
|
||||||
* @param mdir the maildir for this message; optional, useful for testing.
|
|
||||||
*
|
*
|
||||||
* @return a message or an error
|
* @return a message or an error
|
||||||
*/
|
*/
|
||||||
static Result<Message> make_from_text(Options opts,
|
static Result<Message> make_from_text(const std::string& text,
|
||||||
const std::string& text,
|
|
||||||
const std::string& path={},
|
const std::string& path={},
|
||||||
const std::string& mdir={}) try {
|
Options opts={}) try {
|
||||||
return Ok(Message{opts, text, path, mdir});
|
return Ok(Message{text, path, opts});
|
||||||
} catch (Error& err) {
|
} catch (Error& err) {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
|
@ -177,12 +166,27 @@ 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; ie, if the path is
|
* Get the maildir this message lives in; i.e., if the path is
|
||||||
* ~/Maildir/foo/bar/cur/msg, the maildir would 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
|
||||||
|
* constructed use make_from_document() have a non-empty value for
|
||||||
|
* 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); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<void> set_maildir(const std::string& maildir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the subject of this message
|
* Get the subject of this message
|
||||||
*
|
*
|
||||||
|
@ -262,9 +266,8 @@ public:
|
||||||
* @return a list with the tags for this msg. Don't modify/free
|
* @return a list with the tags for this msg. Don't modify/free
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> tags() const {
|
std::vector<std::string> tags() const {
|
||||||
return document().string_vec_value(Field::Id::References);
|
return document().string_vec_value(Field::Id::Tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert to Sexp
|
* Convert to Sexp
|
||||||
*/
|
*/
|
||||||
|
@ -288,8 +291,6 @@ public:
|
||||||
Result<void> update_after_move(const std::string& new_path,
|
Result<void> update_after_move(const std::string& new_path,
|
||||||
const std::string& new_maildir,
|
const std::string& new_maildir,
|
||||||
Flags new_flags);
|
Flags new_flags);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Below require a file-backed message, which is a relatively slow
|
* Below require a file-backed message, which is a relatively slow
|
||||||
* if there isn't one already; see load_mime_message()
|
* if there isn't one already; see load_mime_message()
|
||||||
|
@ -369,11 +370,12 @@ public:
|
||||||
bool has_mime_message() const;
|
bool has_mime_message() const;
|
||||||
|
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|
||||||
|
Message(Document&& doc); // XXX: make private
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Message(Options opts, const std::string& path, const std::string& mdir);
|
Message(const std::string& path, Options opts);
|
||||||
Message(Options opts, const std::string& str, const std::string& path,
|
Message(const std::string& str, const std::string& path, Options opt);
|
||||||
const std::string& mdir);
|
|
||||||
Message(Document&& doc);
|
|
||||||
|
|
||||||
std::unique_ptr<Private> priv_;
|
std::unique_ptr<Private> priv_;
|
||||||
}; // Message
|
}; // Message
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "gmime/gmime-message.h"
|
#include "gmime/gmime-message.h"
|
||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <regex>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
@ -71,13 +72,12 @@ Mu::init_gmime(void)
|
||||||
Option<std::string>
|
Option<std::string>
|
||||||
MimeObject::header(const std::string& hdr) const noexcept
|
MimeObject::header(const std::string& hdr) const noexcept
|
||||||
{
|
{
|
||||||
const char *val{g_mime_object_get_header(self(), hdr.c_str())};
|
if (auto val{g_mime_object_get_header(self(), hdr.c_str())}; !val)
|
||||||
if (!val)
|
|
||||||
return Nothing;
|
return Nothing;
|
||||||
if (!g_utf8_validate(val, -1, {}))
|
else if (!g_utf8_validate(val, -1, {}))
|
||||||
return utf8_clean(hdr);
|
return utf8_clean(val);
|
||||||
else
|
else
|
||||||
return val;
|
return std::string{val};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -328,7 +328,7 @@ MimeMessage::references() const noexcept
|
||||||
// is ref already in the list?
|
// is ref already in the list?
|
||||||
auto is_dup = [](auto&& seq, const std::string& ref) {
|
auto is_dup = [](auto&& seq, const std::string& ref) {
|
||||||
return seq_find_if(seq, [&](auto&& str) { return ref == str; })
|
return seq_find_if(seq, [&](auto&& str) { return ref == str; })
|
||||||
== seq.cend();
|
!= seq.cend();
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::string> refs;
|
std::vector<std::string> refs;
|
||||||
|
|
|
@ -62,14 +62,13 @@ statement you can use something like:
|
||||||
goto * instructions[pOp->opcode];
|
goto * instructions[pOp->opcode];
|
||||||
)";
|
)";
|
||||||
auto message{Message::make_from_text(
|
auto message{Message::make_from_text(
|
||||||
{}, test_message_1,
|
test_message_1,
|
||||||
"/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S",
|
"/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S")};
|
||||||
"/inbox")};
|
|
||||||
g_assert_true(!!message);
|
g_assert_true(!!message);
|
||||||
|
|
||||||
assert_equal(message->path(),
|
assert_equal(message->path(),
|
||||||
"/home/test/Maildir/inbox/cur/1649279256.107710_1.evergrey:2,S");
|
"/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());
|
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);
|
||||||
|
|
||||||
g_assert_true(message->path().empty());
|
g_assert_true(message->path().empty());
|
||||||
|
@ -330,10 +329,8 @@ Q46aYjxe0As6AP90bcAZ3dcn5RcTJaM0UhZssguawZ+tnriD3+5DPkMMCg==
|
||||||
g_assert_cmpuint(*imported, ==, 1);
|
g_assert_cmpuint(*imported, ==, 1);
|
||||||
|
|
||||||
auto message{Message::make_from_text(
|
auto message{Message::make_from_text(
|
||||||
{},
|
|
||||||
msgtext,
|
msgtext,
|
||||||
"/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS",
|
"/home/test/Maildir/inbox/cur/1649279777.107710_1.mindcrime:2,RS")};
|
||||||
"/inbox")};
|
|
||||||
g_assert_true(!!message);
|
g_assert_true(!!message);
|
||||||
|
|
||||||
g_assert_true(message->bcc().empty());
|
g_assert_true(message->bcc().empty());
|
||||||
|
@ -423,10 +420,8 @@ C0bdoCx44QVU8HaZ2x91h3GoM/0q5bqM/rvCauwbokiJgAUrznecNPY=
|
||||||
}
|
}
|
||||||
|
|
||||||
auto message{Message::make_from_text(
|
auto message{Message::make_from_text(
|
||||||
{},
|
|
||||||
msgtext,
|
msgtext,
|
||||||
"/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS",
|
"/home/test/Maildir/inbox/cur/1649279888.107710_1.mindcrime:2,FS")};
|
||||||
"/archive")};
|
|
||||||
g_assert_true(!!message);
|
g_assert_true(!!message);
|
||||||
g_assert_true(message->flags() == (Flags::Encrypted|Flags::Seen|Flags::Flagged));
|
g_assert_true(message->flags() == (Flags::Encrypted|Flags::Seen|Flags::Flagged));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue