mumble-voip_mumble/src/mumble/JSONSerialization.cpp

439 lines
14 KiB
C++

// Copyright 2022-2023 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "JSONSerialization.h"
#include "Cert.h"
#include "SettingsMacros.h"
template< typename T, bool isEnum > struct SaveValueConverter {
static const T &getValue(const T &value) { return value; }
static void loadValue(const nlohmann::json &json, T &variable) { json.get_to(variable); }
};
template< typename T > using has_enum_conversion_function = decltype(enumToString(std::declval< T >()));
template< typename T > struct SaveValueConverter< T, true > {
static const char *getValue(const T &enumeration) {
#if BOOST_VERSION >= 106700
// Use the "detection idiom" to provide a proper error message in case there is no conversion function for the
// given enum.
// boost::is_detected is only available since Boost 1.67 and since we only use it to create a better error
// message, we can simply not make use of it, if earlier versions are used.
static_assert(boost::is_detected< has_enum_conversion_function, T >::value,
"No viable enum-to-name function found");
#endif
return enumToString(enumeration);
}
static void loadValue(const nlohmann::json &json, T &variable) {
variable = stringToEnum< T >(json.get< std::string >());
}
};
template< typename T > void save(nlohmann::json &json, const SettingsKey &key, const T &value) {
json[key] = SaveValueConverter< T, std::is_enum< T >::value >::getValue(value);
}
template< typename T > void save(nlohmann::json &json, const char *category, const SettingsKey &key, const T &value) {
if (!json.contains(category)) {
json[category] = nlohmann::json::object();
}
save(json[category], key, value);
}
template< typename T, bool isArray > struct CopyHelper {
static void copy(T &dest, const T &source) { dest = source; }
};
template< typename T > struct CopyHelper< T, true > {
template< size_t N > static constexpr std::size_t size(const T (&)[N]) { return N; }
static constexpr std::size_t size(const T &array) { return sizeof(array) / sizeof(*array); }
static void copy(T &dest, const T &source) {
static_assert(size(dest) == size(dest), "Can only copy arrays of equal size");
std::copy(source, source + size(source), dest);
}
};
template< typename T >
void load(const nlohmann::json &json, const SettingsKey &key, T &variable, const T &defaultValue,
bool useDefault = true) {
nlohmann::json valueEntry = key.selectFrom(json);
if (!valueEntry.is_null()) {
SaveValueConverter< T, std::is_enum< T >::value >::loadValue(valueEntry, variable);
} else if (useDefault) {
CopyHelper< T, std::is_array< T >::value >::copy(variable, defaultValue);
}
}
template< typename T >
void load(const nlohmann::json &json, const char *category, const SettingsKey &key, T &variable, const T &defaultValue,
bool useDefault = true) {
if (!json.contains(category)) {
if (useDefault) {
CopyHelper< T, std::is_array< T >::value >::copy(variable, defaultValue);
}
return;
}
const nlohmann::json &categoryJSON = json.at(category);
load(categoryJSON, key, variable, defaultValue, useDefault);
}
void to_json(nlohmann::json &j, const Settings &settings) {
j[SettingsKeys::SETTINGS_VERSION_KEY] = 1;
const Settings defaultValues;
#define PROCESS(category, key, variable) \
if (settings.variable != defaultValues.variable) { \
save(j, #category, SettingsKeys::key, settings.variable); \
}
PROCESS_ALL_SETTINGS
#undef PROCESS
if (settings.qlShortcuts != defaultValues.qlShortcuts) {
// We only remove server specific shortcuts since they are saved in the DB.
// We should consider moving those out of the DB into the JSON settings.
QList< Shortcut > genericShortcuts;
for (const Shortcut &shortcut : settings.qlShortcuts) {
if (!shortcut.isServerSpecific()) {
genericShortcuts.push_back(shortcut);
}
}
j["shortcuts"]["defined"] = genericShortcuts;
}
if (settings.qmMessages != defaultValues.qmMessages) {
j["messages"]["traits"] = settings.qmMessages;
}
if (settings.qmMessageSounds != defaultValues.qmMessageSounds) {
j["messages"]["sounds"] = settings.qmMessageSounds;
}
if (settings.qmLCDDevices != defaultValues.qmLCDDevices) {
j["lcd"]["devices"] = settings.qmLCDDevices;
}
if (settings.qhPluginSettings != defaultValues.qhPluginSettings) {
j["plugins"] = settings.qhPluginSettings;
}
if (settings.os != defaultValues.os) {
j["overlay"] = settings.os;
}
j[SettingsKeys::CERTIFICATE_KEY] = CertWizard::exportCert(settings.kpCertificate);
// Save whether Mumble has quit regularly (in contrast to having crashed). This flag is set right before saving the
// settings because Mumble is shut down (regularly). In contrast, when saving the settings because the user has made
// changes in the UI, this will be false. Thus when loading the settings again and seeing this flag is false, it
// means Mumble never got to perform its regular shutdown routine, meaning that it is likely that it has crashed
// before.
j[SettingsKeys::MUMBLE_QUIT_NORMALLY_KEY] = settings.mumbleQuitNormally;
}
void migrateSettings(nlohmann::json &json, int settingsVersion) {
// Perform conversions required to transform the given JSON into the format applicable to be read out by the most
// recent standards
// Check if the old ask_on_quit key exists and the new one does not exist within the json file
if (json.contains("ask_on_quit")
&& (!json.contains(static_cast< const char * >(SettingsKeys::QUIT_BEHAVIOR_KEY)))) {
if (!json.at("ask_on_quit").get< bool >()) {
json[SettingsKeys::QUIT_BEHAVIOR_KEY] = QuitBehavior::ALWAYS_QUIT;
} else {
json[SettingsKeys::QUIT_BEHAVIOR_KEY] = QuitBehavior::ALWAYS_ASK;
}
}
if (json.contains("play_transmit_cue")
&& (!json.contains(static_cast< const char * >(SettingsKeys::TRANSMIT_CUE_WHEN_PTT_KEY)))) {
json[SettingsKeys::TRANSMIT_CUE_WHEN_PTT_KEY] = json.at("play_transmit_cue").get< bool >();
}
(void) settingsVersion;
}
void from_json(const nlohmann::json &j, Settings &settings) {
int settingsVersion = j.at(SettingsKeys::SETTINGS_VERSION_KEY).get< int >();
// Copy since we might have to make modifications
nlohmann::json json = j;
migrateSettings(json, settingsVersion);
#define PROCESS(category, key, variable) \
load(json, #category, SettingsKeys::key, settings.variable, settings.variable, true);
PROCESS_ALL_SETTINGS
#undef PROCESS
if (json.contains("shortcuts") && json.at("shortcuts").contains("defined")) {
settings.qlShortcuts = json.at("shortcuts").at("defined");
}
if (json.contains("messages") && json.at("messages").contains("traits")) {
settings.qmMessages = json.at("messages").at("traits");
}
if (json.contains("messages") && json.at("messages").contains("sounds")) {
settings.qmMessageSounds = json.at("messages").at("sounds");
}
if (json.contains("lcd") && json.at("lcd").contains("devices")) {
settings.qmLCDDevices = json.at("lcd").at("devices");
}
if (json.contains("plugins")) {
settings.qhPluginSettings = json.at("plugins");
}
if (json.contains("overlay")) {
settings.os = json.at("overlay");
}
if (json.contains(static_cast< const char * >(SettingsKeys::CERTIFICATE_KEY))) {
settings.kpCertificate = CertWizard::importCert(json.at(SettingsKeys::CERTIFICATE_KEY));
}
if (json.contains(static_cast< const char * >(SettingsKeys::MUMBLE_QUIT_NORMALLY_KEY))) {
settings.mumbleQuitNormally = json.at(SettingsKeys::MUMBLE_QUIT_NORMALLY_KEY);
}
#ifndef USE_RENAMENOISE
if (settings.noiseCancelMode == Settings::NoiseCancelRNN || settings.noiseCancelMode == Settings::NoiseCancelBoth) {
// Use Speex instead as this Mumble build was built without support for ReNameNoise
settings.noiseCancelMode = Settings::NoiseCancelSpeex;
}
#endif
}
void to_json(nlohmann::json &j, const OverlaySettings &settings) {
#define PROCESS(category, key, variable) save(j, SettingsKeys::key, settings.variable);
PROCESS_ALL_OVERLAY_SETTINGS
#undef PROCESS
}
void from_json(const nlohmann::json &j, OverlaySettings &settings) {
#define PROCESS(category, key, variable) load(j, SettingsKeys::key, settings.variable, settings.variable, true);
PROCESS_ALL_OVERLAY_SETTINGS
#undef PROCESS
}
void to_json(nlohmann::json &j, const QString &string) {
j = string.toStdString();
}
void from_json(const nlohmann::json &j, QString &string) {
string = QString::fromStdString(j.get< std::string >());
}
void to_json(nlohmann::json &j, const QVariant &variant) {
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream << variant;
j = data.toBase64().toStdString();
}
void from_json(const nlohmann::json &j, QVariant &variant) {
QByteArray data = QByteArray::fromBase64(QByteArray::fromStdString(j.get< std::string >()));
QDataStream stream(&data, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_0);
stream >> variant;
}
void to_json(nlohmann::json &j, const QPoint &point) {
j = nlohmann::json::object({
{ "x", point.x() },
{ "y", point.y() },
});
}
void from_json(const nlohmann::json &j, QPoint &point) {
point.setX(j.at("x").get< int >());
point.setY(j.at("y").get< int >());
}
void to_json(nlohmann::json &j, const ShortcutTarget &target) {
bool link = false;
bool subchannel = false;
if (target.bCurrentSelection) {
j["current_selection"] = target.bCurrentSelection;
link = true;
subchannel = true;
} else if (target.bUsers) {
j["users"] = target.qlUsers;
} else {
j["channel_id"] = target.iChannel;
j["group"] = target.qsGroup;
link = true;
subchannel = true;
}
if (link) {
j["include_links"] = target.bLinks;
}
if (subchannel) {
j["include_subchannel"] = target.bChildren;
}
}
void from_json(const nlohmann::json &j, ShortcutTarget &target) {
bool link = false;
bool subchannel = false;
if (j.contains("current_selection")) {
j.at("current_selection").get_to(target.bCurrentSelection);
link = true;
subchannel = true;
} else if (j.contains("users")) {
j.at("users").get_to(target.qlUsers);
target.bUsers = true;
} else {
j.at("channel_id").get_to(target.iChannel);
j.at("group").get_to(target.qsGroup);
link = true;
subchannel = true;
}
if (link) {
j.at("link").get_to(target.bLinks);
}
if (subchannel) {
j.at("include_subchannel").get_to(target.bChildren);
}
}
void to_json(nlohmann::json &j, const Shortcut &shortcut) {
j["suppress"] = shortcut.bSuppress;
j["index"] = shortcut.iIndex;
j["buttons"] = shortcut.qlButtons;
j["data"] = shortcut.qvData;
}
void from_json(const nlohmann::json &j, Shortcut &shortcut) {
j.at("suppress").get_to(shortcut.bSuppress);
j.at("index").get_to(shortcut.iIndex);
j.at("buttons").get_to(shortcut.qlButtons);
j.at("data").get_to(shortcut.qvData);
}
void to_json(nlohmann::json &j, const PluginSetting &setting) {
j["path"] = setting.path;
j["enabled"] = setting.enabled;
j["positional_data_enabled"] = setting.positionalDataEnabled;
j["keyboard_monitoring_allowed"] = setting.allowKeyboardMonitoring;
}
void from_json(const nlohmann::json &j, PluginSetting &setting) {
j.at("path").get_to(setting.path);
j.at("enabled").get_to(setting.enabled);
j.at("positional_data_enabled").get_to(setting.positionalDataEnabled);
j.at("keyboard_monitoring_allowed").get_to(setting.allowKeyboardMonitoring);
}
void to_json(nlohmann::json &j, const QByteArray &byteArray) {
j = byteArray.toBase64().toStdString();
}
void from_json(const nlohmann::json &j, QByteArray &byteArray) {
byteArray = QByteArray::fromBase64(QByteArray::fromStdString(j.get< std::string >()));
}
void to_json(nlohmann::json &j, const QColor &color) {
j["red"] = color.red();
j["green"] = color.green();
j["blue"] = color.blue();
j["alpha"] = color.alpha();
}
void from_json(const nlohmann::json &j, QColor &color) {
color.setRed(j.at("red").get< decltype(color.red()) >());
color.setGreen(j.at("green").get< decltype(color.green()) >());
color.setBlue(j.at("blue").get< decltype(color.blue()) >());
color.setAlpha(j.at("alpha").get< decltype(color.alpha()) >());
}
void to_json(nlohmann::json &j, const QFont &font) {
j = font.toString();
}
void from_json(const nlohmann::json &j, QFont &font) {
font.fromString(j.get< QString >());
}
void to_json(nlohmann::json &j, const QRect &rect) {
j["x"] = rect.x();
j["y"] = rect.y();
j["width"] = rect.width();
j["height"] = rect.height();
}
void from_json(const nlohmann::json &j, QRect &rect) {
rect.setX(j.at("x").get< int >());
rect.setY(j.at("y").get< int >());
rect.setWidth(j.at("width").get< int >());
rect.setHeight(j.at("height").get< int >());
}
void to_json(nlohmann::json &j, const QRectF &rect) {
j["x"] = rect.x();
j["y"] = rect.y();
j["width"] = rect.width();
j["height"] = rect.height();
}
void from_json(const nlohmann::json &j, QRectF &rect) {
rect.setX(j.at("x").get< qreal >());
rect.setY(j.at("y").get< qreal >());
rect.setWidth(j.at("width").get< qreal >());
rect.setHeight(j.at("height").get< qreal >());
}
void to_json(nlohmann::json &j, const QSize &size) {
j["width"] = size.width();
j["height"] = size.height();
}
void from_json(const nlohmann::json &j, QSize &size) {
size.setWidth(j.at("width").get< int >());
size.setHeight(j.at("height").get< int >());
}
void to_json(nlohmann::json &j, const QSizeF &size) {
j["width"] = size.width();
j["height"] = size.height();
}
void from_json(const nlohmann::json &j, QSizeF &size) {
size.setWidth(j.at("width").get< qreal >());
size.setHeight(j.at("height").get< qreal >());
}