478 lines
12 KiB
C++
478 lines
12 KiB
C++
/*
|
|
** Copyright (C) 2023-2026 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_CONFIG_HH__
|
|
#define MU_CONFIG_HH__
|
|
|
|
#include <cstdint>
|
|
#include <cinttypes>
|
|
#include <string_view>
|
|
#include <string>
|
|
#include <array>
|
|
#include <vector>
|
|
#include <variant>
|
|
#include <unordered_map>
|
|
|
|
#include "mu-xapian-db.hh"
|
|
|
|
#include <glib.h>
|
|
#include <gmime/gmime.h>
|
|
|
|
#include <utils/mu-utils.hh>
|
|
#include <utils/mu-result.hh>
|
|
#include <utils/mu-option.hh>
|
|
|
|
#include <config.h>
|
|
|
|
namespace Mu {
|
|
|
|
struct Property {
|
|
enum struct Id {
|
|
// store properties, i.e. properties associated with
|
|
// some store object (and store in the database), either
|
|
// at init time or updated during runtime.
|
|
BatchSize, /**< Xapian batch-size */
|
|
Contacts, /**< Cache of contact information */
|
|
Created, /**< Time of creation */
|
|
IgnoredAddresses, /**< Email addresses ignored for the
|
|
contacts-cache */
|
|
Labels, /**< Serialized label information. */
|
|
LastChange, /**< Time of last change */
|
|
LastIndex, /**< Time of last index */
|
|
MaxMessageSize, /**< Maximum message size (in bytes) */
|
|
PersonalAddresses, /**< List of personal e-mail addresses */
|
|
RootMaildir, /**< Root maildir path */
|
|
SchemaVersion, /**< Xapian DB schema version */
|
|
NgramsEnabled, /**< Support ngrams for indexing & querying
|
|
* for e.g. CJK languages */
|
|
// build/system properties
|
|
MuVersion, /**< Mu version */
|
|
XapianVersion, /**< Xapian runtime version */
|
|
GlibVersion, /**< GLib version */
|
|
GmimeVersion, /**< GMime version */
|
|
ScmEnabled, /**< Built with SCM (Guile) support */
|
|
LanguageEnabled /**< Built with language-detection support */
|
|
};
|
|
|
|
enum struct Flags {
|
|
None = 0, /**< Nothing in particular */
|
|
ReadOnly = 1 << 0, /**< Property is read-only for external use
|
|
* (but can change from within the store) */
|
|
Configurable = 1 << 1, /**< A user-configurable parameter; name
|
|
* starts with 'conf-' */
|
|
Internal = 1 << 2, /**< Mu-internal field */
|
|
Runtime = 1 << 3, /**< May change at runtime */
|
|
System = 1 << 4, /**< System property; read-only */
|
|
};
|
|
enum struct Type {
|
|
Boolean, /**< Some boolean value */
|
|
Number, /**< Some number */
|
|
Timestamp, /**< Timestamp number */
|
|
Path, /**< Path string */
|
|
String, /**< A string */
|
|
StringList, /**< A list of strings */
|
|
};
|
|
|
|
using Value = std::variant<int64_t, std::string, std::vector<std::string> >;
|
|
using DefaultValue = std::string(*)(); /**< Function that returns a string_view */
|
|
|
|
Id id;
|
|
Type type;
|
|
Flags flags;
|
|
std::string_view name;
|
|
DefaultValue default_val;
|
|
std::string_view description;
|
|
};
|
|
|
|
MU_ENABLE_BITOPS(Property::Flags);
|
|
|
|
class Config {
|
|
public:
|
|
using Id = Property::Id;
|
|
using Type = Property::Type;
|
|
using Flags = Property::Flags;
|
|
using Value = Property::Value;
|
|
|
|
/**
|
|
* Properties specific to the store.
|
|
*/
|
|
static constexpr auto properties = std::to_array<Property>({
|
|
// store-properties
|
|
{
|
|
Id::BatchSize,
|
|
Type::Number,
|
|
Flags::Configurable,
|
|
"batch-size",
|
|
[]()->std::string {return "50000";},
|
|
"Maximum number of changes in a database transaction"
|
|
},
|
|
{
|
|
Id::Contacts,
|
|
Type::String,
|
|
Flags::Internal,
|
|
"contacts",
|
|
{},
|
|
"Serialized contact information"
|
|
},
|
|
{
|
|
Id::Created,
|
|
Type::Timestamp,
|
|
Flags::ReadOnly,
|
|
MetadataIface::created_key,
|
|
{},
|
|
"Database creation time"
|
|
},
|
|
{
|
|
Id::IgnoredAddresses,
|
|
Type::StringList,
|
|
Flags::Configurable,
|
|
"ignored-addresses",
|
|
{},
|
|
"E-mail addresses ignored for the contacts-cache, "
|
|
"literal or /regexp/"
|
|
},
|
|
{
|
|
Id::Labels,
|
|
Type::String,
|
|
Flags::Internal,
|
|
"labels",
|
|
{},
|
|
"Serialized labels information"
|
|
},
|
|
|
|
{
|
|
Id::LastChange,
|
|
Type::Timestamp,
|
|
Flags::ReadOnly | Flags::Runtime,
|
|
MetadataIface::last_change_key,
|
|
{},
|
|
"Time when last change occurred"
|
|
},
|
|
{
|
|
Id::LastIndex,
|
|
Type::Timestamp,
|
|
Flags::ReadOnly | Flags::Runtime,
|
|
"last-index",
|
|
{},
|
|
"Time when last indexing operation was completed"
|
|
},
|
|
{
|
|
Id::MaxMessageSize,
|
|
Type::Number,
|
|
Flags::Configurable,
|
|
"max-message-size",
|
|
[]()->std::string {return "100000000";}, // default max: 100M bytes
|
|
"Maximum message size (in bytes); bigger messages are skipped"
|
|
},
|
|
{
|
|
Id::PersonalAddresses,
|
|
Type::StringList,
|
|
Flags::Configurable,
|
|
"personal-addresses",
|
|
{},
|
|
"Personal e-mail addresses, literal or /regexp/"
|
|
},
|
|
{
|
|
Id::RootMaildir,
|
|
Type::Path,
|
|
Flags::ReadOnly,
|
|
"root-maildir",
|
|
{},
|
|
"Absolute path of the top of the Maildir tree"
|
|
},
|
|
{
|
|
Id::SchemaVersion,
|
|
Type::Number,
|
|
Flags::ReadOnly,
|
|
"schema-version",
|
|
{},
|
|
"Version of the Xapian database schema"
|
|
},
|
|
{
|
|
Id::NgramsEnabled,
|
|
Type::Boolean,
|
|
Flags::Configurable,
|
|
"ngrams-enabled",
|
|
{},
|
|
"Support n-grams for working with CJK and other languages"
|
|
},
|
|
|
|
// system properties
|
|
{
|
|
Id::MuVersion,
|
|
Type::String,
|
|
Flags::System,
|
|
"mu-version",
|
|
[]()->std::string {return VERSION;},
|
|
"mu version string"
|
|
},
|
|
{
|
|
Id::XapianVersion,
|
|
Type::String,
|
|
Flags::System,
|
|
"xapian-version",
|
|
[]()->std::string {return Xapian::version_string();},
|
|
"Xapian runtime version string"
|
|
},
|
|
{
|
|
Id::GlibVersion,
|
|
Type::String,
|
|
Flags::System,
|
|
"glib-version",
|
|
[]()->std::string{return mu_format("{}.{}.{}", glib_major_version, glib_minor_version,
|
|
glib_micro_version);},
|
|
"GLib runtime version string"
|
|
},
|
|
{
|
|
Id::GmimeVersion,
|
|
Type::String,
|
|
Flags::System,
|
|
"gmime-version",
|
|
[]()->std::string {return mu_format("{}.{}.{}", gmime_major_version, gmime_minor_version,
|
|
gmime_micro_version);},
|
|
"GMime runtime version string"
|
|
},
|
|
{
|
|
Id::ScmEnabled,
|
|
Type::Boolean,
|
|
Flags::System,
|
|
"scm-enabled",
|
|
[]()->std::string { return mu_format("{}", BUILD_SCM); },
|
|
"Is SCM/Guile supported?"
|
|
},
|
|
{
|
|
Id::LanguageEnabled,
|
|
Type::Boolean,
|
|
Flags::System,
|
|
"language-enabled",
|
|
[]()->std::string { return mu_format("{}", HAVE_CLD2); },
|
|
"Is language detection/searching supported?"
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Construct a new Config object.
|
|
*
|
|
* @param db The config-store (database); must stay valid for the
|
|
* lifetime of this config.
|
|
*/
|
|
Config(MetadataIface& cstore): cstore_{cstore}{}
|
|
|
|
/**
|
|
* Get the property by its id
|
|
*
|
|
* @param id a property id
|
|
*
|
|
* @return the property
|
|
*/
|
|
template <Id ID>
|
|
constexpr static const Property& property() {
|
|
return properties[static_cast<size_t>(ID)];
|
|
}
|
|
|
|
/**
|
|
* Get a Property by its name.
|
|
*
|
|
* @param name The name
|
|
*
|
|
* @return the property or Nothing if not found
|
|
*/
|
|
static Option<const Property&> property(const std::string& name) {
|
|
const auto pname{std::string_view(name.data(), name.size())};
|
|
for (auto&& prop: properties)
|
|
if (prop.name == pname)
|
|
return prop;
|
|
return Nothing;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the property value decoded based on the type
|
|
*
|
|
* @param str the raw string value
|
|
*
|
|
* @return the value
|
|
*/
|
|
template<Type type>
|
|
static auto decode(const std::string& str) {
|
|
if constexpr (type == Type::Number)
|
|
return static_cast<size_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
|
else if constexpr (type == Type::Boolean)
|
|
return static_cast<bool>(str.empty() ? false :
|
|
std::atol(str.c_str()) != 0);
|
|
else if constexpr (type == Type::Timestamp)
|
|
return static_cast<int64_t>(str.empty() ? 0 : std::atoll(str.c_str()));
|
|
else if constexpr (type == Type::Path || type == Type::String)
|
|
return str;
|
|
else if constexpr (type == Type::StringList)
|
|
return split(str, SepaChar1);
|
|
throw std::logic_error("invalid type");
|
|
}
|
|
|
|
/**
|
|
* Get default value for the property, or Nothing if it does not have one.
|
|
*
|
|
* @param prop some property
|
|
*
|
|
* @return string or nothing
|
|
*/
|
|
static Option<std::string> default_value(const Property& prop) {
|
|
if (!prop.default_val)
|
|
return Nothing;
|
|
else
|
|
return prop.default_val();
|
|
}
|
|
|
|
/**
|
|
* Get the raw string-value for some property
|
|
*
|
|
* @param prop some property
|
|
*
|
|
* @return a string
|
|
*/
|
|
std::string as_raw_string(const Property& prop) const {
|
|
// system properties are _not_ stored in the db.
|
|
if (any_of(prop.flags & Property::Flags::System)) {
|
|
return default_value(prop).value_or("");
|
|
} else {
|
|
if (const auto str = cstore_.metadata(std::string{prop.name}); !str.empty())
|
|
return str;
|
|
else
|
|
return default_value(prop).value_or("");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the string-value for some property (for display)
|
|
*
|
|
* @param prop some property
|
|
*
|
|
* @return a string
|
|
*/
|
|
std::string as_display_string(const Property& prop) const {
|
|
// system properties are _not_ stored in the db.
|
|
const auto str{as_raw_string(prop)};
|
|
switch (prop.type) {
|
|
case Type::Boolean:
|
|
return decode<Type::Boolean>(str) ? "yes" : "no";
|
|
case Type::StringList:
|
|
return mu_format("{}", mu_join(decode<Type::StringList>(str), ", "));
|
|
case Type::Timestamp: {
|
|
const auto t{decode<Type::Timestamp>(str)};
|
|
return t == 0 ? "never" : mu_format("{:%c}", mu_time(t));
|
|
}
|
|
default:
|
|
return str;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the property value of the correct type
|
|
*
|
|
* @param prop_id a property id
|
|
*
|
|
* @return the value or Nothing
|
|
*/
|
|
template<Id ID>
|
|
auto get() const {
|
|
constexpr auto& prop{property<ID>()};
|
|
return decode<prop.type>(as_raw_string(prop));
|
|
}
|
|
|
|
/**
|
|
* Set a new value for some property
|
|
*
|
|
* @param prop_id property-id
|
|
* @param val the new value (of the correct type)
|
|
*
|
|
* @return Ok() or some error
|
|
*/
|
|
template<Id ID, typename T>
|
|
Result<void> set(const T& val) {
|
|
constexpr auto&& prop{property<ID>()};
|
|
if (any_of(prop.flags & Property::Flags::System))
|
|
return Err(Error::Code::AccessDenied, "cannot write system property");
|
|
if (read_only())
|
|
return Err(Error::Code::AccessDenied, "cannot write to read-only db");
|
|
|
|
const auto strval = std::invoke([&]{
|
|
if constexpr (prop.type == Type::Number || prop.type == Type::Timestamp)
|
|
return mu_format("{}", static_cast<int64_t>(val));
|
|
if constexpr (prop.type == Type::Boolean)
|
|
return val ? "1" : "0";
|
|
else if constexpr (prop.type == Type::Path || prop.type == Type::String)
|
|
return std::string{val};
|
|
else if constexpr (prop.type == Type::StringList)
|
|
return join(val, SepaChar1);
|
|
else
|
|
throw std::logic_error("invalid prop " + std::string{prop.name});
|
|
});
|
|
|
|
cstore_.set_metadata(std::string{prop.name}, strval);
|
|
return Ok();
|
|
}
|
|
|
|
/**
|
|
* Set a new value for some property; unlike set(), merely log a
|
|
* warning if that fails. For callers that cannot meaningfully
|
|
* handle the error.
|
|
*
|
|
* @param prop_id property-id
|
|
* @param val the new value (of the correct type)
|
|
*/
|
|
template<Id ID, typename T>
|
|
void checked_set(const T& val) {
|
|
if (const auto res{set<ID>(val)}; !res)
|
|
mu_warning("failed to set config property '{}': {}",
|
|
property<ID>().name, res.error());
|
|
}
|
|
|
|
/**
|
|
* Is this a read-only Config?
|
|
*
|
|
*
|
|
* @return true or false
|
|
*/
|
|
bool read_only() const { return cstore_.read_only();};
|
|
|
|
/**
|
|
* Import configurable settings to some other MetadataIface
|
|
*
|
|
* @param target some other metadata interface
|
|
*/
|
|
void import_configurable(const Config& src) const {
|
|
for (auto&& prop: properties) {
|
|
if (any_of(prop.flags & Flags::Configurable)) {
|
|
const auto&& key{std::string{prop.name}};
|
|
if (const auto& val{src.cstore_.metadata(key)}; !val.empty())
|
|
cstore_.set_metadata(key, std::string{val});
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
MetadataIface& cstore_;
|
|
};
|
|
|
|
|
|
} // namespace Mu
|
|
|
|
#endif /* MU_CONFIG_DB_HH__ */
|