t7x/src/common/utils/properties.cpp

178 lines
3.6 KiB
C++

#include "properties.hpp"
#include "finally.hpp"
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include "rapidjson/filereadstream.h"
#include "rapidjson/filewritestream.h"
#include "rapidjson/encodedstream.h"
#include "io.hpp"
#include "com.hpp"
#include "string.hpp"
namespace utils::properties
{
namespace
{
typedef rapidjson::EncodedOutputStream<rapidjson::UTF8<>, rapidjson::FileWriteStream> OutputStream;
typedef rapidjson::EncodedInputStream<rapidjson::UTF8<>, rapidjson::FileReadStream> InputStream;
std::filesystem::path get_properties_folder()
{
static auto props = get_appdata_path() / "user";
return props;
}
std::filesystem::path get_properties_file()
{
static auto props = []
{
auto path = std::filesystem::path("t7x/players/properties.json");
const auto legacy_path = get_properties_folder() / "properties.json";
if (io::file_exists(legacy_path) && !io::file_exists(path))
{
std::error_code e;
std::filesystem::copy(legacy_path, path, std::filesystem::copy_options::skip_existing, e);
}
return path;
}();
return props;
}
rapidjson::Document load_properties()
{
rapidjson::Document default_doc{};
default_doc.SetObject();
char read_buffer[256]{0}; // Raw buffer for reading
const std::wstring& props = get_properties_file();
FILE* fp;
auto err = _wfopen_s(&fp, props.data(), L"rb");
if (err || !fp)
{
return default_doc;
}
const auto _ = finally([&]
{
if (fp)
{
fclose(fp);
}
});
// This will handle the BOM
rapidjson::FileReadStream bis(fp, read_buffer, sizeof(read_buffer));
InputStream eis(bis);
rapidjson::Document doc{};
const rapidjson::ParseResult result = doc.ParseStream<rapidjson::kParseNoFlags, rapidjson::UTF8<>>(eis);
if (!result || !doc.IsObject())
{
return default_doc;
}
return doc;
}
void store_properties(const rapidjson::Document& doc)
{
char write_buffer[256]{0}; // Raw buffer for writing
const std::wstring& props = get_properties_file();
io::create_directory(get_properties_folder());
FILE* fp;
auto err = _wfopen_s(&fp, props.data(), L"wb");
if (err || !fp)
{
return;
}
const auto _ = finally([&]
{
if (fp)
{
fclose(fp);
}
});
rapidjson::FileWriteStream bos(fp, write_buffer, sizeof(write_buffer));
OutputStream eos(bos);
rapidjson::Writer writer(eos);
doc.Accept(writer);
}
}
std::filesystem::path get_appdata_path()
{
PWSTR path;
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path)))
{
throw std::runtime_error("Failed to read APPDATA path!");
}
auto _ = utils::finally([&path]
{
CoTaskMemFree(path);
});
static auto appdata = std::filesystem::path(path) / "t7x";
return appdata;
}
std::unique_lock<named_mutex> lock()
{
static named_mutex mutex{"t7x-properties-lock"};
return std::unique_lock{mutex};
}
std::optional<std::string> load(const std::string& name)
{
const auto _ = lock();
const auto doc = load_properties();
if (!doc.HasMember(name))
{
return {};
}
const auto& value = doc[name];
if (!value.IsString())
{
return {};
}
return {std::string{value.GetString()}};
}
void store(const std::string& name, const std::string& value)
{
const auto _ = lock();
auto doc = load_properties();
while (doc.HasMember(name))
{
doc.RemoveMember(name);
}
rapidjson::Value key{};
key.SetString(name, doc.GetAllocator());
rapidjson::Value member{};
member.SetString(value, doc.GetAllocator());
doc.AddMember(key, member, doc.GetAllocator());
store_properties(doc);
}
}