mu/lib/message/mu-message.hh

459 lines
11 KiB
C++

/*
** Copyright (C) 2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
**
** This program is free software; you can redistribute it and/or modify it
** under the terms of the GNU General Public License as published by the
** Free Software Foundation; either version 3, or (at your option) any
** later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software Foundation,
** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
**
*/
#ifndef MU_MESSAGE_HH__
#define MU_MESSAGE_HH__
#include <memory>
#include <string>
#include <vector>
#include "mu-contact.hh"
#include "mu-priority.hh"
#include "mu-flags.hh"
#include "mu-fields.hh"
#include "mu-document.hh"
#include "mu-message-part.hh"
#include <xapian.h>
#include "utils/mu-utils.hh"
#include "utils/mu-option.hh"
#include "utils/mu-result.hh"
#include "utils/mu-sexp.hh"
namespace Mu {
class Message {
public:
enum struct Options {
None = 0, /**< Defaults */
Decrypt = 1 << 0, /**< Attempt to decrypt */
RetrieveKeys = 1 << 1, /**< Auto-retrieve crypto keys (implies network
* access) */
};
/**
* Move CTOR
*
* @param some other message
*/
Message(Message&& other) noexcept;
/**
* operator=
*
* @param other move some object object
*
* @return
*/
Message& operator=(Message&& other) noexcept;
/**
* Construct a message based on a path
*
* @param path path to message
* @param opts options
*
* @return a message or an error
*/
static Result<Message> make_from_path(const std::string& path,
Options opts={}) try {
return Ok(Message{path,opts});
} catch (Error& err) {
return Err(err);
}
/* LCOV_EXCL_START */
catch (...) {
return Err(Mu::Error(Error::Code::Message,
"failed to create message from path"));
}
/* LCOV_EXCL_STOP */
/**
* Construct a message based on a Xapian::Document
*
* @param doc a Mu Document
*
* @return a message or an error
*/
static Result<Message> make_from_document(Xapian::Document&& doc) try {
return Ok(Message{std::move(doc)});
} catch (Error& err) {
return Err(err);
}
/* LCOV_EXCL_START */
catch (...) {
return Err(Mu::Error(Error::Code::Message,
"failed to create message from document"));
}
/* LCOV_EXCL_STOP */
/**
* Construct a message from a string. This is mostly useful for testing.
*
* @param text message text
* @param path path to message - optional; path does not have to exist.
* @param opts options
*
* @return a message or an error
*/
static Result<Message> make_from_text(const std::string& text,
const std::string& path={},
Options opts={}) try {
return Ok(Message{text, path, opts});
} catch (Error& err) {
return Err(err);
}
/* LCOV_EXCL_START */
catch (...) {
return Err(Mu::Error(Error::Code::Message,
"failed to create message from text"));
}
/* LCOV_EXCL_STOP */
/**
* DTOR
*/
~Message();
/**
* Get the document.
*
*
* @return document
*/
const Document& document() const;
/**
* Get the document-id, or 0 if non-existent.
*
* @return document id
*/
unsigned docid() const;
/**
* Get the file system path of this message
*
* @return the path of this Message or NULL in case of error.
* the returned string should *not* be modified or freed.
*/
std::string path() const { return document().string_value(Field::Id::Path); }
/**
* Get the sender (From:) of this message
*
* @return the sender(s) of this Message
*/
Contacts from() const { return document().contacts_value(Field::Id::From); }
/**
* Get the recipient(s) (To:) for this message
*
* @return recipients
*/
Contacts to() const { return document().contacts_value(Field::Id::To); }
/**
* Get the recipient(s) (Cc:) for this message
*
* @return recipients
*/
Contacts cc() const { return document().contacts_value(Field::Id::Cc); }
/**
* Get the recipient(s) (Bcc:) for this message
*
* @return recipients
*/
Contacts bcc() const { return document().contacts_value(Field::Id::Bcc); }
/**
* 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
*
* This is determined when _storing_ the message (which uses
* set_maildir())
*
* @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<void> set_maildir(const std::string& maildir);
/**
* Get the subject of this message
*
* @return the subject of this Message
*/
std::string subject() const { return document().string_value(Field::Id::Subject); }
/**
* Get the Message-Id of this message
*
* @return the Message-Id of this message (without the enclosing <>), or
* a fake message-id for messages that don't have them.
*
* For file-backed message, this fake message-id is based on a hash of the
* message contents. For non-file-backed (test) messages, some other value
* is concocted.
*/
std::string message_id() const { return document().string_value(Field::Id::MessageId);}
/**
* get the mailing list for a message, i.e. the mailing-list
* identifier in the List-Id header.
*
* @return the mailing list id for this message (without the enclosing <>)
* or NULL in case of error or if there is none.
*/
std::string mailing_list() const { return document().string_value(Field::Id::MailingList);}
/**
* get the message date/time (the Date: field) as time_t
*
* @return message date/time or 0 in case of error or if there
* is no such header.
*/
::time_t date() const {
return static_cast<::time_t>(document().integer_value(Field::Id::Date));
}
/**
* get the last change-time this message. For path/document-based
* messages this corresponds with the ctime of the underlying file; for
* the text-based ones (as used for testing) it is the creation time.
*
* @return last-change time or 0 if unknown
*/
::time_t changed() const {
return static_cast<::time_t>(document().integer_value(Field::Id::Changed));
}
/**
* get the flags for this message.
*
* @return the file/content flags
*/
Flags flags() const { return document().flags_value(); }
/**
* Update the flags for this message. This is useful for flags
* that can only be determined after the message has been created already,
* such as the 'personal' flag.
*
* @param flags new flags.
*/
void set_flags(Flags flags);
/**
* get the message priority for this message. The X-Priority, X-MSMailPriority,
* Importance and Precedence header are checked, in that order. if no known or
* explicit priority is set, Priority::Id::Normal is assumed
*
* @return the message priority
*/
Priority priority() const { return document().priority_value(); }
/**
* get the file size in bytes of this message
*
* @return the filesize
*/
size_t size() const { return static_cast<size_t>(document().integer_value(Field::Id::Size)); }
/**
* get the list of references (consisting of both the References and
* In-Reply-To fields), with the oldest first and the direct parent as
* the last one. Note, any reference (message-id) will appear at most
* once, duplicates are filtered out.
*
* @return a vec with the references for this msg.
*/
std::vector<std::string> references() const {
return document().string_vec_value(Field::Id::References);
}
/**
* get the list of tags (ie., X-Label)
*
* @param msg a valid MuMsg
*
* @return a list with the tags for this msg. Don't modify/free
*/
std::vector<std::string> tags() const {
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().cached_sexp();
}
/*
* Convert to Sexp
*/
/**
* Get the s-expression for this message. Stays valid as long
* as this message is.
*
* @return a Mu::Sexp::List representing the message.
*/
const Mu::Sexp::List& to_sexp_list() const;
Mu::Sexp to_sexp() const {
return Sexp::make_list(Sexp::List(to_sexp_list()));
}
/**
* Update the cached sexp for this message which is stored in the
* document. This should be done immediately before storing it in the
* database.
*
*/
void update_cached_sexp();
/*
* And some non-const message, for updating an existing
* message after a file-system move.
*
* @return Ok or an error.
*/
Result<void> 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()
*/
/**
* Get the text body
*
* @return text body
*/
Option<std::string> body_text() const;
/**
* Get the HTML body
*
* @return text body
*/
Option<std::string> body_html() const;
/**
* Get some message-header
*
* @param header_field name of the header
*
* @return the value (UTF-8), or Nothing.
*/
Option<std::string> header(const std::string& header_field) const;
/**
* Get all contacts for this message.
*
* @return contacts
*/
Contacts all_contacts() const;
/**
* Get information about MIME-parts in this message.
*
* @return mime-part info.
*/
using Part = MessagePart;
const std::vector<Part>& parts() const;
/**
* Get the path to a cche directory for this message, which
* is useful for temporarily saving attachments
*
* @param index optionally, create <cache-path>/<index> instead;
* this is useful for having part-specific subdirectories.
*
* @return path to a (created) cache directory, or an error.
*/
Result<std::string> cache_path(Option<size_t> index={}) const;
/**
* Load the GMime (file) message (for a database-backed message),
* if not already (but see @param reload).
*
* Affects cached-state only, so we still mark this as 'const'
*
* @param reload whether to force reloading (even if already)
*
* @return true if loading worked; false otherwise.
*/
bool load_mime_message(bool reload=false) const;
/**
* Clear the GMime message.
*
* Affects cached-state only, so we still mark this as 'const'
*/
void unload_mime_message() const;
/**
* Has a (file-base) GMime message been loaded?
*
*
* @return true or false
*/
bool has_mime_message() const;
struct Private;
/*
* Usually the make_ builders are better to create a message, but in
* some special cases, we need a heap-allocated message... */
Message(Xapian::Document&& xdoc);
Message(const std::string& path, Options opts);
private:
Message(const std::string& str, const std::string& path, Options opt);
std::unique_ptr<Private> priv_;
}; // Message
MU_ENABLE_BITOPS(Message::Options);
} // Mu
#endif /* MU_MESSAGE_HH__ */