Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
246 lines
5.6 KiB
C++
246 lines
5.6 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "base/options.h"
|
|
|
|
#include "base/call_delayed.h"
|
|
#include "base/variant.h"
|
|
#include "base/debug_log.h"
|
|
|
|
#include <QtCore/QJsonDocument>
|
|
#include <QtCore/QJsonObject>
|
|
#include <QtCore/QJsonValue>
|
|
#include <QtCore/QFile>
|
|
|
|
namespace base::options {
|
|
namespace details {
|
|
namespace {
|
|
|
|
constexpr auto kSaveDelay = crl::time(1000);
|
|
|
|
bool WriteScheduled/* = false*/;
|
|
|
|
struct Compare {
|
|
bool operator()(const char *a, const char *b) const noexcept {
|
|
return strcmp(a, b) < 0;
|
|
}
|
|
};
|
|
using MapType = base::flat_map<const char*, not_null<BasicOption*>, Compare>;
|
|
|
|
[[nodiscard]] MapType &Map() {
|
|
static auto result = MapType();
|
|
return result;
|
|
}
|
|
|
|
[[nodiscard]] QString &LocalPath() {
|
|
static auto result = QString();
|
|
return result;
|
|
}
|
|
|
|
void Read(const QString &path) {
|
|
auto file = QFile(path);
|
|
if (!file.exists()) {
|
|
return;
|
|
} else if (!file.open(QIODevice::ReadOnly)) {
|
|
LOG(("Experimental: Error opening file from '%1'.").arg(path));
|
|
return;
|
|
}
|
|
auto error = QJsonParseError();
|
|
const auto parsed = QJsonDocument::fromJson(file.readAll(), &error);
|
|
if (error.error != QJsonParseError::NoError) {
|
|
LOG(("Experimental: Error parsing json from '%1': %2 (%3)"
|
|
).arg(path
|
|
).arg(error.error
|
|
).arg(error.errorString()));
|
|
return;
|
|
} else if (!parsed.isObject()) {
|
|
LOG(("Experimental: Non object in json from '%1'.").arg(path));
|
|
return;
|
|
}
|
|
auto &map = Map();
|
|
const auto values = parsed.object();
|
|
for (auto i = values.begin(); i != values.end(); ++i) {
|
|
const auto key = i.key().toLatin1() + char(0);
|
|
const auto j = map.find(key.data());
|
|
if (j == end(map)) {
|
|
LOG(("Experimental: Unknown option '%1'.").arg(i.key()));
|
|
continue;
|
|
}
|
|
const auto value = *i;
|
|
v::match(j->second->value(), [&](const auto ¤t) {
|
|
using T = std::remove_cvref_t<decltype(current)>;
|
|
if constexpr (std::is_same_v<T, bool>) {
|
|
if (value.isBool()) {
|
|
j->second->set(value.toBool());
|
|
return;
|
|
}
|
|
} else if constexpr (std::is_same_v<T, int>) {
|
|
if (value.isDouble()) {
|
|
j->second->set(value.toInt());
|
|
return;
|
|
}
|
|
} else if constexpr (std::is_same_v<T, QString>) {
|
|
if (value.isString()) {
|
|
j->second->set(value.toString());
|
|
return;
|
|
}
|
|
} else {
|
|
static_assert(unsupported_type(T()));
|
|
}
|
|
LOG(("Experimental: Wrong option value type for '%1'."
|
|
).arg(i.key()));
|
|
});
|
|
}
|
|
}
|
|
|
|
void Write() {
|
|
const auto &path = LocalPath();
|
|
if (!WriteScheduled || path.isEmpty()) {
|
|
return;
|
|
}
|
|
WriteScheduled = false;
|
|
|
|
auto map = QJsonObject();
|
|
for (const auto &[name, option] : Map()) {
|
|
const auto &value = option->value();
|
|
if (value != option->defaultValue()) {
|
|
map.insert(name, v::match(value, [](const auto ¤t) {
|
|
using T = std::remove_cvref_t<decltype(current)>;
|
|
if constexpr (std::is_same_v<T, bool>
|
|
|| std::is_same_v<T, int>
|
|
|| std::is_same_v<T, QString>) {
|
|
return QJsonValue(current);
|
|
} else {
|
|
static_assert(unsupported_type(T()));
|
|
}
|
|
}));
|
|
}
|
|
}
|
|
if (map.isEmpty()) {
|
|
QFile(path).remove();
|
|
} else if (auto file = QFile(path); file.open(QIODevice::WriteOnly)) {
|
|
file.write(QJsonDocument(map).toJson(QJsonDocument::Indented));
|
|
} else {
|
|
LOG(("Experimental: Could not write '%1'.").arg(path));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BasicOption::BasicOption(
|
|
const char id[],
|
|
const char name[],
|
|
const char description[],
|
|
ValueType defaultValue,
|
|
Scope scope,
|
|
bool restartRequired)
|
|
: _value(defaultValue)
|
|
, _defaultValue(std::move(defaultValue))
|
|
, _id(QString::fromUtf8(id))
|
|
, _name(QString::fromUtf8(name))
|
|
, _description(QString::fromUtf8(description))
|
|
, _scope(scope)
|
|
, _restartRequired(restartRequired) {
|
|
const auto [i, ok] = Map().emplace(id, this);
|
|
|
|
Ensures(ok);
|
|
}
|
|
|
|
void BasicOption::set(ValueType value) {
|
|
Expects(value.index() == _value.index());
|
|
|
|
_value = std::move(value);
|
|
if (!WriteScheduled && !LocalPath().isEmpty()) {
|
|
WriteScheduled = true;
|
|
call_delayed(kSaveDelay, [] { Write(); });
|
|
}
|
|
}
|
|
|
|
const ValueType &BasicOption::value() const {
|
|
return _value;
|
|
}
|
|
|
|
const ValueType &BasicOption::defaultValue() const {
|
|
return _defaultValue;
|
|
}
|
|
|
|
const QString &BasicOption::id() const {
|
|
return _id;
|
|
}
|
|
|
|
const QString &BasicOption::name() const {
|
|
return _name;
|
|
}
|
|
|
|
const QString &BasicOption::description() const {
|
|
return _description;
|
|
}
|
|
|
|
bool BasicOption::relevant() const {
|
|
const auto scopeFn = std::get_if<ScopeFn>(&_scope);
|
|
if (scopeFn) {
|
|
return (*scopeFn)();
|
|
}
|
|
|
|
const auto scopeFlags = v::get<ScopeFlags>(_scope);
|
|
#ifdef Q_OS_WIN
|
|
return scopeFlags & windows;
|
|
#elif defined Q_OS_MAC // Q_OS_WIN
|
|
return scopeFlags & macos;
|
|
#else // Q_OS_MAC || Q_OS_WIN
|
|
return scopeFlags & linux;
|
|
#endif // Q_OS_MAC || Q_OS_WIN
|
|
}
|
|
|
|
bool BasicOption::restartRequired() const {
|
|
return _restartRequired;
|
|
}
|
|
|
|
Scope BasicOption::scope() const {
|
|
return _scope;
|
|
}
|
|
|
|
BasicOption &Lookup(const char id[]) {
|
|
const auto i = Map().find(id);
|
|
|
|
Ensures(i != end(Map()));
|
|
return *i->second;
|
|
}
|
|
|
|
} // namespace details
|
|
|
|
bool changed() {
|
|
for (const auto &[name, option] : details::Map()) {
|
|
if (option->value() != option->defaultValue()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void reset() {
|
|
for (const auto &[name, option] : details::Map()) {
|
|
if (option->value() != option->defaultValue()) {
|
|
option->set(option->defaultValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
void init(const QString &path) {
|
|
Expects(details::LocalPath().isEmpty());
|
|
|
|
if (!path.isEmpty()) {
|
|
details::Read(path);
|
|
|
|
details::LocalPath() = path;
|
|
static const auto guard = gsl::finally([] {
|
|
details::Write();
|
|
});
|
|
}
|
|
}
|
|
|
|
} // namespace base::options
|