Initial loading of JSON-based settings

This commit is contained in:
Dan Paulat 2021-10-24 13:13:45 -05:00
parent 28ea12cbfe
commit 1c0140fc98
8 changed files with 561 additions and 38 deletions

View file

@ -1,5 +1,6 @@
#include <scwx/qt/main/main_window.hpp>
#include <scwx/qt/manager/resource_manager.hpp>
#include <scwx/qt/manager/settings_manager.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
@ -10,6 +11,9 @@ int main(int argc, char* argv[])
boost::log::core::get()->set_filter(boost::log::trivial::severity >=
boost::log::trivial::debug);
QCoreApplication::setApplicationName("Supercell Wx");
scwx::qt::manager::SettingsManager::Initialize();
scwx::qt::manager::ResourceManager::PreLoad();
QApplication a(argc, argv);

View file

@ -0,0 +1,116 @@
#include <scwx/qt/manager/settings_manager.hpp>
#include <scwx/qt/util/json.hpp>
#include <filesystem>
#include <fstream>
#include <QDir>
#include <QStandardPaths>
#include <boost/log/trivial.hpp>
namespace scwx
{
namespace qt
{
namespace manager
{
namespace SettingsManager
{
static const std::string logPrefix_ = "[scwx::qt::manager::settings_manager] ";
static std::shared_ptr<settings::GeneralSettings> generalSettings_ = nullptr;
static boost::json::value ConvertSettingsToJson();
static void GenerateDefaultSettings();
static bool LoadSettings(const boost::json::object& settingsJson);
bool Initialize()
{
std::string appDataPath {
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)
.toStdString()};
if (!std::filesystem::is_directory(appDataPath))
{
if (!std::filesystem::create_directories(appDataPath))
{
BOOST_LOG_TRIVIAL(error)
<< logPrefix_ << "Unable to create application data directory: \""
<< appDataPath << "\"";
return false;
}
}
std::string settingsPath {appDataPath + "/settings.json"};
ReadSettings(settingsPath);
return true;
}
void ReadSettings(const std::string& settingsPath)
{
boost::json::value settingsJson = nullptr;
if (std::filesystem::exists(settingsPath))
{
settingsJson = util::json::ReadJsonFile(settingsPath);
}
if (settingsJson == nullptr || !settingsJson.is_object())
{
GenerateDefaultSettings();
settingsJson = ConvertSettingsToJson();
util::json::WriteJsonFile(settingsPath, settingsJson);
}
else
{
bool jsonDirty = LoadSettings(settingsJson.as_object());
if (jsonDirty)
{
settingsJson = ConvertSettingsToJson();
util::json::WriteJsonFile(settingsPath, settingsJson);
}
};
}
std::shared_ptr<settings::GeneralSettings> general_settings()
{
return generalSettings_;
}
static boost::json::value ConvertSettingsToJson()
{
boost::json::object settingsJson;
settingsJson["general"] = generalSettings_->ToJson();
return settingsJson;
}
static void GenerateDefaultSettings()
{
BOOST_LOG_TRIVIAL(info) << logPrefix_ << "Generating default settings";
generalSettings_ = settings::GeneralSettings::Create();
}
static bool LoadSettings(const boost::json::object& settingsJson)
{
BOOST_LOG_TRIVIAL(info) << logPrefix_ << "Loading settings";
bool jsonDirty = false;
generalSettings_ = settings::GeneralSettings::Load(
settingsJson.if_contains("general"), jsonDirty);
return jsonDirty;
}
} // namespace SettingsManager
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,22 @@
#pragma once
#include <scwx/qt/settings/general_settings.hpp>
namespace scwx
{
namespace qt
{
namespace manager
{
namespace SettingsManager
{
bool Initialize();
void ReadSettings(const std::string& settingsPath);
std::shared_ptr<settings::GeneralSettings> general_settings();
} // namespace SettingsManager
} // namespace manager
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,98 @@
#include <scwx/qt/settings/general_settings.hpp>
#include <scwx/qt/util/json.hpp>
#include <boost/log/trivial.hpp>
namespace scwx
{
namespace qt
{
namespace settings
{
static const std::string logPrefix_ = "[scwx::qt::settings::general_settings] ";
static const std::string& DEFAULT_DEFAULT_RADAR_SITE = "KLSX";
class GeneralSettingsImpl
{
public:
explicit GeneralSettingsImpl() {}
~GeneralSettingsImpl() {}
void SetDefaults() { defaultRadarSite_ = DEFAULT_DEFAULT_RADAR_SITE; }
std::string defaultRadarSite_;
};
GeneralSettings::GeneralSettings() : p(std::make_unique<GeneralSettingsImpl>())
{
}
GeneralSettings::~GeneralSettings() = default;
GeneralSettings::GeneralSettings(GeneralSettings&&) noexcept = default;
GeneralSettings&
GeneralSettings::operator=(GeneralSettings&&) noexcept = default;
const std::string& GeneralSettings::default_radar_site()
{
return p->defaultRadarSite_;
}
boost::json::value GeneralSettings::ToJson()
{
boost::json::object json;
json["default_radar_site"] = p->defaultRadarSite_;
return json;
}
std::shared_ptr<GeneralSettings> GeneralSettings::Create()
{
std::shared_ptr<GeneralSettings> generalSettings =
std::make_shared<GeneralSettings>();
generalSettings->p->SetDefaults();
return generalSettings;
}
std::shared_ptr<GeneralSettings>
GeneralSettings::Load(const boost::json::value* json, bool& jsonDirty)
{
std::shared_ptr<GeneralSettings> generalSettings =
std::make_shared<GeneralSettings>();
if (json != nullptr && json->is_object())
{
jsonDirty |=
!util::json::FromJsonString(json->as_object(),
"default_radar_site",
generalSettings->p->defaultRadarSite_,
DEFAULT_DEFAULT_RADAR_SITE);
}
else
{
if (json == nullptr)
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Key is not present, resetting to defaults";
}
else if (!json->is_object())
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << "Invalid json, resetting to defaults";
}
generalSettings->p->SetDefaults();
jsonDirty = true;
}
return generalSettings;
}
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,43 @@
#pragma once
#include <memory>
#include <string>
#include <boost/json.hpp>
namespace scwx
{
namespace qt
{
namespace settings
{
class GeneralSettingsImpl;
class GeneralSettings
{
public:
explicit GeneralSettings();
~GeneralSettings();
GeneralSettings(const GeneralSettings&) = delete;
GeneralSettings& operator=(const GeneralSettings&) = delete;
GeneralSettings(GeneralSettings&&) noexcept;
GeneralSettings& operator=(GeneralSettings&&) noexcept;
const std::string& default_radar_site();
boost::json::value ToJson();
static std::shared_ptr<GeneralSettings> Create();
static std::shared_ptr<GeneralSettings> Load(const boost::json::value* json,
bool& jsonDirty);
private:
std::unique_ptr<GeneralSettingsImpl> p;
};
} // namespace settings
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,193 @@
#include <scwx/qt/util/json.hpp>
#include <fstream>
#include <boost/log/trivial.hpp>
namespace scwx
{
namespace qt
{
namespace util
{
namespace json
{
static const std::string logPrefix_ = "[scwx::qt::util::json] ";
/* Adapted from:
* https://www.boost.org/doc/libs/1_77_0/libs/json/doc/html/json/examples.html#json.examples.pretty
*
* Copyright (c) 2019, 2020 Vinnie Falco
* Copyright (c) 2020 Krystian Stasiowski
* Distributed under the Boost Software License, Version 1.0. (See
* http://www.boost.org/LICENSE_1_0.txt)
*/
static void PrettyPrintJson(std::ostream& os,
boost::json::value const& jv,
std::string* indent = nullptr);
bool FromJsonString(const boost::json::object& json,
const std::string& key,
std::string& value,
const std::string& defaultValue)
{
const boost::json::value* jv = json.if_contains(key);
bool found = false;
if (jv != nullptr)
{
if (jv->is_string())
{
value = boost::json::value_to<std::string>(*jv);
found = true;
}
else
{
BOOST_LOG_TRIVIAL(warning)
<< logPrefix_ << key
<< " is not a string, setting to default: " << defaultValue;
value = defaultValue;
}
}
else
{
BOOST_LOG_TRIVIAL(debug)
<< logPrefix_ << key
<< " is not present, setting to default: " << defaultValue;
value = defaultValue;
}
return found;
}
boost::json::value ReadJsonFile(const std::string& path)
{
std::ifstream ifs {path};
std::string line;
boost::json::stream_parser p;
boost::json::error_code ec;
while (std::getline(ifs, line))
{
p.write(line, ec);
if (ec)
{
BOOST_LOG_TRIVIAL(warning) << logPrefix_ << ec.message();
return nullptr;
}
}
p.finish(ec);
if (ec)
{
BOOST_LOG_TRIVIAL(warning) << logPrefix_ << ec.message();
return nullptr;
}
return p.release();
}
void WriteJsonFile(const std::string& path,
const boost::json::value& json,
bool prettyPrint)
{
std::ofstream ofs {path};
if (prettyPrint)
{
PrettyPrintJson(ofs, json);
}
else
{
ofs << json;
}
ofs.close();
}
static void PrettyPrintJson(std::ostream& os,
boost::json::value const& jv,
std::string* indent)
{
std::string indent_;
if (!indent)
indent = &indent_;
switch (jv.kind())
{
case boost::json::kind::object:
{
os << "{\n";
indent->append(4, ' ');
auto const& obj = jv.get_object();
if (!obj.empty())
{
auto it = obj.begin();
for (;;)
{
os << *indent << boost::json::serialize(it->key()) << " : ";
PrettyPrintJson(os, it->value(), indent);
if (++it == obj.end())
break;
os << ",\n";
}
}
os << "\n";
indent->resize(indent->size() - 4);
os << *indent << "}";
break;
}
case boost::json::kind::array:
{
os << "[\n";
indent->append(4, ' ');
auto const& arr = jv.get_array();
if (!arr.empty())
{
auto it = arr.begin();
for (;;)
{
os << *indent;
PrettyPrintJson(os, *it, indent);
if (++it == arr.end())
break;
os << ",\n";
}
}
os << "\n";
indent->resize(indent->size() - 4);
os << *indent << "]";
break;
}
case boost::json::kind::string:
{
os << boost::json::serialize(jv.get_string());
break;
}
case boost::json::kind::uint64: os << jv.get_uint64(); break;
case boost::json::kind::int64: os << jv.get_int64(); break;
case boost::json::kind::double_: os << jv.get_double(); break;
case boost::json::kind::bool_:
if (jv.get_bool())
os << "true";
else
os << "false";
break;
case boost::json::kind::null: os << "null"; break;
}
if (indent->empty())
os << "\n";
}
} // namespace json
} // namespace util
} // namespace qt
} // namespace scwx

View file

@ -0,0 +1,27 @@
#pragma once
#include <boost/json.hpp>
namespace scwx
{
namespace qt
{
namespace util
{
namespace json
{
bool FromJsonString(const boost::json::object& json,
const std::string& key,
std::string& value,
const std::string& defaultValue);
boost::json::value ReadJsonFile(const std::string& path);
void WriteJsonFile(const std::string& path,
const boost::json::value& json,
bool prettyPrint = true);
} // namespace json
} // namespace util
} // namespace qt
} // namespace scwx