Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PRE-WORK] VPN-5854 - Refactor storage management #8820

Merged
merged 16 commits into from
Dec 21, 2023
Merged
8 changes: 8 additions & 0 deletions src/cmake/shared-sources.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,14 @@ target_sources(shared-sources INTERFACE
${CMAKE_SOURCE_DIR}/src/rfc/rfc4291.h
${CMAKE_SOURCE_DIR}/src/rfc/rfc5735.cpp
${CMAKE_SOURCE_DIR}/src/rfc/rfc5735.h
${CMAKE_SOURCE_DIR}/src/settings/settinggroup.cpp
${CMAKE_SOURCE_DIR}/src/settings/settinggroup.h
${CMAKE_SOURCE_DIR}/src/settings/settingsbase.cpp
${CMAKE_SOURCE_DIR}/src/settings/settingsbase.h
${CMAKE_SOURCE_DIR}/src/settings/setting.cpp
${CMAKE_SOURCE_DIR}/src/settings/setting.h
${CMAKE_SOURCE_DIR}/src/settings/settingfactory.h
${CMAKE_SOURCE_DIR}/src/settings/settingfactory.cpp
${CMAKE_SOURCE_DIR}/src/settingsholder.cpp
${CMAKE_SOURCE_DIR}/src/settingsholder.h
${CMAKE_SOURCE_DIR}/src/signature.cpp
Expand Down
83 changes: 83 additions & 0 deletions src/settings/setting.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "settings/setting.h"

#include "leakdetector.h"
#include "settingsbase.h"

Setting::Setting(QObject* parent, const QString& key,
std::function<QVariant()> defaultValue, bool removeWhenReset,
bool sensitiveSetting)
: QObject(parent),
m_key(key),
m_defaultValue(defaultValue),
m_sensitiveSetting(sensitiveSetting),
m_removeWhenReset(removeWhenReset) {
MZ_COUNT_CTOR(Setting);
}

Setting::~Setting() { MZ_COUNT_DTOR(Setting); }

QVariant Setting::get() const {
auto value = SettingsBase::instance()->m_settings.value(m_key);

if (value.isNull()) {
value = m_defaultValue();
}

return value;
}

void Setting::set(QVariant value) const {
if (!isSet() || get() != value) {
SettingsBase::instance()->m_settings.setValue(m_key, value);
emit changed();
}
}

void Setting::reset() const {
if (!m_removeWhenReset) {
return;
}

remove();
}
brizental marked this conversation as resolved.
Show resolved Hide resolved

void Setting::remove() const {
if (!isSet()) {
return;
}

SettingsBase::instance()->m_settings.remove(m_key);
emit changed();
}

bool Setting::isSet() const {
return SettingsBase::instance()->m_settings.contains(m_key);
}

QString Setting::log() const {
if (!isSet()) {
return "";
}

QString logLine;
if (m_sensitiveSetting) {
logLine.append(QString("%1 -> <Sensitive>").arg(m_key));
} else {
logLine.append(QString("%1 -> ").arg(m_key));
QVariant value = get();
switch (value.typeId()) {
case QVariant::List:
case QVariant::StringList:
brizental marked this conversation as resolved.
Show resolved Hide resolved
logLine.append(QString("[%1]").arg(value.toStringList().join(",")));
break;
default:
logLine.append(value.toString());
}
}

return logLine;
}
106 changes: 106 additions & 0 deletions src/settings/setting.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef SETTING_H
#define SETTING_H

#include <QObject>
#include <QVariant>

/**
* @brief Represents a setting stored in the underlying QSettings storage.
*/
class Setting : public QObject {
Q_OBJECT

public:
~Setting();

QString key() const { return m_key; }

/**
* @brief Get the stored value for this setting. If not set, will return the
* default value.
*
* @return T
*/
Q_INVOKABLE QVariant get() const;
brizental marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Set a value for this setting.
*
* If the value is the same as the stored value, this is a no-op.
* If the metric was never set and the provided value is the default value,
* it will be set.
*
* @param value
*/
Q_INVOKABLE void set(QVariant value) const;

/**
* @brief Resets the value for this setting if removeWhenReset is true.
* Otherwise, this is no-op.
*
*/
void reset() const;

/**
* @brief Removes the stored value for this setting.
*
*/
void remove() const;

/**
* @brief Checks whether or not this setting has a stored value.
*
*/
Q_INVOKABLE bool isSet() const;

/**
* @brief Gets a string formatted for adding this setting to logs.
*
* The setting value will be redacted in case this is a sensitive setting.
*
* The log line is formatted like so:
* - key -> value
* - key -> <Sensitive>
*
* @return QString
*/
QString log() const;

signals:
/**
* @brief This signal is emmited whenever the underlying storage value related
* to the setting is changed.
*
* Check the documentation of the updater functions above to understand when
* the underlying value is changed or not.
*
*/
void changed() const;
brizental marked this conversation as resolved.
Show resolved Hide resolved

private:
Setting(QObject* parent, const QString& key,
std::function<QVariant()> defaultValue, bool removeWhenReset,
bool sensitiveSetting);

private:
QString m_key;

std::function<QVariant()> m_defaultValue;
brizental marked this conversation as resolved.
Show resolved Hide resolved

bool m_sensitiveSetting;
bool m_removeWhenReset;

brizental marked this conversation as resolved.
Show resolved Hide resolved
friend class SettingFactory;
friend class SettingGroup;

#ifdef UNIT_TEST
friend class TestSettingFactory;
friend class TestSettingGroup;
#endif
};

#endif // SETTING_H
35 changes: 35 additions & 0 deletions src/settings/settingfactory.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "settingfactory.h"

// static
Setting* SettingFactory::createOrGetSetting(
const QString& key, std::function<QVariant()> defaultValue,
bool removeWhenReset, bool sensitiveSetting) {
auto s = SettingsBase::getSetting(key);
if (s) {
Q_ASSERT(defaultValue() == s->m_defaultValue());
Q_ASSERT(removeWhenReset == s->m_removeWhenReset);
Q_ASSERT(sensitiveSetting == s->m_sensitiveSetting);

return s;
}

s = new Setting(
SettingsBase::instance(), key,
[defaultValue]() { return QVariant(defaultValue()); }, removeWhenReset,
sensitiveSetting);

SettingsBase::instance()->registerSetting(s);
return s;
}

// static
Setting* SettingFactory::createOrGetSetting(
const QString& key, std::function<std::nullptr_t()> defaultValue,
bool removeWhenReset, bool sensitiveSetting) {
return createOrGetSetting(
key, []() { return QVariant(); }, removeWhenReset, sensitiveSetting);
}
44 changes: 44 additions & 0 deletions src/settings/settingfactory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef SETTINGSFACTORY_H
#define SETTINGSFACTORY_H

#include "setting.h"
#include "settingsbase.h"

class SettingFactory {
public:
/**
* @brief Construct a new Setting object and register it with the
* SettingsBase.
*
* If another setting with the same key is already registered, no new setting
* is registered and a pointer to the existing setting is returned. This
* function will crash in debug mode in case the existing setting has a
* different configuration from the new setting.
*
* @param key A QSettings valid key.
* @param defaultValue The default value to be returned when this setting is
* not set. Default value will be null if unset.
* @param removeWhenReset Whether or not this setting should actually be
* removed when `reset` is called. Default is true.
* @param sensitiveSetting Whether or not this is a sensitive setting i.e. a
* setting that must not be logged in plain text. Default is false.
*/
static Setting* createOrGetSetting(
const QString& key,
std::function<QVariant()> defaultValue = []() { return QVariant(); },
bool removeWhenReset = true, bool sensitiveSetting = false);

static Setting* createOrGetSetting(
const QString& key, std::function<std::nullptr_t()> defaultValue,
bool removeWhenReset, bool sensitiveSetting);

private:
SettingFactory() = default;
~SettingFactory() = default;
};

#endif // SETTINGSFACTORY_H
93 changes: 93 additions & 0 deletions src/settings/settinggroup.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "settings/settinggroup.h"

#include "leakdetector.h"
#include "settingfactory.h"
#include "settingsbase.h"

SettingGroup::SettingGroup(const QString& groupKey, bool removeWhenReset,
bool sensitiveSetting, QStringList acceptedKeys)
: m_groupKey(groupKey),
m_sensitiveSetting(sensitiveSetting),
m_removeWhenReset(removeWhenReset),
m_acceptedKeys(acceptedKeys) {
MZ_COUNT_CTOR(Setting);

// Group settings are dynamic, therefore we need to load from memory all
// settings that exist under this group prefix in order to emit change signals
// when they change.
auto sb = SettingsBase::instance();
sb->m_settings.beginGroup(m_groupKey);
QStringList keys = sb->m_settings.allKeys();
sb->m_settings.endGroup();
foreach (const QString& key, keys) {
addSetting(key);
}
}

SettingGroup::~SettingGroup() { MZ_COUNT_DTOR(Setting); }

void SettingGroup::addSetting(const QString& key) {
auto settingKey = getSettingKey(key);
auto setting = SettingFactory::createOrGetSetting(
settingKey, []() { return nullptr; }, m_removeWhenReset,
m_sensitiveSetting);

// Make this setting's getter available for QML.
this->setProperty(key.toUtf8().constData(), get(key));
brizental marked this conversation as resolved.
Show resolved Hide resolved

// Emit the group change signal for this group when the setting is changed.
connect(setting, &Setting::changed, [key, this]() {
this->setProperty(key.toUtf8().constData(), get(key));
emit changed();
});
}

bool SettingGroup::mayRecord(const QString& key) {
if (m_acceptedKeys.count() == 0) {
return true;
}

return m_acceptedKeys.contains(key);
}

QVariant SettingGroup::get(const QString& key) const {
auto setting = SettingsBase::getSetting(getSettingKey(key));
if (!setting) {
return QVariant();
}

return setting->get();
}

void SettingGroup::set(const QString& key, QVariant value) {
if (!mayRecord(key)) {
return;
}

auto fullKey = getSettingKey(key);
auto setting = SettingsBase::getSetting(fullKey);
if (!setting) {
addSetting(key);
setting = SettingsBase::getSetting(fullKey);
}

setting->set(value);
}

void SettingGroup::remove() {
auto sb = SettingsBase::instance();
sb->m_settings.beginGroup(m_groupKey);
QStringList keys = sb->m_settings.allKeys();
sb->m_settings.remove("");
sb->m_settings.endGroup();

foreach (const QString& key, keys) {
auto setting = SettingsBase::getSetting(key);
Q_ASSERT(setting);
setting->changed();
}
}
brizental marked this conversation as resolved.
Show resolved Hide resolved
Loading