Refactor preferences

This commit is contained in:
Tamás Bálint Misius 2023-01-18 15:07:51 +01:00
parent 220844521a
commit afda2826bf
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
26 changed files with 669 additions and 707 deletions

View File

@ -32,6 +32,7 @@
#include "X86KillDenormals.h"
#include "Misc.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
@ -97,8 +98,9 @@ int GetModifiers()
void LoadWindowPosition()
{
int savedWindowX = Client::Ref().GetPrefInteger("WindowX", INT_MAX);
int savedWindowY = Client::Ref().GetPrefInteger("WindowY", INT_MAX);
auto &prefs = GlobalPrefs::Ref();
int savedWindowX = prefs.Get("WindowX", INT_MAX);
int savedWindowY = prefs.Get("WindowY", INT_MAX);
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
@ -133,8 +135,9 @@ void SaveWindowPosition()
int borderTop, borderLeft;
SDL_GetWindowBordersSize(sdl_window, &borderTop, &borderLeft, nullptr, nullptr);
Client::Ref().SetPref("WindowX", x - borderLeft);
Client::Ref().SetPref("WindowY", y - borderTop);
auto &prefs = GlobalPrefs::Ref();
prefs.Set("WindowX", x - borderLeft);
prefs.Set("WindowY", y - borderTop);
}
void CalculateMousePosition(int *x, int *y)
@ -453,7 +456,7 @@ void LargeScreenDialog()
message << "\nTo undo this, hit Cancel. You can change this in settings at any time.";
if (!ConfirmPrompt::Blocking("Large screen detected", message.Build()))
{
Client::Ref().SetPref("Scale", 1);
GlobalPrefs::Ref().Set("Scale", 1);
engine->SetScale(1);
}
}
@ -603,8 +606,21 @@ int GuessBestScale()
return guess;
}
struct ExplicitSingletons
{
// These need to be listed in the order they are populated in main.
std::unique_ptr<GlobalPrefs> globalPrefs;
std::unique_ptr<Client> client;
http::RequestManagerPtr requestManager;
};
static std::unique_ptr<ExplicitSingletons> explicitSingletons;
int main(int argc, char * argv[])
{
atexit([]() {
explicitSingletons.reset();
});
explicitSingletons = std::make_unique<ExplicitSingletons>();
#ifdef WIN
if constexpr (DEBUG)
{
@ -710,14 +726,17 @@ int main(int argc, char * argv[])
SDL_free(ddir);
}
}
// We're now in the correct directory, time to get prefs.
explicitSingletons->globalPrefs = std::make_unique<GlobalPrefs>();
scale = Client::Ref().GetPrefInteger("Scale", 1);
resizable = Client::Ref().GetPrefBool("Resizable", false);
fullscreen = Client::Ref().GetPrefBool("Fullscreen", false);
altFullscreen = Client::Ref().GetPrefBool("AltFullscreen", false);
forceIntegerScaling = Client::Ref().GetPrefBool("ForceIntegerScaling", true);
momentumScroll = Client::Ref().GetPrefBool("MomentumScroll", true);
showAvatars = Client::Ref().GetPrefBool("ShowAvatars", true);
auto &prefs = GlobalPrefs::Ref();
scale = prefs.Get("Scale", 1);
resizable = prefs.Get("Resizable", false);
fullscreen = prefs.Get("Fullscreen", false);
altFullscreen = prefs.Get("AltFullscreen", false);
forceIntegerScaling = prefs.Get("ForceIntegerScaling", true);
momentumScroll = prefs.Get("MomentumScroll", true);
showAvatars = prefs.Get("ShowAvatars", true);
auto true_string = [](ByteString str) {
str = str.ToLower();
@ -736,7 +755,7 @@ int main(int argc, char * argv[])
if (kioskArg.has_value())
{
fullscreen = true_string(kioskArg.value());
Client::Ref().SetPref("Fullscreen", fullscreen);
prefs.Set("Fullscreen", fullscreen);
}
if (true_arg(arguments["redirect"]))
@ -755,7 +774,7 @@ int main(int argc, char * argv[])
try
{
scale = scaleArg.value().ToNumber<int>();
Client::Ref().SetPref("Scale", scale);
prefs.Set("Scale", scale);
}
catch (const std::runtime_error &e)
{
@ -763,7 +782,7 @@ int main(int argc, char * argv[])
}
}
auto clientConfig = [](Argument arg, ByteString name, ByteString defaultValue) {
auto clientConfig = [&prefs](Argument arg, ByteString name, ByteString defaultValue) {
ByteString value;
if (arg.has_value())
{
@ -772,11 +791,11 @@ int main(int argc, char * argv[])
{
value = defaultValue;
}
Client::Ref().SetPref(name, value);
prefs.Set(name, value);
}
else
{
value = Client::Ref().GetPrefByteString(name, defaultValue);
value = prefs.Get(name, defaultValue);
}
return value;
};
@ -784,8 +803,9 @@ int main(int argc, char * argv[])
ByteString cafileString = clientConfig(arguments["cafile"], "CAFile", "");
ByteString capathString = clientConfig(arguments["capath"], "CAPath", "");
bool disableNetwork = true_arg(arguments["disable-network"]);
auto requestManager = http::RequestManager::Create(proxyString, cafileString, capathString, disableNetwork);
explicitSingletons->requestManager = http::RequestManager::Create(proxyString, cafileString, capathString, disableNetwork);
explicitSingletons->client = std::make_unique<Client>();
Client::Ref().Initialize();
// TODO: maybe bind the maximum allowed scale to screen size somehow
@ -799,7 +819,7 @@ int main(int argc, char * argv[])
scale = GuessBestScale();
if (scale > 1)
{
Client::Ref().SetPref("Scale", scale);
prefs.Set("Scale", scale);
SDL_SetWindowSize(sdl_window, WINDOWW * scale, WINDOWH * scale);
showLargeScreenDialog = true;
}
@ -819,7 +839,7 @@ int main(int argc, char * argv[])
engine = &ui::Engine::Ref();
engine->SetMaxSize(desktopWidth, desktopHeight);
engine->Begin(WINDOWW, WINDOWH);
engine->SetFastQuit(Client::Ref().GetPrefBool("FastQuit", true));
engine->SetFastQuit(prefs.Get("FastQuit", true));
bool enableBluescreen = !DEBUG && !true_arg(arguments["disable-bluescreen"]);
if (enableBluescreen)
@ -949,7 +969,6 @@ int main(int argc, char * argv[])
ui::Engine::Ref().CloseWindow();
delete gameController;
delete ui::Engine::Ref().g;
Client::Ref().Shutdown();
if (SDL_GetWindowFlags(sdl_window) & SDL_WINDOW_OPENGL)
{
// * nvidia-460 egl registers callbacks with x11 that end up being called

View File

@ -1,5 +1,6 @@
#include "Client.h"
#include "prefs/GlobalPrefs.h"
#include "client/http/Request.h" // includes curl.h, needs to come first to silence a warning on windows
#include <cstring>
@ -65,48 +66,30 @@ Client::Client():
updateAvailable(false),
authUser(0, "")
{
//Read config
std::ifstream configFile;
configFile.open("powder.pref", std::ios::binary);
if (configFile)
auto &prefs = GlobalPrefs::Ref();
authUser.UserID = prefs.Get("User.ID", 0);
authUser.Username = prefs.Get("User.Username", ByteString(""));
authUser.SessionID = prefs.Get("User.SessionID", ByteString(""));
authUser.SessionKey = prefs.Get("User.SessionKey", ByteString(""));
auto elevation = prefs.Get("User.Elevation", ByteString(""));
authUser.UserElevation = User::ElevationNone;
if (elevation == "Admin")
{
try
{
preferences.clear();
configFile >> preferences;
int ID = preferences["User"]["ID"].asInt();
ByteString Username = preferences["User"]["Username"].asString();
ByteString SessionID = preferences["User"]["SessionID"].asString();
ByteString SessionKey = preferences["User"]["SessionKey"].asString();
ByteString Elevation = preferences["User"]["Elevation"].asString();
authUser.UserID = ID;
authUser.Username = Username;
authUser.SessionID = SessionID;
authUser.SessionKey = SessionKey;
if (Elevation == "Admin")
authUser.UserElevation = User::ElevationAdmin;
else if (Elevation == "Mod")
authUser.UserElevation = User::ElevationModerator;
else
authUser.UserElevation = User::ElevationNone;
}
catch (std::exception &e)
{
}
configFile.close();
firstRun = false;
authUser.UserElevation = User::ElevationAdmin;
}
else
firstRun = true;
if (elevation == "Mod")
{
authUser.UserElevation = User::ElevationModerator;
}
firstRun = !prefs.BackedByFile();
}
void Client::Initialize()
{
if (GetPrefBool("version.update", false))
auto &prefs = GlobalPrefs::Ref();
if (prefs.Get("version.update", false))
{
SetPref("version.update", false);
prefs.Set("version.update", false);
update_finish();
}
@ -412,51 +395,38 @@ void Client::RemoveListener(ClientListener * listener)
}
}
void Client::WritePrefs()
{
std::ofstream configFile;
configFile.open("powder.pref", std::ios::trunc);
if (configFile)
{
if (authUser.UserID)
{
preferences["User"]["ID"] = authUser.UserID;
preferences["User"]["SessionID"] = authUser.SessionID;
preferences["User"]["SessionKey"] = authUser.SessionKey;
preferences["User"]["Username"] = authUser.Username;
if (authUser.UserElevation == User::ElevationAdmin)
preferences["User"]["Elevation"] = "Admin";
else if (authUser.UserElevation == User::ElevationModerator)
preferences["User"]["Elevation"] = "Mod";
else
preferences["User"]["Elevation"] = "None";
}
else
{
preferences["User"] = Json::nullValue;
}
configFile << preferences;
configFile.close();
}
}
void Client::Shutdown()
{
//Save config
WritePrefs();
}
Client::~Client()
{
}
void Client::SetAuthUser(User user)
{
authUser = user;
WritePrefs();
{
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
if (authUser.UserID)
{
prefs.Set("User.ID", authUser.UserID);
prefs.Set("User.SessionID", authUser.SessionID);
prefs.Set("User.SessionKey", authUser.SessionKey);
prefs.Set("User.Username", authUser.Username);
ByteString elevation = "None";
if (authUser.UserElevation == User::ElevationAdmin)
{
elevation = "Admin";
}
if (authUser.UserElevation == User::ElevationModerator)
{
elevation = "Mod";
}
prefs.Set("User.Elevation", elevation);
}
else
{
prefs.Clear("User");
}
}
notifyAuthUserChanged();
}
@ -1264,254 +1234,6 @@ void Client::SaveAuthorInfo(Json::Value *saveInto)
}
}
// powder.pref preference getting / setting functions
// Recursively go down the json to get the setting we want
Json::Value Client::GetPref(Json::Value root, ByteString prop, Json::Value defaultValue)
{
try
{
if(ByteString::Split split = prop.SplitBy('.'))
return GetPref(root[split.Before()], split.After(), defaultValue);
else
return root.get(prop, defaultValue);
}
catch (std::exception & e)
{
return defaultValue;
}
}
ByteString Client::GetPrefByteString(ByteString prop, ByteString defaultValue)
{
try
{
return GetPref(preferences, prop, defaultValue).asString();
}
catch (std::exception & e)
{
return defaultValue;
}
}
String Client::GetPrefString(ByteString prop, String defaultValue)
{
try
{
return ByteString(GetPref(preferences, prop, defaultValue.ToUtf8()).asString()).FromUtf8(false);
}
catch (std::exception & e)
{
return defaultValue;
}
}
double Client::GetPrefNumber(ByteString prop, double defaultValue)
{
try
{
return GetPref(preferences, prop, defaultValue).asDouble();
}
catch (std::exception & e)
{
return defaultValue;
}
}
int Client::GetPrefInteger(ByteString prop, int defaultValue)
{
try
{
return GetPref(preferences, prop, defaultValue).asInt();
}
catch (std::exception & e)
{
return defaultValue;
}
}
unsigned int Client::GetPrefUInteger(ByteString prop, unsigned int defaultValue)
{
try
{
return GetPref(preferences, prop, defaultValue).asUInt();
}
catch (std::exception & e)
{
return defaultValue;
}
}
bool Client::GetPrefBool(ByteString prop, bool defaultValue)
{
try
{
return GetPref(preferences, prop, defaultValue).asBool();
}
catch (std::exception & e)
{
return defaultValue;
}
}
std::vector<ByteString> Client::GetPrefByteStringArray(ByteString prop)
{
try
{
std::vector<ByteString> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(arr[i].asString());
return ret;
}
catch (std::exception & e)
{
}
return std::vector<ByteString>();
}
std::vector<String> Client::GetPrefStringArray(ByteString prop)
{
try
{
std::vector<String> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(ByteString(arr[i].asString()).FromUtf8(false));
return ret;
}
catch (std::exception & e)
{
}
return std::vector<String>();
}
std::vector<double> Client::GetPrefNumberArray(ByteString prop)
{
try
{
std::vector<double> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(arr[i].asDouble());
return ret;
}
catch (std::exception & e)
{
}
return std::vector<double>();
}
std::vector<int> Client::GetPrefIntegerArray(ByteString prop)
{
try
{
std::vector<int> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(arr[i].asInt());
return ret;
}
catch (std::exception & e)
{
}
return std::vector<int>();
}
std::vector<unsigned int> Client::GetPrefUIntegerArray(ByteString prop)
{
try
{
std::vector<unsigned int> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(arr[i].asUInt());
return ret;
}
catch (std::exception & e)
{
}
return std::vector<unsigned int>();
}
std::vector<bool> Client::GetPrefBoolArray(ByteString prop)
{
try
{
std::vector<bool> ret;
Json::Value arr = GetPref(preferences, prop);
for (int i = 0; i < (int)arr.size(); i++)
ret.push_back(arr[i].asBool());
return ret;
}
catch (std::exception & e)
{
}
return std::vector<bool>();
}
// Helper preference setting function.
// To actually save any changes to preferences, we need to directly do preferences[property] = thing
// any other way will set the value of a copy of preferences, not the original
// This function will recursively go through and create an object with the property we wanted set,
// and return it to SetPref to do the actual setting
Json::Value Client::SetPrefHelper(Json::Value root, ByteString prop, Json::Value value)
{
if(ByteString::Split split = prop.SplitBy('.'))
{
Json::Value toSet = GetPref(root, split.Before());
toSet = SetPrefHelper(toSet, split.After(), value);
root[split.Before()] = toSet;
}
else
root[prop] = value;
return root;
}
void Client::SetPref(ByteString prop, Json::Value value)
{
try
{
if(ByteString::Split split = prop.SplitBy('.'))
preferences[split.Before()] = SetPrefHelper(preferences[split.Before()], split.After(), value);
else
preferences[prop] = value;
WritePrefs();
}
catch (std::exception & e)
{
}
}
void Client::SetPref(ByteString prop, std::vector<Json::Value> value)
{
try
{
Json::Value arr;
for (int i = 0; i < (int)value.size(); i++)
{
arr.append(value[i]);
}
SetPref(prop, arr);
}
catch (std::exception & e)
{
}
}
void Client::SetPrefUnicode(ByteString prop, String value)
{
SetPref(prop, value.ToUtf8());
}
bool Client::DoInstallation()
{
bool ok = true;
@ -1652,8 +1374,9 @@ bool Client::DoInstallation()
bool AddCustomGol(String ruleString, String nameString, unsigned int highColor, unsigned int lowColor)
{
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value newCustomGOLTypes(Json::arrayValue);
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
std::vector<ByteString> newCustomGOLTypes;
bool nameTaken = false;
for (auto gol : customGOLTypes)
{
@ -1665,14 +1388,147 @@ bool AddCustomGol(String ruleString, String nameString, unsigned int highColor,
nameTaken = true;
}
}
newCustomGOLTypes.append(gol);
newCustomGOLTypes.push_back(gol);
}
if (nameTaken)
return false;
StringBuilder sb;
sb << nameString << " " << ruleString << " " << highColor << " " << lowColor;
newCustomGOLTypes.append(sb.Build().ToUtf8());
Client::Ref().SetPref("CustomGOL.Types", newCustomGOLTypes);
newCustomGOLTypes.push_back(sb.Build().ToUtf8());
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
return true;
}
String Client::DoMigration(ByteString fromDir, ByteString toDir)
{
if (fromDir.at(fromDir.length() - 1) != '/')
fromDir = fromDir + '/';
if (toDir.at(toDir.length() - 1) != '/')
toDir = toDir + '/';
std::ofstream logFile(fromDir + "/migrationlog.txt", std::ios::out);
logFile << "Running migration of data from " << fromDir + " to " << toDir << std::endl;
// Get lists of files to migrate
auto stamps = Platform::DirectorySearch(fromDir + "stamps", "", { ".stm" });
auto saves = Platform::DirectorySearch(fromDir + "Saves", "", { ".cps", ".stm" });
auto scripts = Platform::DirectorySearch(fromDir + "scripts", "", { ".lua", ".txt" });
auto downloadedScripts = Platform::DirectorySearch(fromDir + "scripts/downloaded", "", { ".lua" });
bool hasScriptinfo = Platform::FileExists(toDir + "scripts/downloaded/scriptinfo");
auto screenshots = Platform::DirectorySearch(fromDir, "screenshot", { ".png" });
bool hasAutorun = Platform::FileExists(fromDir + "autorun.lua");
bool hasPref = Platform::FileExists(fromDir + "powder.pref");
if (stamps.empty() && saves.empty() && scripts.empty() && downloadedScripts.empty() && screenshots.empty() && !hasAutorun && !hasPref)
{
logFile << "Nothing to migrate.";
return "Nothing to migrate. This button is used to migrate data from pre-96.0 TPT installations to the shared directory";
}
StringBuilder result;
std::stack<ByteString> dirsToDelete;
// Migrate a list of files
auto migrateList = [&](std::vector<ByteString> list, ByteString directory, String niceName) {
result << '\n' << niceName << ": ";
if (!list.empty() && !directory.empty())
Platform::MakeDirectory(toDir + directory);
int migratedCount = 0, failedCount = 0;
for (auto &item : list)
{
std::string from = fromDir + directory + "/" + item;
std::string to = toDir + directory + "/" + item;
if (!Platform::FileExists(to))
{
if (rename(from.c_str(), to.c_str()))
{
failedCount++;
logFile << "failed to move " << from << " to " << to << std::endl;
}
else
{
migratedCount++;
logFile << "moved " << from << " to " << to << std::endl;
}
}
else
{
logFile << "skipping " << from << "(already exists)" << std::endl;
}
}
dirsToDelete.push(directory);
result << "\bt" << migratedCount << " migratated\x0E, \br" << failedCount << " failed\x0E";
int duplicates = list.size() - migratedCount - failedCount;
if (duplicates)
result << ", " << list.size() - migratedCount - failedCount << " skipped (duplicate)";
};
// Migrate a single file
auto migrateFile = [&fromDir, &toDir, &result, &logFile](ByteString filename) {
ByteString from = fromDir + filename;
ByteString to = toDir + filename;
if (!Platform::FileExists(to))
{
if (rename(from.c_str(), to.c_str()))
{
logFile << "failed to move " << from << " to " << to << std::endl;
result << "\n\br" << filename.FromUtf8() << " migration failed\x0E";
}
else
{
logFile << "moved " << from << " to " << to << std::endl;
result << '\n' << filename.FromUtf8() << " migrated";
}
}
else
{
logFile << "skipping " << from << "(already exists)" << std::endl;
result << '\n' << filename.FromUtf8() << " skipped (already exists)";
}
if (!Platform::RemoveFile(fromDir + filename)) {
logFile << "failed to delete " << filename << std::endl;
}
};
// Do actual migration
Platform::RemoveFile(fromDir + "stamps/stamps.def");
migrateList(stamps, "stamps", "Stamps");
migrateList(saves, "Saves", "Saves");
if (!scripts.empty())
migrateList(scripts, "scripts", "Scripts");
if (!hasScriptinfo && !downloadedScripts.empty())
{
migrateList(downloadedScripts, "scripts/downloaded", "Downloaded scripts");
migrateFile("scripts/downloaded/scriptinfo");
}
if (!screenshots.empty())
migrateList(screenshots, "", "Screenshots");
if (hasAutorun)
migrateFile("autorun.lua");
if (hasPref)
migrateFile("powder.pref");
// Delete leftover directories
while (!dirsToDelete.empty())
{
ByteString toDelete = dirsToDelete.top();
if (!Platform::DeleteDirectory(fromDir + toDelete)) {
logFile << "failed to delete " << toDelete << std::endl;
}
dirsToDelete.pop();
}
// chdir into the new directory
chdir(toDir.c_str());
if (scripts.size())
RescanStamps();
logFile << std::endl << std::endl << "Migration complete. Results: " << result.Build().ToUtf8();
logFile.close();
return result.Build();
}

View File

@ -6,7 +6,7 @@
#include <memory>
#include "common/String.h"
#include "common/Singleton.h"
#include "common/ExplicitSingleton.h"
#include <json/json.h>
#include "User.h"
@ -47,7 +47,7 @@ namespace http
{
class Request;
}
class Client: public Singleton<Client> {
class Client: public ExplicitSingleton<Client> {
private:
String messageOfTheDay;
std::vector<std::pair<String, ByteString> > serverNotifications;
@ -73,11 +73,6 @@ private:
void notifyMessageOfTheDay();
void notifyNewNotification(std::pair<String, ByteString> notification);
// internal preferences handling
Json::Value preferences;
Json::Value GetPref(Json::Value root, ByteString prop, Json::Value defaultValue = Json::nullValue);
Json::Value SetPrefHelper(Json::Value root, ByteString prop, Json::Value value);
// Save stealing info
Json::Value authors;
@ -155,27 +150,8 @@ public:
RequestStatus ParseServerReturn(ByteString &result, int status, bool json);
void Tick();
void CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession);
void Shutdown();
// preferences functions
void WritePrefs();
ByteString GetPrefByteString(ByteString prop, ByteString defaultValue);
String GetPrefString(ByteString prop, String defaultValue);
double GetPrefNumber(ByteString prop, double defaultValue);
int GetPrefInteger(ByteString prop, int defaultValue);
unsigned int GetPrefUInteger(ByteString prop, unsigned int defaultValue);
bool GetPrefBool(ByteString prop, bool defaultValue);
std::vector<ByteString> GetPrefByteStringArray(ByteString prop);
std::vector<String> GetPrefStringArray(ByteString prop);
std::vector<double> GetPrefNumberArray(ByteString prop);
std::vector<int> GetPrefIntegerArray(ByteString prop);
std::vector<unsigned int> GetPrefUIntegerArray(ByteString prop);
std::vector<bool> GetPrefBoolArray(ByteString prop);
void SetPref(ByteString prop, Json::Value value);
void SetPref(ByteString property, std::vector<Json::Value> value);
void SetPrefUnicode(ByteString prop, String value);
String DoMigration(ByteString fromDir, ByteString toDir);
};
bool AddCustomGol(String ruleString, String nameString, unsigned int highColor, unsigned int lowColor);

View File

@ -1,6 +1,5 @@
#include "ImageRequest.h"
#include "common/Singleton.h"
#include "graphics/Graphics.h"
#include "Config.h"

View File

@ -318,8 +318,9 @@ bool ReadFile(std::vector<char> &fileData, ByteString filename)
return true;
}
bool WriteFile(std::vector<char> fileData, ByteString filename)
bool WriteFile(std::vector<char> fileData, ByteString filename, bool replaceAtomically)
{
// TODO: replaceAtomically
std::ofstream f(filename, std::ios::binary);
if (f) f.write(&fileData[0], fileData.size());
if (!f)
@ -381,4 +382,31 @@ ByteString ExecutableName()
#endif
}
void DoRestart()
{
ByteString exename = ExecutableName();
if (exename.length())
{
#ifdef WIN
int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL)));
if (ret <= 32)
{
fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret);
}
else
{
exit(0);
}
#elif defined(LIN) || defined(MACOSX)
execl(exename.c_str(), exename.c_str(), NULL);
int ret = errno;
fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret);
#endif
}
else
{
fprintf(stderr, "cannot restart: no executable name???\n");
}
exit(-1);
}
}

View File

@ -37,10 +37,9 @@ namespace Platform
*/
bool MakeDirectory(ByteString dir);
std::vector<ByteString> DirectorySearch(ByteString directory, ByteString search, std::vector<ByteString> extensions);
String DoMigration(ByteString fromDir, ByteString toDir);
bool ReadFile(std::vector<char> &fileData, ByteString filename);
bool WriteFile(std::vector<char> fileData, ByteString filename);
bool WriteFile(std::vector<char> fileData, ByteString filename, bool replaceAtomically = false); // TODO: Revisit call sites, remove default.
#ifdef WIN
ByteString WinNarrow(const std::wstring &source);

View File

@ -1,187 +0,0 @@
#include "Platform.h"
#include "client/Client.h"
#ifdef WIN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <direct.h>
# include <io.h>
# include <shlobj.h>
# include <shlwapi.h>
# include <shellapi.h>
# include <windows.h>
#else
# include <unistd.h>
# include <ctime>
# include <sys/time.h>
# include <dirent.h>
#endif
#ifdef MACOSX
# include <mach-o/dyld.h>
#endif
#include <fstream>
namespace Platform
{
void DoRestart()
{
ByteString exename = ExecutableName();
if (exename.length())
{
#ifdef WIN
int ret = int(INT_PTR(ShellExecuteW(NULL, NULL, WinWiden(exename).c_str(), NULL, NULL, SW_SHOWNORMAL)));
if (ret <= 32)
{
fprintf(stderr, "cannot restart: ShellExecute(...) failed: code %i\n", ret);
}
else
{
Client::Ref().Shutdown(); // very ugly hack; will fix soon(tm)
exit(0);
}
#elif defined(LIN) || defined(MACOSX)
execl(exename.c_str(), exename.c_str(), NULL);
int ret = errno;
fprintf(stderr, "cannot restart: execl(...) failed: code %i\n", ret);
#endif
}
else
{
fprintf(stderr, "cannot restart: no executable name???\n");
}
exit(-1);
}
String DoMigration(ByteString fromDir, ByteString toDir)
{
if (fromDir.at(fromDir.length() - 1) != '/')
fromDir = fromDir + '/';
if (toDir.at(toDir.length() - 1) != '/')
toDir = toDir + '/';
std::ofstream logFile(fromDir + "/migrationlog.txt", std::ios::out);
logFile << "Running migration of data from " << fromDir + " to " << toDir << std::endl;
// Get lists of files to migrate
auto stamps = DirectorySearch(fromDir + "stamps", "", { ".stm" });
auto saves = DirectorySearch(fromDir + "Saves", "", { ".cps", ".stm" });
auto scripts = DirectorySearch(fromDir + "scripts", "", { ".lua", ".txt" });
auto downloadedScripts = DirectorySearch(fromDir + "scripts/downloaded", "", { ".lua" });
bool hasScriptinfo = FileExists(toDir + "scripts/downloaded/scriptinfo");
auto screenshots = DirectorySearch(fromDir, "screenshot", { ".png" });
bool hasAutorun = FileExists(fromDir + "autorun.lua");
bool hasPref = FileExists(fromDir + "powder.pref");
if (stamps.empty() && saves.empty() && scripts.empty() && downloadedScripts.empty() && screenshots.empty() && !hasAutorun && !hasPref)
{
logFile << "Nothing to migrate.";
return "Nothing to migrate. This button is used to migrate data from pre-96.0 TPT installations to the shared directory";
}
StringBuilder result;
std::stack<ByteString> dirsToDelete;
// Migrate a list of files
auto migrateList = [&](std::vector<ByteString> list, ByteString directory, String niceName) {
result << '\n' << niceName << ": ";
if (!list.empty() && !directory.empty())
MakeDirectory(toDir + directory);
int migratedCount = 0, failedCount = 0;
for (auto &item : list)
{
std::string from = fromDir + directory + "/" + item;
std::string to = toDir + directory + "/" + item;
if (!FileExists(to))
{
if (rename(from.c_str(), to.c_str()))
{
failedCount++;
logFile << "failed to move " << from << " to " << to << std::endl;
}
else
{
migratedCount++;
logFile << "moved " << from << " to " << to << std::endl;
}
}
else
{
logFile << "skipping " << from << "(already exists)" << std::endl;
}
}
dirsToDelete.push(directory);
result << "\bt" << migratedCount << " migratated\x0E, \br" << failedCount << " failed\x0E";
int duplicates = list.size() - migratedCount - failedCount;
if (duplicates)
result << ", " << list.size() - migratedCount - failedCount << " skipped (duplicate)";
};
// Migrate a single file
auto migrateFile = [&fromDir, &toDir, &result, &logFile](ByteString filename) {
ByteString from = fromDir + filename;
ByteString to = toDir + filename;
if (!FileExists(to))
{
if (rename(from.c_str(), to.c_str()))
{
logFile << "failed to move " << from << " to " << to << std::endl;
result << "\n\br" << filename.FromUtf8() << " migration failed\x0E";
}
else
{
logFile << "moved " << from << " to " << to << std::endl;
result << '\n' << filename.FromUtf8() << " migrated";
}
}
else
{
logFile << "skipping " << from << "(already exists)" << std::endl;
result << '\n' << filename.FromUtf8() << " skipped (already exists)";
}
if (!RemoveFile(fromDir + filename)) {
logFile << "failed to delete " << filename << std::endl;
}
};
// Do actual migration
RemoveFile(fromDir + "stamps/stamps.def");
migrateList(stamps, "stamps", "Stamps");
migrateList(saves, "Saves", "Saves");
if (!scripts.empty())
migrateList(scripts, "scripts", "Scripts");
if (!hasScriptinfo && !downloadedScripts.empty())
{
migrateList(downloadedScripts, "scripts/downloaded", "Downloaded scripts");
migrateFile("scripts/downloaded/scriptinfo");
}
if (!screenshots.empty())
migrateList(screenshots, "", "Screenshots");
if (hasAutorun)
migrateFile("autorun.lua");
if (hasPref)
migrateFile("powder.pref");
// Delete leftover directories
while (!dirsToDelete.empty())
{
ByteString toDelete = dirsToDelete.top();
if (!DeleteDirectory(fromDir + toDelete)) {
logFile << "failed to delete " << toDelete << std::endl;
}
dirsToDelete.pop();
}
// chdir into the new directory
chdir(toDir.c_str());
if (scripts.size())
Client::Ref().RescanStamps();
logFile << std::endl << std::endl << "Migration complete. Results: " << result.Build().ToUtf8();
logFile.close();
return result.Build();
}
}

View File

@ -4,7 +4,3 @@ common_files += files(
'tpt-rand.cpp',
'tpt-thread-local.cpp',
)
powder_files += files(
'PlatformPowder.cpp',
)

View File

@ -13,9 +13,9 @@ public:
int ReturnStatus;
String ReturnValue;
operator ByteString() const
operator String() const
{
return Command.ToUtf8();
return Command;
}
};

View File

@ -1,11 +1,9 @@
#include "ConsoleModel.h"
#include "ConsoleView.h"
#include "client/Client.h"
#include "prefs/GlobalPrefs.h"
ConsoleModel::ConsoleModel() {
std::vector<String> previousHistory = Client::Ref().GetPrefStringArray("Console.History");
std::vector<String> previousHistory = GlobalPrefs::Ref().Get("Console.History", std::vector<String>{});
for(std::vector<String>::reverse_iterator iter = previousHistory.rbegin(), end = previousHistory.rend(); iter != end; ++iter)
{
if(previousCommands.size()<25)
@ -48,7 +46,7 @@ void ConsoleModel::AddLastCommand(ConsoleCommand command)
if(previousCommands.size()>25)
previousCommands.pop_front();
currentCommandIndex = previousCommands.size();
Client::Ref().SetPref("Console.History", std::vector<Json::Value>(previousCommands.begin(), previousCommands.end()));
GlobalPrefs::Ref().Set("Console.History", std::vector<String>(previousCommands.begin(), previousCommands.end()));
notifyPreviousCommandsChanged();
}

View File

@ -1,7 +1,7 @@
#include "Favorite.h"
#include <json/json.h>
#include "client/Client.h"
#include "prefs/GlobalPrefs.h"
#include <algorithm>
@ -42,10 +42,10 @@ void Favorite::RemoveFavorite(ByteString identifier)
void Favorite::SaveFavoritesToPrefs()
{
Client::Ref().SetPref("Favorites", std::vector<Json::Value>(favoritesList.begin(), favoritesList.end()));
GlobalPrefs::Ref().Set("Favorites", favoritesList);
}
void Favorite::LoadFavoritesFromPrefs()
{
favoritesList = Client::Ref().GetPrefByteStringArray("Favorites");
favoritesList = GlobalPrefs::Ref().Get("Favorites", std::vector<ByteString>{});
}

View File

@ -1,5 +1,6 @@
#include "Tool.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "common/tpt-rand.h"
#include "simulation/GOLString.h"
@ -105,8 +106,9 @@ toolSelection(toolSelection)
}
else
{
ruleField->SetText(Client::Ref().GetPrefString("CustomGOL.Rule", "B3/S23"));
nameField->SetText(Client::Ref().GetPrefString("CustomGOL.Name", "CGOL"));
auto &prefs = GlobalPrefs::Ref();
ruleField->SetText(prefs.Get("CustomGOL.Rule", String("B3/S23")));
nameField->SetText(prefs.Get("CustomGOL.Name", String("CGOL")));
highColour.Red = RNG::Ref().between(0x80, 0xFF);
highColour.Green = RNG::Ref().between(0x80, 0xFF);
highColour.Blue = RNG::Ref().between(0x80, 0xFF);
@ -152,8 +154,12 @@ void GOLWindow::Validate()
}
ruleString = SerialiseGOLRule(rule); // * Make it canonical.
Client::Ref().SetPrefUnicode("CustomGOL.Name", nameString);
Client::Ref().SetPrefUnicode("CustomGOL.Rule", ruleString);
{
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
prefs.Set("CustomGOL.Name", nameString);
prefs.Set("CustomGOL.Rule", ruleString);
}
auto color1 = (((highColour.Red << 8) | highColour.Green) << 8) | highColour.Blue;
auto color2 = (((lowColour.Red << 8) | lowColour.Green) << 8) | lowColour.Blue;

View File

@ -16,6 +16,7 @@
#include "GameControllerEvents.h"
#include "lua/CommandInterface.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "client/GameSave.h"
#include "common/Platform.h"
@ -88,7 +89,7 @@ GameController::GameController():
gameView->AttachController(this);
gameModel->AddObserver(gameView);
gameView->SetDebugHUD(Client::Ref().GetPrefBool("Renderer.DebugMode", false));
gameView->SetDebugHUD(GlobalPrefs::Ref().Get("Renderer.DebugMode", false));
CommandInterface::Create(this, gameModel);
@ -1352,7 +1353,6 @@ void GameController::OpenOptions()
{
options = new OptionsController(gameModel, [this] {
gameModel->UpdateQuickOptions();
Client::Ref().WritePrefs(); // * I don't think there's a reason for this but I'm too lazy to check. -- LBPHacker
});
ui::Engine::Ref().ShowWindow(options->GetView());

View File

@ -16,6 +16,7 @@
#include "QuickOptions.h"
#include "lua/CommandInterface.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
@ -67,56 +68,51 @@ GameModel::GameModel():
std::fill(regularToolset, regularToolset+4, (Tool*)NULL);
//Default render prefs
std::vector<unsigned int> tempArray;
tempArray.push_back(RENDER_FIRE);
tempArray.push_back(RENDER_EFFE);
tempArray.push_back(RENDER_BASC);
ren->SetRenderMode(tempArray);
tempArray.clear();
ren->SetDisplayMode(tempArray);
ren->SetRenderMode({
RENDER_FIRE,
RENDER_EFFE,
RENDER_BASC,
});
ren->SetDisplayMode({});
ren->SetColourMode(0);
//Load config into renderer
ren->SetColourMode(Client::Ref().GetPrefUInteger("Renderer.ColourMode", 0));
auto &prefs = GlobalPrefs::Ref();
ren->SetColourMode(prefs.Get("Renderer.ColourMode", 0U));
tempArray = Client::Ref().GetPrefUIntegerArray("Renderer.DisplayModes");
if(tempArray.size())
auto displayModes = prefs.Get("Renderer.DisplayModes", std::vector<unsigned int>{});
if (displayModes.size())
{
std::vector<unsigned int> displayModes(tempArray.begin(), tempArray.end());
ren->SetDisplayMode(displayModes);
}
tempArray = Client::Ref().GetPrefUIntegerArray("Renderer.RenderModes");
if(tempArray.size())
auto renderModes = prefs.Get("Renderer.RenderModes", std::vector<unsigned int>{});
if (renderModes.size())
{
std::vector<unsigned int> renderModes(tempArray.begin(), tempArray.end());
ren->SetRenderMode(renderModes);
}
ren->gravityFieldEnabled = Client::Ref().GetPrefBool("Renderer.GravityField", false);
ren->decorations_enable = Client::Ref().GetPrefBool("Renderer.Decorations", true);
ren->gravityFieldEnabled = prefs.Get("Renderer.GravityField", false);
ren->decorations_enable = prefs.Get("Renderer.Decorations", true);
//Load config into simulation
edgeMode = Client::Ref().GetPrefInteger("Simulation.EdgeMode", 0);
edgeMode = prefs.Get("Simulation.EdgeMode", 0); // TODO: EdgeMode enum
sim->SetEdgeMode(edgeMode);
ambientAirTemp = float(R_TEMP) + 273.15f;
{
auto temp = Client::Ref().GetPrefNumber("Simulation.AmbientAirTemp", ambientAirTemp);
auto temp = prefs.Get("Simulation.AmbientAirTemp", ambientAirTemp);
if (MIN_TEMP <= temp && MAX_TEMP >= temp)
{
ambientAirTemp = temp;
}
}
sim->air->ambientAirTemp = ambientAirTemp;
decoSpace = Client::Ref().GetPrefInteger("Simulation.DecoSpace", 0);
decoSpace = prefs.Get("Simulation.DecoSpace", 0); // TODO: DecoSpace enum
sim->SetDecoSpace(decoSpace);
int ngrav_enable = Client::Ref().GetPrefInteger("Simulation.NewtonianGravity", 0);
int ngrav_enable = prefs.Get("Simulation.NewtonianGravity", 0); // TODO: NewtonianGravity enum
if (ngrav_enable)
sim->grav->start_grav_async();
sim->aheat_enable = Client::Ref().GetPrefInteger("Simulation.AmbientHeat", 0);
sim->pretty_powder = Client::Ref().GetPrefInteger("Simulation.PrettyPowder", 0);
sim->aheat_enable = prefs.Get("Simulation.AmbientHeat", 0); // TODO: AmbientHeat enum
sim->pretty_powder = prefs.Get("Simulation.PrettyPowder", 0); // TODO: PrettyPowder enum
Favorite::Ref().LoadFavoritesFromPrefs();
@ -128,14 +124,14 @@ GameModel::GameModel():
BuildMenus();
perfectCircle = Client::Ref().GetPrefBool("PerfectCircleBrush", true);
perfectCircle = prefs.Get("PerfectCircleBrush", true);
BuildBrushList();
//Set default decoration colour
unsigned char colourR = std::min(Client::Ref().GetPrefInteger("Decoration.Red", 200), 255);
unsigned char colourG = std::min(Client::Ref().GetPrefInteger("Decoration.Green", 100), 255);
unsigned char colourB = std::min(Client::Ref().GetPrefInteger("Decoration.Blue", 50), 255);
unsigned char colourA = std::min(Client::Ref().GetPrefInteger("Decoration.Alpha", 255), 255);
unsigned char colourR = std::max(std::min(prefs.Get("Decoration.Red", 200), 255), 0);
unsigned char colourG = std::max(std::min(prefs.Get("Decoration.Green", 100), 255), 0);
unsigned char colourB = std::max(std::min(prefs.Get("Decoration.Blue", 50), 255), 0);
unsigned char colourA = std::max(std::min(prefs.Get("Decoration.Alpha", 255), 255), 0);
SetColourSelectorColour(ui::Colour(colourR, colourG, colourB, colourA));
@ -148,41 +144,38 @@ GameModel::GameModel():
colourPresets.push_back(ui::Colour(0, 0, 255));
colourPresets.push_back(ui::Colour(0, 0, 0));
undoHistoryLimit = Client::Ref().GetPrefInteger("Simulation.UndoHistoryLimit", 5);
undoHistoryLimit = prefs.Get("Simulation.UndoHistoryLimit", 5U);
// cap due to memory usage (this is about 3.4GB of RAM)
if (undoHistoryLimit > 200)
SetUndoHistoryLimit(200);
mouseClickRequired = Client::Ref().GetPrefBool("MouseClickRequired", false);
includePressure = Client::Ref().GetPrefBool("Simulation.IncludePressure", true);
temperatureScale = Client::Ref().GetPrefInteger("Renderer.TemperatureScale", 1);
mouseClickRequired = prefs.Get("MouseClickRequired", false);
includePressure = prefs.Get("Simulation.IncludePressure", true);
temperatureScale = prefs.Get("Renderer.TemperatureScale", 1); // TODO: TemperatureScale enum
ClearSimulation();
}
GameModel::~GameModel()
{
//Save to config:
Client::Ref().SetPref("Renderer.ColourMode", ren->GetColourMode());
std::vector<unsigned int> displayModes = ren->GetDisplayMode();
Client::Ref().SetPref("Renderer.DisplayModes", std::vector<Json::Value>(displayModes.begin(), displayModes.end()));
std::vector<unsigned int> renderModes = ren->GetRenderMode();
Client::Ref().SetPref("Renderer.RenderModes", std::vector<Json::Value>(renderModes.begin(), renderModes.end()));
Client::Ref().SetPref("Renderer.GravityField", (bool)ren->gravityFieldEnabled);
Client::Ref().SetPref("Renderer.Decorations", (bool)ren->decorations_enable);
Client::Ref().SetPref("Renderer.DebugMode", ren->debugLines); //These two should always be equivalent, even though they are different things
Client::Ref().SetPref("Simulation.NewtonianGravity", sim->grav->IsEnabled());
Client::Ref().SetPref("Simulation.AmbientHeat", sim->aheat_enable);
Client::Ref().SetPref("Simulation.PrettyPowder", sim->pretty_powder);
Client::Ref().SetPref("Decoration.Red", (int)colour.Red);
Client::Ref().SetPref("Decoration.Green", (int)colour.Green);
Client::Ref().SetPref("Decoration.Blue", (int)colour.Blue);
Client::Ref().SetPref("Decoration.Alpha", (int)colour.Alpha);
auto &prefs = GlobalPrefs::Ref();
{
//Save to config:
Prefs::DeferWrite dw(prefs);
prefs.Set("Renderer.ColourMode", ren->GetColourMode());
prefs.Set("Renderer.DisplayModes", ren->GetDisplayMode());
prefs.Set("Renderer.RenderModes", ren->GetRenderMode());
prefs.Set("Renderer.GravityField", (bool)ren->gravityFieldEnabled);
prefs.Set("Renderer.Decorations", (bool)ren->decorations_enable);
prefs.Set("Renderer.DebugMode", ren->debugLines); //These two should always be equivalent, even though they are different things
prefs.Set("Simulation.NewtonianGravity", sim->grav->IsEnabled());
prefs.Set("Simulation.AmbientHeat", sim->aheat_enable);
prefs.Set("Simulation.PrettyPowder", sim->pretty_powder);
prefs.Set("Decoration.Red", (int)colour.Red);
prefs.Set("Decoration.Green", (int)colour.Green);
prefs.Set("Decoration.Blue", (int)colour.Blue);
prefs.Set("Decoration.Alpha", (int)colour.Alpha);
}
for (size_t i = 0; i < menuList.size(); i++)
{
@ -317,8 +310,9 @@ void GameModel::BuildMenus()
menuList[SC_LIFE]->AddTool(tempTool);
}
{
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value validatedCustomLifeTypes(Json::arrayValue);
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
std::vector<ByteString> validatedCustomLifeTypes;
std::vector<Simulation::CustomGOLData> newCustomGol;
for (auto gol : customGOLTypes)
{
@ -351,10 +345,10 @@ void GameModel::BuildMenus()
continue;
}
newCustomGol.push_back(gd);
validatedCustomLifeTypes.append(gol);
validatedCustomLifeTypes.push_back(gol);
}
// All custom rules that fail validation will be removed
Client::Ref().SetPref("CustomGOL.Types", validatedCustomLifeTypes);
prefs.Set("CustomGOL.Types", validatedCustomLifeTypes);
for (auto &gd : newCustomGol)
{
Tool * tempTool = new ElementTool(PT_LIFE|PMAPID(gd.rule), gd.nameString, "Custom GOL type: " + gd.ruleString, PIXR(gd.colour1), PIXG(gd.colour1), PIXB(gd.colour1), "DEFAULT_PT_LIFECUST_"+gd.nameString.ToAscii(), NULL);
@ -795,7 +789,7 @@ unsigned int GameModel::GetUndoHistoryLimit()
void GameModel::SetUndoHistoryLimit(unsigned int undoHistoryLimit_)
{
undoHistoryLimit = undoHistoryLimit_;
Client::Ref().SetPref("Simulation.UndoHistoryLimit", undoHistoryLimit);
GlobalPrefs::Ref().Set("Simulation.UndoHistoryLimit", undoHistoryLimit);
}
void GameModel::SetVote(int direction)
@ -1653,17 +1647,18 @@ void GameModel::SetPerfectCircle(bool perfectCircle)
bool GameModel::RemoveCustomGOLType(const ByteString &identifier)
{
bool removedAny = false;
auto customGOLTypes = Client::Ref().GetPrefByteStringArray("CustomGOL.Types");
Json::Value newCustomGOLTypes(Json::arrayValue);
auto &prefs = GlobalPrefs::Ref();
auto customGOLTypes = prefs.Get("CustomGOL.Types", std::vector<ByteString>{});
std::vector<ByteString> newCustomGOLTypes;
for (auto gol : customGOLTypes)
{
auto parts = gol.PartitionBy(' ');
if (parts.size() && "DEFAULT_PT_LIFECUST_" + parts[0] == identifier)
removedAny = true;
else
newCustomGOLTypes.append(gol);
newCustomGOLTypes.push_back(gol);
}
Client::Ref().SetPref("CustomGOL.Types", newCustomGOLTypes);
prefs.Set("CustomGOL.Types", newCustomGOLTypes);
BuildMenus();
return removedAny;
}

View File

@ -1,6 +1,6 @@
#include "Tool.h"
#include "client/Client.h"
#include "prefs/GlobalPrefs.h"
#include "Menu.h"
#include "Format.h"
@ -75,12 +75,14 @@ sim(sim_)
{
property->AddOption(std::pair<String, int>(properties[i].Name.FromAscii(), i));
}
property->SetOption(Client::Ref().GetPrefInteger("Prop.Type", 0));
auto &prefs = GlobalPrefs::Ref();
property->SetOption(prefs.Get("Prop.Type", 0));
textField = new ui::Textbox(ui::Point(8, 46), ui::Point(Size.X-16, 16), "", "[value]");
textField->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
textField->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
textField->SetText(Client::Ref().GetPrefString("Prop.Value", ""));
textField->SetText(prefs.Get("Prop.Value", String("")));
AddComponent(textField);
FocusComponent(textField);
SetProperty(false);
@ -216,8 +218,12 @@ void PropertyWindow::SetProperty(bool warn)
new ErrorMessage("Could not set property", "Invalid value provided");
return;
}
Client::Ref().SetPref("Prop.Type", property->GetOption().second);
Client::Ref().SetPrefUnicode("Prop.Value", textField->GetText());
{
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
prefs.Set("Prop.Type", property->GetOption().second);
prefs.Set("Prop.Value", textField->GetText());
}
}
}

View File

@ -6,7 +6,7 @@
#include "simulation/Air.h"
#include "simulation/Gravity.h"
#include "client/Client.h"
#include "prefs/GlobalPrefs.h"
#include "gui/interface/Engine.h"
#include "gui/game/GameModel.h"
@ -85,7 +85,7 @@ int OptionsModel::GetEdgeMode()
}
void OptionsModel::SetEdgeMode(int edgeMode)
{
Client::Ref().SetPref("Simulation.EdgeMode", edgeMode);
GlobalPrefs::Ref().Set("Simulation.EdgeMode", edgeMode);
gModel->SetEdgeMode(edgeMode);
notifySettingsChanged();
}
@ -96,7 +96,7 @@ int OptionsModel::GetTemperatureScale()
}
void OptionsModel::SetTemperatureScale(int temperatureScale)
{
Client::Ref().SetPref("Renderer.TemperatureScale", temperatureScale);
GlobalPrefs::Ref().Set("Renderer.TemperatureScale", temperatureScale);
gModel->SetTemperatureScale(temperatureScale);
notifySettingsChanged();
}
@ -107,7 +107,7 @@ float OptionsModel::GetAmbientAirTemperature()
}
void OptionsModel::SetAmbientAirTemperature(float ambientAirTemp)
{
Client::Ref().SetPref("Simulation.AmbientAirTemp", ambientAirTemp);
GlobalPrefs::Ref().Set("Simulation.AmbientAirTemp", ambientAirTemp);
gModel->SetAmbientAirTemperature(ambientAirTemp);
notifySettingsChanged();
}
@ -152,7 +152,7 @@ int OptionsModel::GetScale()
void OptionsModel::SetScale(int scale)
{
ui::Engine::Ref().SetScale(scale);
Client::Ref().SetPref("Scale", int(scale));
GlobalPrefs::Ref().Set("Scale", int(scale));
notifySettingsChanged();
}
@ -164,7 +164,7 @@ bool OptionsModel::GetResizable()
void OptionsModel::SetResizable(bool resizable)
{
ui::Engine::Ref().SetResizable(resizable);
Client::Ref().SetPref("Resizable", resizable);
GlobalPrefs::Ref().Set("Resizable", resizable);
notifySettingsChanged();
}
@ -175,7 +175,7 @@ bool OptionsModel::GetFullscreen()
void OptionsModel::SetFullscreen(bool fullscreen)
{
ui::Engine::Ref().SetFullscreen(fullscreen);
Client::Ref().SetPref("Fullscreen", fullscreen);
GlobalPrefs::Ref().Set("Fullscreen", fullscreen);
notifySettingsChanged();
}
@ -187,7 +187,7 @@ bool OptionsModel::GetAltFullscreen()
void OptionsModel::SetAltFullscreen(bool altFullscreen)
{
ui::Engine::Ref().SetAltFullscreen(altFullscreen);
Client::Ref().SetPref("AltFullscreen", altFullscreen);
GlobalPrefs::Ref().Set("AltFullscreen", altFullscreen);
notifySettingsChanged();
}
@ -199,7 +199,7 @@ bool OptionsModel::GetForceIntegerScaling()
void OptionsModel::SetForceIntegerScaling(bool forceIntegerScaling)
{
ui::Engine::Ref().SetForceIntegerScaling(forceIntegerScaling);
Client::Ref().SetPref("ForceIntegerScaling", forceIntegerScaling);
GlobalPrefs::Ref().Set("ForceIntegerScaling", forceIntegerScaling);
notifySettingsChanged();
}
@ -210,7 +210,7 @@ bool OptionsModel::GetFastQuit()
void OptionsModel::SetFastQuit(bool fastquit)
{
ui::Engine::Ref().SetFastQuit(fastquit);
Client::Ref().SetPref("FastQuit", bool(fastquit));
GlobalPrefs::Ref().Set("FastQuit", bool(fastquit));
notifySettingsChanged();
}
@ -220,7 +220,7 @@ int OptionsModel::GetDecoSpace()
}
void OptionsModel::SetDecoSpace(int decoSpace)
{
Client::Ref().SetPref("Simulation.DecoSpace", decoSpace);
GlobalPrefs::Ref().Set("Simulation.DecoSpace", decoSpace);
gModel->SetDecoSpace(decoSpace);
notifySettingsChanged();
}
@ -233,7 +233,7 @@ bool OptionsModel::GetShowAvatars()
void OptionsModel::SetShowAvatars(bool state)
{
ui::Engine::Ref().ShowAvatars = state;
Client::Ref().SetPref("ShowAvatars", state);
GlobalPrefs::Ref().Set("ShowAvatars", state);
notifySettingsChanged();
}
@ -244,7 +244,7 @@ bool OptionsModel::GetMouseClickRequired()
void OptionsModel::SetMouseClickRequired(bool mouseClickRequired)
{
Client::Ref().SetPref("MouseClickRequired", mouseClickRequired);
GlobalPrefs::Ref().Set("MouseClickRequired", mouseClickRequired);
gModel->SetMouseClickRequired(mouseClickRequired);
notifySettingsChanged();
}
@ -256,7 +256,7 @@ bool OptionsModel::GetIncludePressure()
void OptionsModel::SetIncludePressure(bool includePressure)
{
Client::Ref().SetPref("Simulation.IncludePressure", includePressure);
GlobalPrefs::Ref().Set("Simulation.IncludePressure", includePressure);
gModel->SetIncludePressure(includePressure);
notifySettingsChanged();
}
@ -268,7 +268,7 @@ bool OptionsModel::GetPerfectCircle()
void OptionsModel::SetPerfectCircle(bool perfectCircle)
{
Client::Ref().SetPref("PerfectCircleBrush", perfectCircle);
GlobalPrefs::Ref().Set("PerfectCircleBrush", perfectCircle);
gModel->SetPerfectCircle(perfectCircle);
notifySettingsChanged();
}
@ -280,7 +280,7 @@ bool OptionsModel::GetMomentumScroll()
void OptionsModel::SetMomentumScroll(bool state)
{
Client::Ref().SetPref("MomentumScroll", state);
GlobalPrefs::Ref().Set("MomentumScroll", state);
ui::Engine::Ref().MomentumScroll = state;
notifySettingsChanged();
}

View File

@ -14,6 +14,7 @@
#include "graphics/Renderer.h"
#include "gui/Style.h"
#include "simulation/ElementDefs.h"
#include "client/Client.h"
#include "gui/dialogues/ConfirmPrompt.h"
#include "gui/dialogues/InformationMessage.h"
@ -420,7 +421,7 @@ OptionsView::OptionsView():
ByteString to = Platform::sharedCwd;
new ConfirmPrompt("Do Migration?", "This will migrate all stamps, saves, and scripts from\n\bt" + from.FromUtf8() + "\bw\nto the shared data directory at\n\bt" + to.FromUtf8() + "\bw\n\n" +
"Files that already exist will not be overwritten.", { [=] () {
String ret = Platform::DoMigration(from, to);
String ret = Client::Ref().DoMigration(from, to);
new InformationMessage("Migration Complete", ret, false);
} });
} });

View File

@ -8,6 +8,7 @@
#include "Config.h"
#include "Update.h"
#include "prefs/GlobalPrefs.h"
#include "client/Client.h"
#include "common/Platform.h"
#include "tasks/Task.h"
@ -32,7 +33,13 @@ private:
}
bool doWork() override
{
String error;
auto &prefs = GlobalPrefs::Ref();
auto niceNotifyError = [this](String error) {
notifyError("Downloaded update is corrupted\n" + error);
return false;
};
auto request = std::make_unique<http::Request>(updateName);
request->Start();
notifyStatus("Downloading update");
@ -48,15 +55,11 @@ private:
auto [ status, data ] = request->Finish();
if (status!=200)
{
error = String::Build("Server responded with Status ", status);
notifyError("Could not download update: " + error);
return false;
return niceNotifyError("Could not download update: " + String::Build("Server responded with Status ", status));
}
if (!data.size())
{
error = "Server responded with nothing";
notifyError("Server did not return any data");
return false;
return niceNotifyError("Server did not return any data");
}
notifyStatus("Unpacking update");
@ -66,13 +69,11 @@ private:
if(data.size()<16)
{
error = String::Build("Unsufficient data, got ", data.size(), " bytes");
goto corrupt;
return niceNotifyError(String::Build("Unsufficient data, got ", data.size(), " bytes"));
}
if (data[0]!=0x42 || data[1]!=0x75 || data[2]!=0x54 || data[3]!=0x54)
{
error = "Invalid update format";
goto corrupt;
return niceNotifyError("Invalid update format");
}
uncompressedLength = (unsigned char)data[4];
@ -80,40 +81,28 @@ private:
uncompressedLength |= ((unsigned char)data[6])<<16;
uncompressedLength |= ((unsigned char)data[7])<<24;
char * res;
res = (char *)malloc(uncompressedLength);
if (!res)
{
error = String::Build("Unable to allocate ", uncompressedLength, " bytes of memory for decompression");
goto corrupt;
}
std::vector<char> res(uncompressedLength);
int dstate;
dstate = BZ2_bzBuffToBuffDecompress((char *)res, (unsigned *)&uncompressedLength, &data[8], data.size()-8, 0, 0);
dstate = BZ2_bzBuffToBuffDecompress(&res[0], (unsigned *)&uncompressedLength, &data[8], data.size()-8, 0, 0);
if (dstate)
{
error = String::Build("Unable to decompress update: ", dstate);
free(res);
goto corrupt;
return niceNotifyError(String::Build("Unable to decompress update: ", dstate));
}
notifyStatus("Applying update");
notifyProgress(-1);
Client::Ref().SetPref("version.update", true);
if (update_start(res, uncompressedLength))
prefs.Set("version.update", true);
if (update_start(&res[0], uncompressedLength))
{
Client::Ref().SetPref("version.update", false);
prefs.Set("version.update", false);
update_cleanup();
notifyError("Update failed - try downloading a new version.");
return false;
}
return true;
corrupt:
notifyError("Downloaded update is corrupted\n" + error);
return false;
}
};

View File

@ -12,7 +12,7 @@
#include "LuaSmartRef.h"
#include "PowderToy.h"
#include "client/Client.h"
#include "prefs/GlobalPrefs.h"
#include "common/Platform.h"
#include "graphics/Graphics.h"
#include "graphics/Renderer.h"
@ -1321,9 +1321,20 @@ int luatpt_setwindowsize(lua_State* l)
int kiosk = luaL_optint(l,2,0);
// TODO: handle this the same way as it's handled in PowderToySDL.cpp
// > maybe bind the maximum allowed scale to screen size somehow
if (scale < 1 || scale > 10) scale = 1;
if (kiosk!=1) kiosk = 0;
Client::Ref().SetPref("Scale", scale);
if (scale < 1 || scale > 10)
{
scale = 1;
}
if (kiosk!=1)
{
kiosk = 0;
}
{
auto &prefs = GlobalPrefs::Ref();
Prefs::DeferWrite dw(prefs);
prefs.Set("Scale", scale);
prefs.Set("Fullscreen", bool(kiosk));
}
ui::Engine::Ref().SetScale(scale);
ui::Engine::Ref().SetFullscreen(kiosk);
return 0;

View File

@ -44,6 +44,7 @@ else
'lua/PlainCommandInterface.cpp',
)
endif
subdir('prefs')
subdir('resampler')
subdir('simulation')
subdir('tasks')

11
src/prefs/GlobalPrefs.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "Prefs.h"
#include "common/ExplicitSingleton.h"
class GlobalPrefs : public Prefs, public ExplicitSingleton<GlobalPrefs>
{
public:
GlobalPrefs() : Prefs("powder.pref")
{
}
};

153
src/prefs/Prefs.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "Prefs.h"
#include "common/Platform.h"
#include "common/tpt-rand.h"
#include <fstream>
#include <iostream>
Prefs::Prefs(ByteString newPath) : path(newPath)
{
Read();
}
void Prefs::Read()
{
std::vector<char> data;
if (!Platform::ReadFile(data, path))
{
return;
}
Json::CharReaderBuilder rbuilder;
std::unique_ptr<Json::CharReader> const reader(rbuilder.newCharReader());
ByteString errs;
if (!reader->parse(&data[0], &data[0] + data.size(), &root, &errs))
{
std::cerr << errs << std::endl;
return;
}
backedByFile = true;
}
void Prefs::ShouldWrite()
{
shouldWrite = true;
Write();
}
void Prefs::Write()
{
if (deferWriteLevel)
{
return;
}
if (!shouldWrite)
{
return;
}
shouldWrite = false;
Json::StreamWriterBuilder wbuilder;
wbuilder["indentation"] = "\t";
ByteString data = Json::writeString(wbuilder, root);
if (!Platform::WriteFile(std::vector<char>(data.begin(), data.end()), path, true))
{
return;
}
backedByFile = true;
}
void Prefs::GrabDeferWriteLevel(DeferWriteTag)
{
deferWriteLevel += 1;
}
void Prefs::DropDeferWriteLevel(DeferWriteTag)
{
deferWriteLevel -= 1;
Write();
}
Json::Value Prefs::GetJson(const Json::Value &node, ByteString path)
{
if (node.type() != Json::objectValue)
{
return Json::nullValue;
}
auto split = path.SplitBy('.');
if (!split)
{
return node[path];
}
return GetJson(node[split.Before()], split.After());
}
void Prefs::SetJson(Json::Value &node, ByteString path, Json::Value value)
{
if (node.type() != Json::objectValue)
{
node = Json::objectValue;
}
auto split = path.SplitBy('.');
if (!split)
{
node[path] = value;
return;
}
SetJson(node[split.Before()], split.After(), value);
}
template<> Json::Value Prefs::Bipacker<int>::Pack (const int &value) { return Json::Value(value); }
template<> int Prefs::Bipacker<int>::Unpack(const Json::Value &value) { return value.asInt(); }
template<> Json::Value Prefs::Bipacker<unsigned int>::Pack (const unsigned int &value) { return Json::Value(value); }
template<> unsigned int Prefs::Bipacker<unsigned int>::Unpack(const Json::Value &value) { return value.asUInt(); }
template<> Json::Value Prefs::Bipacker<uint64_t>::Pack (const uint64_t &value) { return Json::Value(Json::UInt64(value)); }
template<> uint64_t Prefs::Bipacker<uint64_t>::Unpack(const Json::Value &value) { return value.asUInt64(); }
template<> Json::Value Prefs::Bipacker<float>::Pack (const float &value) { return Json::Value(value); }
template<> float Prefs::Bipacker<float>::Unpack(const Json::Value &value) { return value.asFloat(); }
template<> Json::Value Prefs::Bipacker<bool>::Pack (const bool &value) { return Json::Value(value); }
template<> bool Prefs::Bipacker<bool>::Unpack(const Json::Value &value) { return value.asBool(); }
template<> Json::Value Prefs::Bipacker<ByteString>::Pack (const ByteString &value) { return Json::Value(value); }
template<> ByteString Prefs::Bipacker<ByteString>::Unpack(const Json::Value &value) { return value.asString(); }
template<> Json::Value Prefs::Bipacker<String>::Pack (const String &value) { return Json::Value(value.ToUtf8()); }
template<> String Prefs::Bipacker<String>::Unpack(const Json::Value &value) { return ByteString(value.asString()).FromUtf8(); }
template<class Item>
struct Prefs::Bipacker<std::vector<Item>>
{
static Json::Value Pack(const std::vector<Item> &value);
static std::vector<Item> Unpack(const Json::Value &value);
};
template<class Item>
Json::Value Prefs::Bipacker<std::vector<Item>>::Pack(const std::vector<Item> &value)
{
Json::Value array = Json::arrayValue;
for (auto item : value)
{
array.append(Bipacker<Item>::Pack(item));
}
return array;
}
template<class Item>
std::vector<Item> Prefs::Bipacker<std::vector<Item>>::Unpack(const Json::Value &value)
{
std::vector<Item> array;
if (value.type() != Json::arrayValue)
{
throw std::exception();
}
for (auto &item : value)
{
array.push_back(Bipacker<Item>::Unpack(item));
}
return array;
}
template struct Prefs::Bipacker<std::vector<String>>;
template struct Prefs::Bipacker<std::vector<ByteString>>;
template struct Prefs::Bipacker<std::vector<unsigned int>>;

104
src/prefs/Prefs.h Normal file
View File

@ -0,0 +1,104 @@
#pragma once
#include "Config.h"
#include "common/String.h"
#include <json/json.h>
class Prefs
{
struct DeferWriteTag
{
};
Json::Value root;
static Json::Value GetJson(const Json::Value &node, ByteString path);
static void SetJson(Json::Value &node, ByteString path, Json::Value value);
template<class Type>
struct Bipacker
{
static Json::Value Pack(const Type &value);
static Type Unpack(const Json::Value &value);
};
void Read();
void Write();
void ShouldWrite();
unsigned int deferWriteLevel = 0;
bool backedByFile = false;
bool shouldWrite = false;
ByteString path;
Prefs(const Prefs &) = delete;
Prefs &operator =(const Prefs &) = delete;
public:
Prefs(ByteString path);
template<class Type>
Type Get(ByteString path, Type defaultValue) const
{
auto value = GetJson(root, path);
if (value != Json::nullValue)
{
try
{
return Bipacker<Type>::Unpack(value);
}
catch (const std::exception &e)
{
}
}
return defaultValue;
}
template<class Enum, class EnumBase = int>
Enum Get(ByteString path, Enum maxValue, Enum defaultValue) const
{
EnumBase value = Get(path, EnumBase(defaultValue));
if (value < 0 || value >= EnumBase(maxValue))
{
value = EnumBase(defaultValue);
}
return Enum(value);
}
template<class Type>
void Set(ByteString path, Type value)
{
SetJson(root, path, Bipacker<Type>::Pack(value));
ShouldWrite();
}
void Clear(ByteString path)
{
SetJson(root, path, Json::nullValue);
ShouldWrite();
}
void GrabDeferWriteLevel(DeferWriteTag);
void DropDeferWriteLevel(DeferWriteTag);
struct DeferWrite
{
Prefs &prefs;
DeferWrite(const DeferWrite &) = delete;
DeferWrite &operator =(const DeferWrite &) = delete;
DeferWrite(Prefs &newPrefs) : prefs(newPrefs)
{
prefs.GrabDeferWriteLevel({});
}
~DeferWrite()
{
prefs.DropDeferWriteLevel({});
}
};
bool BackedByFile() const
{
return backedByFile;
}
};

3
src/prefs/meson.build Normal file
View File

@ -0,0 +1,3 @@
powder_files += files(
'Prefs.cpp',
)

View File

@ -1,5 +1,4 @@
#include "Simulation.h"
#include "client/Client.h"
#include "ElementClasses.h"
#include "graphics/Renderer.h"
#include "gui/game/Brush.h"

View File

@ -1,5 +1,4 @@
#include "GOLString.h"
#include "client/Client.h"
int ParseGOLString(const String &value)
{