Files
mu4e/lib/message/mu-message.hh
Dirk-Jan C. Binnema 5d9bb72c0b message: augment cache-path() so it supports subparts
Take an optional index parameter for a *subpart*.

This is for the case where we save attachments from a message (in particular,
when forwarding). We can't save them in the same directory for the (rare) case
when there are multiple attachments with the same name. And we don't want to
uniquify the name, since that shows up in e.g. the forwarded file name.

This can be solved by saving each in their own indexed subdir.
2022-05-15 11:28:48 +03:00

451 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);
} catch (...) {
return Err(Mu::Error(Error::Code::Message, "failed to create message"));
}
/**
* Construct a message based on a Message::Document
*
* @param doc a Mu Document
*
* @return a message or an error
*/
static Result<Message> make_from_document(Mu::Document&& doc) try {
return Ok(Message{std::move(doc)});
} catch (Error& err) {
return Err(err);
} catch (...) {
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.
* @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);
} catch (...) {
return Err(Mu::Error(Error::Code::Message,
"failed to create message from text"));
}
/**
* 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 (ctime) for this message
*
* @return chnage 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;
Message(Document&& doc); // XXX: make private
private:
Message(const std::string& path, Options opts);
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__ */