Prevent almost all HTTP requests from blocking
The ones that remain blocking are the ones that run on different threads; see Task, yet another big mess to clean up.
This commit is contained in:
parent
c2f8a7df25
commit
c73fa1bcdd
@ -18,6 +18,12 @@ inline std::pair<Signed, Signed> floorDiv(Signed a, Signed b)
|
||||
return { quo, rem };
|
||||
}
|
||||
|
||||
template<class Signed>
|
||||
inline std::pair<Signed, Signed> ceilDiv(Signed a, Signed b)
|
||||
{
|
||||
return floorDiv(a + b - Signed(1), b);
|
||||
}
|
||||
|
||||
//Linear interpolation
|
||||
template <typename T> inline T LinearInterpolate(T val1, T val2, T lowerCoord, T upperCoord, T coord)
|
||||
{
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "client/SaveFile.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/http/requestmanager/RequestManager.h"
|
||||
#include "client/http/GetSaveRequest.h"
|
||||
#include "client/http/GetSaveDataRequest.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include "simulation/SaveRenderer.h"
|
||||
@ -462,15 +464,31 @@ int main(int argc, char * argv[])
|
||||
}
|
||||
int saveId = saveIdPart.ToNumber<int>();
|
||||
|
||||
auto newSave = Client::Ref().GetSave(saveId, 0);
|
||||
if (!newSave)
|
||||
throw std::runtime_error("Could not load save info");
|
||||
auto saveData = Client::Ref().GetSaveData(saveId, 0);
|
||||
if (!saveData.size())
|
||||
throw std::runtime_error(("Could not load save\n" + Client::Ref().GetLastError()).ToUtf8());
|
||||
auto newGameSave = std::make_unique<GameSave>(std::move(saveData));
|
||||
newSave->SetGameSave(std::move(newGameSave));
|
||||
|
||||
auto getSave = std::make_unique<http::GetSaveRequest>(saveId, 0);
|
||||
getSave->Start();
|
||||
getSave->Wait();
|
||||
std::unique_ptr<SaveInfo> newSave;
|
||||
try
|
||||
{
|
||||
newSave = getSave->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
throw std::runtime_error("Could not load save info\n" + ByteString(ex.what()));
|
||||
}
|
||||
auto getSaveData = std::make_unique<http::GetSaveDataRequest>(saveId, 0);
|
||||
getSaveData->Start();
|
||||
getSaveData->Wait();
|
||||
std::unique_ptr<GameSave> saveData;
|
||||
try
|
||||
{
|
||||
saveData = std::make_unique<GameSave>(getSaveData->Finish());
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
throw std::runtime_error("Could not load save\n" + ByteString(ex.what()));
|
||||
}
|
||||
newSave->SetGameSave(std::move(saveData));
|
||||
gameController->LoadSave(std::move(newSave));
|
||||
}
|
||||
catch (std::exception & e)
|
||||
|
@ -1,6 +1,6 @@
|
||||
#include "Client.h"
|
||||
#include "prefs/GlobalPrefs.h"
|
||||
#include "client/http/Request.h"
|
||||
#include "client/http/StartupRequest.h"
|
||||
#include "ClientListener.h"
|
||||
#include "Format.h"
|
||||
#include "MD5.h"
|
||||
@ -13,7 +13,6 @@
|
||||
#include "graphics/Graphics.h"
|
||||
#include "prefs/Prefs.h"
|
||||
#include "lua/CommandInterface.h"
|
||||
#include "gui/preview/Comment.h"
|
||||
#include "Config.h"
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
@ -29,8 +28,6 @@
|
||||
|
||||
Client::Client():
|
||||
messageOfTheDay("Fetching the message of the day..."),
|
||||
versionCheckRequest(nullptr),
|
||||
alternateVersionCheckRequest(nullptr),
|
||||
usingAltUpdateServer(false),
|
||||
updateAvailable(false),
|
||||
authUser(0, "")
|
||||
@ -40,16 +37,7 @@ Client::Client():
|
||||
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")
|
||||
{
|
||||
authUser.UserElevation = User::ElevationAdmin;
|
||||
}
|
||||
if (elevation == "Mod")
|
||||
{
|
||||
authUser.UserElevation = User::ElevationModerator;
|
||||
}
|
||||
authUser.UserElevation = prefs.Get("User.Elevation", User::ElevationNone);
|
||||
firstRun = !prefs.BackedByFile();
|
||||
}
|
||||
|
||||
@ -88,24 +76,14 @@ void Client::Initialize()
|
||||
}
|
||||
|
||||
//Begin version check
|
||||
versionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, SERVER, "/Startup.json"));
|
||||
|
||||
if (authUser.UserID)
|
||||
{
|
||||
versionCheckRequest->AuthHeaders(ByteString::Build(authUser.UserID), authUser.SessionID);
|
||||
}
|
||||
versionCheckRequest = std::make_unique<http::StartupRequest>(false);
|
||||
versionCheckRequest->Start();
|
||||
|
||||
if constexpr (USE_UPDATESERVER)
|
||||
{
|
||||
// use an alternate update server
|
||||
alternateVersionCheckRequest = std::make_unique<http::Request>(ByteString::Build(SCHEME, UPDATESERVER, "/Startup.json"));
|
||||
usingAltUpdateServer = true;
|
||||
if (authUser.UserID)
|
||||
{
|
||||
alternateVersionCheckRequest->AuthHeaders(authUser.Username, "");
|
||||
}
|
||||
alternateVersionCheckRequest = std::make_unique<http::StartupRequest>(true);
|
||||
alternateVersionCheckRequest->Start();
|
||||
usingAltUpdateServer = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,203 +103,82 @@ String Client::GetMessageOfTheDay()
|
||||
return messageOfTheDay;
|
||||
}
|
||||
|
||||
void Client::AddServerNotification(std::pair<String, ByteString> notification)
|
||||
void Client::AddServerNotification(ServerNotification notification)
|
||||
{
|
||||
serverNotifications.push_back(notification);
|
||||
notifyNewNotification(notification);
|
||||
}
|
||||
|
||||
std::vector<std::pair<String, ByteString> > Client::GetServerNotifications()
|
||||
std::vector<ServerNotification> Client::GetServerNotifications()
|
||||
{
|
||||
return serverNotifications;
|
||||
}
|
||||
|
||||
RequestStatus Client::ParseServerReturn(ByteString &result, int status, bool json)
|
||||
{
|
||||
lastError = "";
|
||||
// no server response, return "Malformed Response"
|
||||
if (status == 200 && !result.size())
|
||||
{
|
||||
status = 603;
|
||||
}
|
||||
if (status == 302)
|
||||
return RequestOkay;
|
||||
if (status != 200)
|
||||
{
|
||||
lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
|
||||
return RequestFailure;
|
||||
}
|
||||
|
||||
if (json)
|
||||
{
|
||||
std::istringstream datastream(result);
|
||||
Json::Value root;
|
||||
|
||||
try
|
||||
{
|
||||
datastream >> root;
|
||||
// assume everything is fine if an empty [] is returned
|
||||
if (root.size() == 0)
|
||||
{
|
||||
return RequestOkay;
|
||||
}
|
||||
int status = root.get("Status", 1).asInt();
|
||||
if (status != 1)
|
||||
{
|
||||
lastError = ByteString(root.get("Error", "Unspecified Error").asString()).FromUtf8();
|
||||
return RequestFailure;
|
||||
}
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
// sometimes the server returns a 200 with the text "Error: 401"
|
||||
if (!strncmp(result.c_str(), "Error: ", 7))
|
||||
{
|
||||
status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
|
||||
lastError = String::Build("HTTP Error ", status, ": ", http::StatusText(status));
|
||||
return RequestFailure;
|
||||
}
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
return RequestFailure;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strncmp(result.c_str(), "OK", 2))
|
||||
{
|
||||
lastError = result.FromUtf8();
|
||||
return RequestFailure;
|
||||
}
|
||||
}
|
||||
return RequestOkay;
|
||||
}
|
||||
|
||||
void Client::Tick()
|
||||
{
|
||||
CheckUpdate(versionCheckRequest, true);
|
||||
CheckUpdate(alternateVersionCheckRequest, false);
|
||||
}
|
||||
|
||||
void Client::CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession)
|
||||
{
|
||||
//Check status on version check request
|
||||
if (updateRequest && updateRequest->CheckDone())
|
||||
auto applyUpdateInfo = false;
|
||||
if (versionCheckRequest && versionCheckRequest->CheckDone())
|
||||
{
|
||||
auto [ status, data ] = updateRequest->Finish();
|
||||
|
||||
if (checkSession && status == 618)
|
||||
if (versionCheckRequest->StatusCode() == 618)
|
||||
{
|
||||
AddServerNotification({ "Failed to load SSL certificates", ByteString(SCHEME) + "powdertoy.co.uk/FAQ.html" });
|
||||
}
|
||||
|
||||
if (status != 200)
|
||||
{
|
||||
//free(data);
|
||||
if (usingAltUpdateServer && !checkSession)
|
||||
this->messageOfTheDay = String::Build("HTTP Error ", status, " while checking for updates: ", http::StatusText(status));
|
||||
else
|
||||
this->messageOfTheDay = String::Build("HTTP Error ", status, " while fetching MotD");
|
||||
}
|
||||
else if(data.size())
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
|
||||
try
|
||||
{
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
//Check session
|
||||
if (checkSession)
|
||||
{
|
||||
if (!objDocument["Session"].asBool())
|
||||
auto info = versionCheckRequest->Finish();
|
||||
if (!info.sessionGood)
|
||||
{
|
||||
SetAuthUser(User(0, ""));
|
||||
}
|
||||
if (!usingAltUpdateServer)
|
||||
{
|
||||
updateInfo = info.updateInfo;
|
||||
applyUpdateInfo = true;
|
||||
messageOfTheDay = info.messageOfTheDay;
|
||||
}
|
||||
|
||||
//Notifications from server
|
||||
Json::Value notificationsArray = objDocument["Notifications"];
|
||||
for (Json::UInt j = 0; j < notificationsArray.size(); j++)
|
||||
for (auto ¬ification : info.notifications)
|
||||
{
|
||||
ByteString notificationLink = notificationsArray[j]["Link"].asString();
|
||||
String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
|
||||
|
||||
std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
|
||||
AddServerNotification(item);
|
||||
}
|
||||
|
||||
|
||||
//MOTD
|
||||
if (!usingAltUpdateServer || !checkSession)
|
||||
{
|
||||
this->messageOfTheDay = ByteString(objDocument["MessageOfTheDay"].asString()).FromUtf8();
|
||||
notifyMessageOfTheDay();
|
||||
|
||||
if constexpr (!IGNORE_UPDATES)
|
||||
{
|
||||
//Check for updates
|
||||
Json::Value versions = objDocument["Updates"];
|
||||
if constexpr (!SNAPSHOT)
|
||||
{
|
||||
Json::Value stableVersion = versions["Stable"];
|
||||
int stableMajor = stableVersion["Major"].asInt();
|
||||
int stableMinor = stableVersion["Minor"].asInt();
|
||||
int stableBuild = stableVersion["Build"].asInt();
|
||||
ByteString stableFile = stableVersion["File"].asString();
|
||||
String stableChangelog = ByteString(stableVersion["Changelog"].asString()).FromUtf8();
|
||||
if (stableBuild > BUILD_NUM)
|
||||
{
|
||||
updateAvailable = true;
|
||||
updateInfo = UpdateInfo(stableMajor, stableMinor, stableBuild, stableFile, stableChangelog, UpdateInfo::Stable);
|
||||
AddServerNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateAvailable)
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
Json::Value betaVersion = versions["Beta"];
|
||||
int betaMajor = betaVersion["Major"].asInt();
|
||||
int betaMinor = betaVersion["Minor"].asInt();
|
||||
int betaBuild = betaVersion["Build"].asInt();
|
||||
ByteString betaFile = betaVersion["File"].asString();
|
||||
String betaChangelog = ByteString(betaVersion["Changelog"].asString()).FromUtf8();
|
||||
if (betaBuild > BUILD_NUM)
|
||||
if (!usingAltUpdateServer)
|
||||
{
|
||||
updateAvailable = true;
|
||||
updateInfo = UpdateInfo(betaMajor, betaMinor, betaBuild, betaFile, betaChangelog, UpdateInfo::Beta);
|
||||
messageOfTheDay = ByteString::Build("Error while fetching MotD: ", ex.what()).FromUtf8();
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (SNAPSHOT || MOD)
|
||||
versionCheckRequest.reset();
|
||||
}
|
||||
if (alternateVersionCheckRequest && alternateVersionCheckRequest->CheckDone())
|
||||
{
|
||||
Json::Value snapshotVersion = versions["Snapshot"];
|
||||
int snapshotSnapshot = snapshotVersion["Snapshot"].asInt();
|
||||
ByteString snapshotFile = snapshotVersion["File"].asString();
|
||||
String snapshotChangelog = ByteString(snapshotVersion["Changelog"].asString()).FromUtf8();
|
||||
if (snapshotSnapshot > SNAPSHOT_ID)
|
||||
try
|
||||
{
|
||||
updateAvailable = true;
|
||||
updateInfo = UpdateInfo(snapshotSnapshot, snapshotFile, snapshotChangelog, UpdateInfo::Snapshot);
|
||||
auto info = alternateVersionCheckRequest->Finish();
|
||||
updateInfo = info.updateInfo;
|
||||
applyUpdateInfo = true;
|
||||
messageOfTheDay = info.messageOfTheDay;
|
||||
for (auto ¬ification : info.notifications)
|
||||
{
|
||||
AddServerNotification(notification);
|
||||
}
|
||||
}
|
||||
|
||||
if(updateAvailable)
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
messageOfTheDay = ByteString::Build("Error while checking for updates: ", ex.what()).FromUtf8();
|
||||
}
|
||||
alternateVersionCheckRequest.reset();
|
||||
}
|
||||
if (applyUpdateInfo && !IGNORE_UPDATES)
|
||||
{
|
||||
if (updateInfo)
|
||||
{
|
||||
notifyUpdateAvailable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception & e)
|
||||
{
|
||||
//Do nothing
|
||||
}
|
||||
}
|
||||
updateRequest.reset();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateInfo Client::GetUpdateInfo()
|
||||
std::optional<UpdateInfo> Client::GetUpdateInfo()
|
||||
{
|
||||
return updateInfo;
|
||||
}
|
||||
@ -350,7 +207,7 @@ void Client::notifyAuthUserChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void Client::notifyNewNotification(std::pair<String, ByteString> notification)
|
||||
void Client::notifyNewNotification(ServerNotification notification)
|
||||
{
|
||||
for (std::vector<ClientListener*>::iterator iterator = listeners.begin(), end = listeners.end(); iterator != end; ++iterator)
|
||||
{
|
||||
@ -391,16 +248,7 @@ void Client::SetAuthUser(User user)
|
||||
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);
|
||||
prefs.Set("User.Elevation", authUser.UserElevation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -415,65 +263,6 @@ User Client::GetAuthUser()
|
||||
return authUser;
|
||||
}
|
||||
|
||||
RequestStatus Client::UploadSave(SaveInfo & save)
|
||||
{
|
||||
lastError = "";
|
||||
int dataStatus;
|
||||
ByteString data;
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
if (authUser.UserID)
|
||||
{
|
||||
if (!save.GetGameSave())
|
||||
{
|
||||
lastError = "Empty game save";
|
||||
return RequestFailure;
|
||||
}
|
||||
|
||||
save.SetID(0);
|
||||
|
||||
auto [ fromNewerVersion, gameData ] = save.GetGameSave()->Serialise();
|
||||
(void)fromNewerVersion;
|
||||
|
||||
if (!gameData.size())
|
||||
{
|
||||
lastError = "Cannot serialize game save";
|
||||
return RequestFailure;
|
||||
}
|
||||
else if (ALLOW_FAKE_NEWER_VERSION && fromNewerVersion && save.GetPublished())
|
||||
{
|
||||
lastError = "Cannot publish save, incompatible with latest release version.";
|
||||
return RequestFailure;
|
||||
}
|
||||
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Save.api"), userID, authUser.SessionID, {
|
||||
{ "Name", save.GetName().ToUtf8() },
|
||||
{ "Description", save.GetDescription().ToUtf8() },
|
||||
{ "Data:save.bin", ByteString(gameData.begin(), gameData.end()) },
|
||||
{ "Publish", save.GetPublished() ? "Public" : "Private" },
|
||||
{ "Key", authUser.SessionKey }
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, false);
|
||||
if (ret == RequestOkay)
|
||||
{
|
||||
int saveID = ByteString(data.begin() + 3, data.end()).ToNumber<int>();
|
||||
if (!saveID)
|
||||
{
|
||||
lastError = "Server did not return Save ID";
|
||||
ret = RequestFailure;
|
||||
}
|
||||
else
|
||||
save.SetID(saveID);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Client::MoveStampToFront(ByteString stampID)
|
||||
{
|
||||
auto it = std::find(stampIDs.begin(), stampIDs.end(), stampID);
|
||||
@ -618,312 +407,6 @@ const std::vector<ByteString> &Client::GetStamps() const
|
||||
return stampIDs;
|
||||
}
|
||||
|
||||
RequestStatus Client::ExecVote(int saveID, int direction)
|
||||
{
|
||||
lastError = "";
|
||||
int dataStatus;
|
||||
ByteString data;
|
||||
|
||||
if (authUser.UserID)
|
||||
{
|
||||
ByteString saveIDText = ByteString::Build(saveID);
|
||||
ByteString userIDText = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(ByteString::Build(SCHEME, SERVER, "/Vote.api"), userIDText, authUser.SessionID, {
|
||||
{ "ID", saveIDText },
|
||||
{ "Action", direction ? (direction == 1 ? "Up" : "Down") : "Reset" },
|
||||
{ "Key", authUser.SessionKey }
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<char> Client::GetSaveData(int saveID, int saveDate)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString urlStr;
|
||||
if (saveDate)
|
||||
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
|
||||
else
|
||||
urlStr = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
|
||||
|
||||
auto [ dataStatus, data ] = http::Request::Simple(urlStr);
|
||||
|
||||
// will always return failure
|
||||
ParseServerReturn(data, dataStatus, false);
|
||||
if (data.size() && dataStatus == 200)
|
||||
{
|
||||
return std::vector<char>(data.begin(), data.end());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
LoginStatus Client::Login(ByteString username, ByteString password, User & user)
|
||||
{
|
||||
lastError = "";
|
||||
|
||||
user.UserID = 0;
|
||||
user.Username = "";
|
||||
user.SessionID = "";
|
||||
user.SessionKey = "";
|
||||
|
||||
auto [ dataStatus, data ] = http::Request::Simple(ByteString::Build("https://", SERVER, "/Login.json"), {
|
||||
{ "name", username },
|
||||
{ "pass", password },
|
||||
});
|
||||
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
if (ret == RequestOkay)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
ByteString usernameTemp = objDocument["Username"].asString();
|
||||
int userIDTemp = objDocument["UserID"].asInt();
|
||||
ByteString sessionIDTemp = objDocument["SessionID"].asString();
|
||||
ByteString sessionKeyTemp = objDocument["SessionKey"].asString();
|
||||
ByteString userElevationTemp = objDocument["Elevation"].asString();
|
||||
|
||||
Json::Value notificationsArray = objDocument["Notifications"];
|
||||
for (Json::UInt j = 0; j < notificationsArray.size(); j++)
|
||||
{
|
||||
ByteString notificationLink = notificationsArray[j]["Link"].asString();
|
||||
String notificationText = ByteString(notificationsArray[j]["Text"].asString()).FromUtf8();
|
||||
|
||||
std::pair<String, ByteString> item = std::pair<String, ByteString>(notificationText, notificationLink);
|
||||
AddServerNotification(item);
|
||||
}
|
||||
|
||||
user.Username = usernameTemp;
|
||||
user.UserID = userIDTemp;
|
||||
user.SessionID = sessionIDTemp;
|
||||
user.SessionKey = sessionKeyTemp;
|
||||
ByteString userElevation = userElevationTemp;
|
||||
if(userElevation == "Admin")
|
||||
user.UserElevation = User::ElevationAdmin;
|
||||
else if(userElevation == "Mod")
|
||||
user.UserElevation = User::ElevationModerator;
|
||||
else
|
||||
user.UserElevation= User::ElevationNone;
|
||||
return LoginOkay;
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
return LoginError;
|
||||
}
|
||||
}
|
||||
return LoginError;
|
||||
}
|
||||
|
||||
RequestStatus Client::DeleteSave(int saveID)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString data;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete&Key=", authUser.SessionKey);
|
||||
int dataStatus;
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RequestStatus Client::AddComment(int saveID, String comment)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID);
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
|
||||
{ "Comment", comment.ToUtf8() },
|
||||
{ "Key", authUser.SessionKey }
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RequestStatus Client::FavouriteSave(int saveID, bool favourite)
|
||||
{
|
||||
lastError = "";
|
||||
ByteStringBuilder urlStream;
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
urlStream << SCHEME << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << authUser.SessionKey;
|
||||
if(!favourite)
|
||||
urlStream << "&Mode=Remove";
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RequestStatus Client::ReportSave(int saveID, String message)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Report.json?ID=", saveID, "&Key=", authUser.SessionKey);
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
|
||||
{ "Reason", message.ToUtf8() },
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RequestStatus Client::UnpublishSave(int saveID)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Unpublish&Key=", authUser.SessionKey);
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RequestStatus Client::PublishSave(int saveID)
|
||||
{
|
||||
lastError = "";
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/View.json?ID=", saveID, "&Key=", authUser.SessionKey);
|
||||
if (authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID, {
|
||||
{ "ActionPublish", "bagels" },
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return RequestFailure;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<SaveInfo> Client::GetSave(int saveID, int saveDate)
|
||||
{
|
||||
lastError = "";
|
||||
ByteStringBuilder urlStream;
|
||||
urlStream << SCHEME << SERVER << "/Browse/View.json?ID=" << saveID;
|
||||
if(saveDate)
|
||||
{
|
||||
urlStream << "&Date=" << saveDate;
|
||||
}
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(urlStream.Build(), userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::tie(dataStatus, data) = http::Request::Simple(urlStream.Build());
|
||||
}
|
||||
if(dataStatus == 200 && data.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
int tempID = objDocument["ID"].asInt();
|
||||
int tempScoreUp = objDocument["ScoreUp"].asInt();
|
||||
int tempScoreDown = objDocument["ScoreDown"].asInt();
|
||||
int tempMyScore = objDocument["ScoreMine"].asInt();
|
||||
ByteString tempUsername = objDocument["Username"].asString();
|
||||
String tempName = ByteString(objDocument["Name"].asString()).FromUtf8();
|
||||
String tempDescription = ByteString(objDocument["Description"].asString()).FromUtf8();
|
||||
int tempCreatedDate = objDocument["DateCreated"].asInt();
|
||||
int tempUpdatedDate = objDocument["Date"].asInt();
|
||||
bool tempPublished = objDocument["Published"].asBool();
|
||||
bool tempFavourite = objDocument["Favourite"].asBool();
|
||||
int tempComments = objDocument["Comments"].asInt();
|
||||
int tempViews = objDocument["Views"].asInt();
|
||||
int tempVersion = objDocument["Version"].asInt();
|
||||
|
||||
Json::Value tagsArray = objDocument["Tags"];
|
||||
std::list<ByteString> tempTags;
|
||||
for (Json::UInt j = 0; j < tagsArray.size(); j++)
|
||||
tempTags.push_back(tagsArray[j].asString());
|
||||
|
||||
auto tempSave = std::make_unique<SaveInfo>(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp,
|
||||
tempScoreDown, tempMyScore, tempUsername, tempName,
|
||||
tempDescription, tempPublished, tempTags);
|
||||
tempSave->Comments = tempComments;
|
||||
tempSave->Favourite = tempFavourite;
|
||||
tempSave->Views = tempViews;
|
||||
tempSave->Version = tempVersion;
|
||||
return tempSave;
|
||||
}
|
||||
catch (std::exception & e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = http::StatusText(dataStatus);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SaveFile> Client::LoadSaveFile(ByteString filename)
|
||||
{
|
||||
ByteString err;
|
||||
@ -964,84 +447,6 @@ std::unique_ptr<SaveFile> Client::LoadSaveFile(ByteString filename)
|
||||
return file;
|
||||
}
|
||||
|
||||
std::list<ByteString> * Client::RemoveTag(int saveID, ByteString tag)
|
||||
{
|
||||
lastError = "";
|
||||
std::list<ByteString> * tags = NULL;
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return NULL;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
if (ret == RequestOkay)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value responseObject;
|
||||
dataStream >> responseObject;
|
||||
|
||||
Json::Value tagsArray = responseObject["Tags"];
|
||||
tags = new std::list<ByteString>();
|
||||
for (Json::UInt j = 0; j < tagsArray.size(); j++)
|
||||
tags->push_back(tagsArray[j].asString());
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
std::list<ByteString> * Client::AddTag(int saveID, ByteString tag)
|
||||
{
|
||||
lastError = "";
|
||||
std::list<ByteString> * tags = NULL;
|
||||
ByteString data;
|
||||
int dataStatus;
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag, "&Key=", authUser.SessionKey);
|
||||
if(authUser.UserID)
|
||||
{
|
||||
ByteString userID = ByteString::Build(authUser.UserID);
|
||||
std::tie(dataStatus, data) = http::Request::SimpleAuth(url, userID, authUser.SessionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = "Not authenticated";
|
||||
return NULL;
|
||||
}
|
||||
RequestStatus ret = ParseServerReturn(data, dataStatus, true);
|
||||
if (ret == RequestOkay)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value responseObject;
|
||||
dataStream >> responseObject;
|
||||
|
||||
Json::Value tagsArray = responseObject["Tags"];
|
||||
tags = new std::list<ByteString>();
|
||||
for (Json::UInt j = 0; j < tagsArray.size(); j++)
|
||||
tags->push_back(tagsArray[j].asString());
|
||||
}
|
||||
catch (std::exception & e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
// stamp-specific wrapper for MergeAuthorInfo
|
||||
// also used for clipboard and lua stamps
|
||||
void Client::MergeStampAuthorInfo(Json::Value stampAuthors)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include "common/ExplicitSingleton.h"
|
||||
#include "StartupInfo.h"
|
||||
#include "User.h"
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
@ -10,53 +11,27 @@
|
||||
|
||||
class SaveInfo;
|
||||
class SaveFile;
|
||||
class SaveComment;
|
||||
class GameSave;
|
||||
class VideoBuffer;
|
||||
|
||||
enum LoginStatus {
|
||||
LoginOkay, LoginError
|
||||
};
|
||||
|
||||
enum RequestStatus {
|
||||
RequestOkay, RequestFailure
|
||||
};
|
||||
|
||||
class UpdateInfo
|
||||
{
|
||||
public:
|
||||
enum BuildType { Stable, Beta, Snapshot };
|
||||
ByteString File;
|
||||
String Changelog;
|
||||
int Major;
|
||||
int Minor;
|
||||
int Build;
|
||||
int Time;
|
||||
BuildType Type;
|
||||
UpdateInfo() : File(""), Changelog(""), Major(0), Minor(0), Build(0), Time(0), Type(Stable) {}
|
||||
UpdateInfo(int major, int minor, int build, ByteString file, String changelog, BuildType type) : File(file), Changelog(changelog), Major(major), Minor(minor), Build(build), Time(0), Type(type) {}
|
||||
UpdateInfo(int time, ByteString file, String changelog, BuildType type) : File(file), Changelog(changelog), Major(0), Minor(0), Build(0), Time(time), Type(type) {}
|
||||
};
|
||||
|
||||
class Prefs;
|
||||
class RequestListener;
|
||||
class ClientListener;
|
||||
namespace http
|
||||
{
|
||||
class Request;
|
||||
class StartupRequest;
|
||||
}
|
||||
class Client: public ExplicitSingleton<Client> {
|
||||
private:
|
||||
String messageOfTheDay;
|
||||
std::vector<std::pair<String, ByteString> > serverNotifications;
|
||||
std::vector<ServerNotification> serverNotifications;
|
||||
|
||||
std::unique_ptr<http::Request> versionCheckRequest;
|
||||
std::unique_ptr<http::Request> alternateVersionCheckRequest;
|
||||
std::unique_ptr<http::StartupRequest> versionCheckRequest;
|
||||
std::unique_ptr<http::StartupRequest> alternateVersionCheckRequest;
|
||||
bool usingAltUpdateServer;
|
||||
bool updateAvailable;
|
||||
UpdateInfo updateInfo;
|
||||
std::optional<UpdateInfo> updateInfo;
|
||||
|
||||
String lastError;
|
||||
bool firstRun;
|
||||
|
||||
std::vector<ByteString> stampIDs;
|
||||
@ -69,7 +44,7 @@ private:
|
||||
void notifyUpdateAvailable();
|
||||
void notifyAuthUserChanged();
|
||||
void notifyMessageOfTheDay();
|
||||
void notifyNewNotification(std::pair<String, ByteString> notification);
|
||||
void notifyNewNotification(ServerNotification notification);
|
||||
|
||||
// Save stealing info
|
||||
Json::Value authors;
|
||||
@ -91,7 +66,7 @@ public:
|
||||
void ClearAuthorInfo() { authors.clear(); }
|
||||
bool IsAuthorsEmpty() { return authors.size() == 0; }
|
||||
|
||||
UpdateInfo GetUpdateInfo();
|
||||
std::optional<UpdateInfo> GetUpdateInfo();
|
||||
|
||||
Client();
|
||||
~Client();
|
||||
@ -99,8 +74,8 @@ public:
|
||||
ByteString FileOpenDialogue();
|
||||
//std::string FileSaveDialogue();
|
||||
|
||||
void AddServerNotification(std::pair<String, ByteString> notification);
|
||||
std::vector<std::pair<String, ByteString> > GetServerNotifications();
|
||||
void AddServerNotification(ServerNotification notification);
|
||||
std::vector<ServerNotification> GetServerNotifications();
|
||||
|
||||
void SetMessageOfTheDay(String message);
|
||||
String GetMessageOfTheDay();
|
||||
@ -111,9 +86,6 @@ public:
|
||||
void AddListener(ClientListener * listener);
|
||||
void RemoveListener(ClientListener * listener);
|
||||
|
||||
RequestStatus ExecVote(int saveID, int direction);
|
||||
RequestStatus UploadSave(SaveInfo & save);
|
||||
|
||||
std::unique_ptr<SaveFile> GetStamp(ByteString stampID);
|
||||
void DeleteStamp(ByteString stampID);
|
||||
ByteString AddStamp(std::unique_ptr<GameSave> saveData);
|
||||
@ -121,30 +93,11 @@ public:
|
||||
const std::vector<ByteString> &GetStamps() const;
|
||||
void MoveStampToFront(ByteString stampID);
|
||||
|
||||
RequestStatus AddComment(int saveID, String comment);
|
||||
|
||||
std::vector<char> GetSaveData(int saveID, int saveDate);
|
||||
|
||||
LoginStatus Login(ByteString username, ByteString password, User & user);
|
||||
|
||||
std::unique_ptr<SaveInfo> GetSave(int saveID, int saveDate);
|
||||
std::unique_ptr<SaveFile> LoadSaveFile(ByteString filename);
|
||||
|
||||
RequestStatus DeleteSave(int saveID);
|
||||
RequestStatus ReportSave(int saveID, String message);
|
||||
RequestStatus UnpublishSave(int saveID);
|
||||
RequestStatus PublishSave(int saveID);
|
||||
RequestStatus FavouriteSave(int saveID, bool favourite);
|
||||
void SetAuthUser(User user);
|
||||
User GetAuthUser();
|
||||
std::list<ByteString> * RemoveTag(int saveID, ByteString tag); //TODO RequestStatus
|
||||
std::list<ByteString> * AddTag(int saveID, ByteString tag);
|
||||
String GetLastError() {
|
||||
return lastError;
|
||||
}
|
||||
RequestStatus ParseServerReturn(ByteString &result, int status, bool json);
|
||||
void Tick();
|
||||
void CheckUpdate(std::unique_ptr<http::Request> &updateRequest, bool checkSession);
|
||||
|
||||
String DoMigration(ByteString fromDir, ByteString toDir);
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include "client/ServerNotification.h"
|
||||
|
||||
class Client;
|
||||
class ClientListener
|
||||
@ -11,6 +12,6 @@ public:
|
||||
virtual void NotifyUpdateAvailable(Client * sender) {}
|
||||
virtual void NotifyAuthUserChanged(Client * sender) {}
|
||||
virtual void NotifyMessageOfTheDay(Client * sender) {}
|
||||
virtual void NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification) {}
|
||||
virtual void NotifyNewNotification(Client * sender, ServerNotification notification) {}
|
||||
};
|
||||
|
||||
|
11
src/client/Comment.h
Normal file
11
src/client/Comment.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "User.h"
|
||||
|
||||
struct Comment
|
||||
{
|
||||
ByteString authorName;
|
||||
User::Elevation authorElevation;
|
||||
bool authorIsSelf;
|
||||
bool authorIsBanned;
|
||||
String content;
|
||||
};
|
10
src/client/LoginInfo.h
Normal file
10
src/client/LoginInfo.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "User.h"
|
||||
#include "ServerNotification.h"
|
||||
#include <vector>
|
||||
|
||||
struct LoginInfo
|
||||
{
|
||||
User user;
|
||||
std::vector<ServerNotification> notifications;
|
||||
};
|
@ -45,7 +45,7 @@ void SaveInfo::SetName(String name)
|
||||
{
|
||||
this->name = name;
|
||||
}
|
||||
String SaveInfo::GetName()
|
||||
const String &SaveInfo::GetName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
@ -54,7 +54,7 @@ void SaveInfo::SetDescription(String description)
|
||||
{
|
||||
Description = description;
|
||||
}
|
||||
String SaveInfo::GetDescription()
|
||||
const String &SaveInfo::GetDescription() const
|
||||
{
|
||||
return Description;
|
||||
}
|
||||
|
@ -37,10 +37,10 @@ public:
|
||||
SaveInfo(int _id, int _createdDate, int _updatedDate, int _votesUp, int _votesDown, int _vote, ByteString _userName, String _name, String description_, bool published_, std::list<ByteString> tags);
|
||||
|
||||
void SetName(String name);
|
||||
String GetName();
|
||||
const String &GetName() const;
|
||||
|
||||
void SetDescription(String description);
|
||||
String GetDescription();
|
||||
const String &GetDescription() const;
|
||||
|
||||
void SetPublished(bool published);
|
||||
bool GetPublished() const;
|
||||
|
17
src/client/Search.h
Normal file
17
src/client/Search.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace http
|
||||
{
|
||||
enum Category
|
||||
{
|
||||
categoryNone,
|
||||
categoryMyOwn,
|
||||
categoryFavourites,
|
||||
};
|
||||
|
||||
enum Sort
|
||||
{
|
||||
sortByVotes,
|
||||
sortByDate,
|
||||
};
|
||||
}
|
8
src/client/ServerNotification.h
Normal file
8
src/client/ServerNotification.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
|
||||
struct ServerNotification
|
||||
{
|
||||
String text;
|
||||
ByteString link;
|
||||
};
|
29
src/client/StartupInfo.h
Normal file
29
src/client/StartupInfo.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include "ServerNotification.h"
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
struct UpdateInfo
|
||||
{
|
||||
enum Channel
|
||||
{
|
||||
channelStable,
|
||||
channelBeta,
|
||||
channelSnapshot,
|
||||
};
|
||||
Channel channel;
|
||||
ByteString file;
|
||||
String changeLog;
|
||||
int major = 0;
|
||||
int minor = 0;
|
||||
int build = 0;
|
||||
};
|
||||
|
||||
struct StartupInfo
|
||||
{
|
||||
bool sessionGood = false;
|
||||
String messageOfTheDay;
|
||||
std::vector<ServerNotification> notifications;
|
||||
std::optional<UpdateInfo> updateInfo;
|
||||
};
|
32
src/client/User.cpp
Normal file
32
src/client/User.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include "User.h"
|
||||
|
||||
static const std::vector<std::pair<User::Elevation, ByteString>> elevationStrings = {
|
||||
{ User::ElevationAdmin , "Admin" },
|
||||
{ User::ElevationMod , "Mod" },
|
||||
{ User::ElevationHalfMod, "HalfMod" },
|
||||
{ User::ElevationNone , "None" },
|
||||
};
|
||||
|
||||
User::Elevation User::ElevationFromString(ByteString str)
|
||||
{
|
||||
auto it = std::find_if(elevationStrings.begin(), elevationStrings.end(), [&str](auto &item) {
|
||||
return item.second == str;
|
||||
});
|
||||
if (it != elevationStrings.end())
|
||||
{
|
||||
return it->first;
|
||||
}
|
||||
return ElevationNone;
|
||||
}
|
||||
|
||||
ByteString User::ElevationToString(Elevation elevation)
|
||||
{
|
||||
auto it = std::find_if(elevationStrings.begin(), elevationStrings.end(), [elevation](auto &item) {
|
||||
return item.first == elevation;
|
||||
});
|
||||
if (it != elevationStrings.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
return "None";
|
||||
}
|
@ -7,8 +7,14 @@ class User
|
||||
public:
|
||||
enum Elevation
|
||||
{
|
||||
ElevationAdmin, ElevationModerator, ElevationNone
|
||||
ElevationNone,
|
||||
ElevationHalfMod,
|
||||
ElevationMod,
|
||||
ElevationAdmin,
|
||||
};
|
||||
static Elevation ElevationFromString(ByteString str);
|
||||
static ByteString ElevationToString(Elevation elevation);
|
||||
|
||||
int UserID;
|
||||
ByteString Username;
|
||||
ByteString SessionID;
|
||||
|
@ -1,35 +1,36 @@
|
||||
#include "APIRequest.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
APIRequest::APIRequest(ByteString url) : Request(url)
|
||||
APIRequest::APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus) : Request(url), checkStatus(newCheckStatus)
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
if (authMode == authRequire && !user.UserID)
|
||||
{
|
||||
FailEarly("Not authenticated");
|
||||
return;
|
||||
}
|
||||
if (authMode != authOmit && user.UserID)
|
||||
{
|
||||
User user = Client::Ref().GetAuthUser();
|
||||
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
|
||||
}
|
||||
}
|
||||
|
||||
APIRequest::Result APIRequest::Finish()
|
||||
Json::Value APIRequest::Finish()
|
||||
{
|
||||
Result result;
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, checkStatus ? responseJson : responseData);
|
||||
Json::Value document;
|
||||
try
|
||||
{
|
||||
ByteString data;
|
||||
std::tie(result.status, data) = Request::Finish();
|
||||
Client::Ref().ParseServerReturn(data, result.status, true);
|
||||
if (result.status == 200 && data.size())
|
||||
std::istringstream ss(data);
|
||||
ss >> document;
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
result.document = std::unique_ptr<Json::Value>(new Json::Value(objDocument));
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
}
|
||||
catch (std::exception & e)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
return document;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,22 +2,22 @@
|
||||
#include "Request.h"
|
||||
#include "common/String.h"
|
||||
#include <json/json.h>
|
||||
#include <memory>
|
||||
#include <map>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class APIRequest : public Request
|
||||
{
|
||||
bool checkStatus;
|
||||
|
||||
public:
|
||||
struct Result
|
||||
enum AuthMode
|
||||
{
|
||||
int status;
|
||||
std::unique_ptr<Json::Value> document;
|
||||
authRequire,
|
||||
authUse,
|
||||
authOmit,
|
||||
};
|
||||
APIRequest(ByteString url, AuthMode authMode, bool newCheckStatus);
|
||||
|
||||
APIRequest(ByteString url);
|
||||
|
||||
Result Finish();
|
||||
Json::Value Finish();
|
||||
};
|
||||
}
|
||||
|
21
src/client/http/AddCommentRequest.cpp
Normal file
21
src/client/http/AddCommentRequest.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "AddCommentRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
AddCommentRequest::AddCommentRequest(int saveID, String comment) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID), authRequire, true)
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
AddPostData(FormData{
|
||||
{ "Comment", comment.ToUtf8() },
|
||||
{ "Key", user.SessionKey },
|
||||
});
|
||||
}
|
||||
|
||||
void AddCommentRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
13
src/client/http/AddCommentRequest.h
Normal file
13
src/client/http/AddCommentRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class AddCommentRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
AddCommentRequest(int saveID, String comment);
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
29
src/client/http/AddTagRequest.cpp
Normal file
29
src/client/http/AddTagRequest.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "AddTagRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
AddTagRequest::AddTagRequest(int saveID, ByteString tag) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=add&ID=", saveID, "&Tag=", tag, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
}
|
||||
|
||||
std::list<ByteString> AddTagRequest::Finish()
|
||||
{
|
||||
auto result = APIRequest::Finish();
|
||||
std::list<ByteString> tags;
|
||||
try
|
||||
{
|
||||
for (auto &tag : result["Tags"])
|
||||
{
|
||||
tags.push_back(tag.asString());
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
}
|
14
src/client/http/AddTagRequest.h
Normal file
14
src/client/http/AddTagRequest.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
#include <list>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class AddTagRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
AddTagRequest(int saveID, ByteString tag);
|
||||
|
||||
std::list<ByteString> Finish();
|
||||
};
|
||||
}
|
16
src/client/http/DeleteSaveRequest.cpp
Normal file
16
src/client/http/DeleteSaveRequest.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "DeleteSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
DeleteSaveRequest::DeleteSaveRequest(int saveID) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Delete&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
}
|
||||
|
||||
void DeleteSaveRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
13
src/client/http/DeleteSaveRequest.h
Normal file
13
src/client/http/DeleteSaveRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class DeleteSaveRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
DeleteSaveRequest(int saveID);
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
30
src/client/http/ExecVoteRequest.cpp
Normal file
30
src/client/http/ExecVoteRequest.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "ExecVoteRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
ExecVoteRequest::ExecVoteRequest(int saveID, int newDirection) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Vote.api"), authRequire, false),
|
||||
direction(newDirection)
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
if (!user.UserID)
|
||||
{
|
||||
FailEarly("Not authenticated");
|
||||
return;
|
||||
}
|
||||
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
|
||||
AddPostData(FormData{
|
||||
{ "ID", ByteString::Build(saveID) },
|
||||
{ "Action", direction ? (direction == 1 ? "Up" : "Down") : "Reset" },
|
||||
{ "Key", user.SessionKey },
|
||||
});
|
||||
}
|
||||
|
||||
void ExecVoteRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseOk);
|
||||
}
|
||||
}
|
20
src/client/http/ExecVoteRequest.h
Normal file
20
src/client/http/ExecVoteRequest.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class ExecVoteRequest : public APIRequest
|
||||
{
|
||||
int direction;
|
||||
|
||||
public:
|
||||
ExecVoteRequest(int saveID, int newDirection);
|
||||
|
||||
void Finish();
|
||||
|
||||
int Direction() const
|
||||
{
|
||||
return direction;
|
||||
}
|
||||
};
|
||||
}
|
28
src/client/http/FavouriteSaveRequest.cpp
Normal file
28
src/client/http/FavouriteSaveRequest.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "FavouriteSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
static ByteString Url(int saveID, bool favourite)
|
||||
{
|
||||
ByteStringBuilder builder;
|
||||
builder << SCHEME << SERVER << "/Browse/Favourite.json?ID=" << saveID << "&Key=" << Client::Ref().GetAuthUser().SessionKey;
|
||||
if (!favourite)
|
||||
{
|
||||
builder << "&Mode=Remove";
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
FavouriteSaveRequest::FavouriteSaveRequest(int saveID, bool newFavourite) :
|
||||
APIRequest(Url(saveID, newFavourite), authRequire, true),
|
||||
favourite(newFavourite)
|
||||
{
|
||||
}
|
||||
|
||||
void FavouriteSaveRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
20
src/client/http/FavouriteSaveRequest.h
Normal file
20
src/client/http/FavouriteSaveRequest.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class FavouriteSaveRequest : public APIRequest
|
||||
{
|
||||
bool favourite;
|
||||
|
||||
public:
|
||||
FavouriteSaveRequest(int saveID, bool newFavourite);
|
||||
|
||||
void Finish();
|
||||
|
||||
bool Favourite() const
|
||||
{
|
||||
return favourite;
|
||||
}
|
||||
};
|
||||
}
|
36
src/client/http/GetCommentsRequest.cpp
Normal file
36
src/client/http/GetCommentsRequest.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "GetCommentsRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
GetCommentsRequest::GetCommentsRequest(int saveID, int start, int count) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", start, "&Count=", count), authOmit, false)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<Comment> GetCommentsRequest::Finish()
|
||||
{
|
||||
auto result = APIRequest::Finish();
|
||||
std::vector<Comment> comments;
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
try
|
||||
{
|
||||
for (auto &comment : result)
|
||||
{
|
||||
comments.push_back({
|
||||
comment["Username"].asString(),
|
||||
User::ElevationFromString(comment["Elevation"].asString()),
|
||||
ByteString(comment["UserID"].asString()).ToNumber<int>() == user.UserID,
|
||||
comment["IsBanned"].asBool(),
|
||||
ByteString(comment["Text"].asString()).FromUtf8(),
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
}
|
14
src/client/http/GetCommentsRequest.h
Normal file
14
src/client/http/GetCommentsRequest.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
#include "client/Comment.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class GetCommentsRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
GetCommentsRequest(int saveID, int start, int count);
|
||||
|
||||
std::vector<Comment> Finish();
|
||||
};
|
||||
}
|
28
src/client/http/GetSaveDataRequest.cpp
Normal file
28
src/client/http/GetSaveDataRequest.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "GetSaveDataRequest.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
static ByteString Url(int saveID, int saveDate)
|
||||
{
|
||||
ByteStringBuilder builder;
|
||||
builder << STATICSCHEME << STATICSERVER << "/" << saveID;
|
||||
if (saveDate)
|
||||
{
|
||||
builder << "_" << saveDate;
|
||||
}
|
||||
builder << ".cps";
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
GetSaveDataRequest::GetSaveDataRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate))
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<char> GetSaveDataRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseData);
|
||||
return std::vector<char>(data.begin(), data.end());
|
||||
}
|
||||
}
|
13
src/client/http/GetSaveDataRequest.h
Normal file
13
src/client/http/GetSaveDataRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "Request.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class GetSaveDataRequest : public Request
|
||||
{
|
||||
public:
|
||||
GetSaveDataRequest(int saveID, int saveDate);
|
||||
|
||||
std::vector<char> Finish();
|
||||
};
|
||||
}
|
69
src/client/http/GetSaveRequest.cpp
Normal file
69
src/client/http/GetSaveRequest.cpp
Normal file
@ -0,0 +1,69 @@
|
||||
#include "GetSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
static ByteString Url(int saveID, int saveDate)
|
||||
{
|
||||
ByteStringBuilder builder;
|
||||
builder << SCHEME << SERVER << "/Browse/View.json?ID=" << saveID;
|
||||
if (saveDate)
|
||||
{
|
||||
builder << "&Date=" << saveDate;
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
GetSaveRequest::GetSaveRequest(int saveID, int saveDate) : Request(Url(saveID, saveDate))
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
if (user.UserID)
|
||||
{
|
||||
// This is needed so we know how we rated this save.
|
||||
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<SaveInfo> GetSaveRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseData);
|
||||
std::unique_ptr<SaveInfo> saveInfo;
|
||||
try
|
||||
{
|
||||
Json::Value document;
|
||||
std::istringstream ss(data);
|
||||
ss >> document;
|
||||
std::list<ByteString> tags;
|
||||
for (auto &tag : document["Tags"])
|
||||
{
|
||||
tags.push_back(tag.asString());
|
||||
}
|
||||
saveInfo = std::make_unique<SaveInfo>(
|
||||
document["ID"].asInt(),
|
||||
document["DateCreated"].asInt(),
|
||||
document["Date"].asInt(),
|
||||
document["ScoreUp"].asInt(),
|
||||
document["ScoreDown"].asInt(),
|
||||
document["ScoreMine"].asInt(),
|
||||
document["Username"].asString(),
|
||||
ByteString(document["Name"].asString()).FromUtf8(),
|
||||
ByteString(document["Description"].asString()).FromUtf8(),
|
||||
document["Published"].asBool(),
|
||||
tags
|
||||
);
|
||||
saveInfo->Comments = document["Comments"].asInt();
|
||||
saveInfo->Favourite = document["Favourite"].asBool();
|
||||
saveInfo->Views = document["Views"].asInt();
|
||||
saveInfo->Version = document["Version"].asInt();
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return saveInfo;
|
||||
}
|
||||
}
|
16
src/client/http/GetSaveRequest.h
Normal file
16
src/client/http/GetSaveRequest.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "Request.h"
|
||||
#include <memory>
|
||||
|
||||
class SaveInfo;
|
||||
|
||||
namespace http
|
||||
{
|
||||
class GetSaveRequest : public Request
|
||||
{
|
||||
public:
|
||||
GetSaveRequest(int saveID, int saveDate);
|
||||
|
||||
std::unique_ptr<SaveInfo> Finish();
|
||||
};
|
||||
}
|
@ -5,18 +5,18 @@
|
||||
namespace http
|
||||
{
|
||||
GetUserInfoRequest::GetUserInfoRequest(ByteString username) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/User.json?Name=", username))
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/User.json?Name=", username), authOmit, false)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<UserInfo> GetUserInfoRequest::Finish()
|
||||
UserInfo GetUserInfoRequest::Finish()
|
||||
{
|
||||
std::unique_ptr<UserInfo> user_info;
|
||||
auto result = APIRequest::Finish();
|
||||
if (result.document)
|
||||
UserInfo userInfo;
|
||||
try
|
||||
{
|
||||
auto &user = (*result.document)["User"];
|
||||
user_info = std::unique_ptr<UserInfo>(new UserInfo(
|
||||
auto &user = result["User"];
|
||||
userInfo = UserInfo(
|
||||
user["ID"].asInt(),
|
||||
user["Age"].asInt(),
|
||||
user["Username"].asString(),
|
||||
@ -29,9 +29,13 @@ namespace http
|
||||
user["Forum"]["Topics"].asInt(),
|
||||
user["Forum"]["Replies"].asInt(),
|
||||
user["Forum"]["Reputation"].asInt()
|
||||
));
|
||||
);
|
||||
}
|
||||
return user_info;
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
class UserInfo;
|
||||
#include "client/UserInfo.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
@ -10,6 +9,6 @@ namespace http
|
||||
public:
|
||||
GetUserInfoRequest(ByteString username);
|
||||
|
||||
std::unique_ptr<UserInfo> Finish();
|
||||
UserInfo Finish();
|
||||
};
|
||||
}
|
||||
|
@ -1,30 +1,28 @@
|
||||
#include "ImageRequest.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include "client/Client.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace http
|
||||
{
|
||||
ImageRequest::ImageRequest(ByteString url, Vec2<int> size):
|
||||
Request(std::move(url)),
|
||||
size(size)
|
||||
{}
|
||||
ImageRequest::ImageRequest(ByteString url, Vec2<int> newRequestedSize) : Request(url), requestedSize(newRequestedSize)
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoBuffer> ImageRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
(void)status; // We don't use this for anything, not ideal >_>
|
||||
std::unique_ptr<VideoBuffer> vb;
|
||||
if (data.size())
|
||||
{
|
||||
vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
|
||||
ParseResponse(data, status, responseData);
|
||||
auto vb = VideoBuffer::FromPNG(std::vector<char>(data.begin(), data.end()));
|
||||
if (vb)
|
||||
vb->Resize(size, true);
|
||||
{
|
||||
vb->Resize(requestedSize, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
vb = std::make_unique<VideoBuffer>(Vec2(15, 16));
|
||||
vb->BlendChar(Vec2(2, 4), 0xE06E, 0xFFFFFF_rgb .WithAlpha(0xFF));
|
||||
}
|
||||
}
|
||||
return vb;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include "common/String.h"
|
||||
#include "common/Vec2.h"
|
||||
#include "Request.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class VideoBuffer;
|
||||
@ -11,10 +10,10 @@ namespace http
|
||||
{
|
||||
class ImageRequest : public Request
|
||||
{
|
||||
Vec2<int> size;
|
||||
Vec2<int> requestedSize;
|
||||
|
||||
public:
|
||||
ImageRequest(ByteString url, Vec2<int> size);
|
||||
ImageRequest(ByteString url, Vec2<int> newRequestedSize);
|
||||
|
||||
std::unique_ptr<VideoBuffer> Finish();
|
||||
};
|
||||
|
45
src/client/http/LoginRequest.cpp
Normal file
45
src/client/http/LoginRequest.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "LoginRequest.h"
|
||||
#include "Config.h"
|
||||
#include "client/Client.h"
|
||||
#include <json/json.h>
|
||||
|
||||
namespace http
|
||||
{
|
||||
LoginRequest::LoginRequest(ByteString username, ByteString password) : Request(ByteString::Build("https://", SERVER, "/Login.json"))
|
||||
{
|
||||
AddPostData(FormData{
|
||||
{ "name", username },
|
||||
{ "pass", password },
|
||||
});
|
||||
}
|
||||
|
||||
LoginInfo LoginRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseJson);
|
||||
LoginInfo loginInfo = { { 0, "" }, {} };
|
||||
try
|
||||
{
|
||||
Json::Value document;
|
||||
std::istringstream ss(data);
|
||||
ss >> document;
|
||||
loginInfo.user.Username = document["Username"].asString();
|
||||
loginInfo.user.UserID = document["UserID"].asInt();
|
||||
loginInfo.user.SessionID = document["SessionID"].asString();
|
||||
loginInfo.user.SessionKey = document["SessionKey"].asString();
|
||||
loginInfo.user.UserElevation = User::ElevationFromString(document["Elevation"].asString());
|
||||
for (auto &item : document["Notifications"])
|
||||
{
|
||||
loginInfo.notifications.push_back({
|
||||
ByteString(item["Text"].asString()).FromUtf8(),
|
||||
item["Link"].asString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return loginInfo;
|
||||
}
|
||||
}
|
14
src/client/http/LoginRequest.h
Normal file
14
src/client/http/LoginRequest.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "Request.h"
|
||||
#include "client/LoginInfo.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class LoginRequest : public Request
|
||||
{
|
||||
public:
|
||||
LoginRequest(ByteString username, ByteString password);
|
||||
|
||||
LoginInfo Finish();
|
||||
};
|
||||
}
|
20
src/client/http/LogoutRequest.cpp
Normal file
20
src/client/http/LogoutRequest.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include "LogoutRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
LogoutRequest::LogoutRequest() :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Logout.json"), authRequire, false)
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
AddPostData(FormData{
|
||||
{ "Key", user.SessionKey },
|
||||
});
|
||||
}
|
||||
|
||||
void LogoutRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
13
src/client/http/LogoutRequest.h
Normal file
13
src/client/http/LogoutRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class LogoutRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
LogoutRequest();
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
19
src/client/http/PublishSaveRequest.cpp
Normal file
19
src/client/http/PublishSaveRequest.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "PublishSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
PublishSaveRequest::PublishSaveRequest(int saveID) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/View.json?ID=", saveID, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
AddPostData(FormData{
|
||||
{ "ActionPublish", "bagels" },
|
||||
});
|
||||
}
|
||||
|
||||
void PublishSaveRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
13
src/client/http/PublishSaveRequest.h
Normal file
13
src/client/http/PublishSaveRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class PublishSaveRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
PublishSaveRequest(int saveID);
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
29
src/client/http/RemoveTagRequest.cpp
Normal file
29
src/client/http/RemoveTagRequest.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
#include "RemoveTagRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
RemoveTagRequest::RemoveTagRequest(int saveID, ByteString tag) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/EditTag.json?Op=delete&ID=", saveID, "&Tag=", tag, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
}
|
||||
|
||||
std::list<ByteString> RemoveTagRequest::Finish()
|
||||
{
|
||||
auto result = APIRequest::Finish();
|
||||
std::list<ByteString> tags;
|
||||
try
|
||||
{
|
||||
for (auto &tag : result["Tags"])
|
||||
{
|
||||
tags.push_back(tag.asString());
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
}
|
14
src/client/http/RemoveTagRequest.h
Normal file
14
src/client/http/RemoveTagRequest.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
#include <list>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class RemoveTagRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
RemoveTagRequest(int saveID, ByteString tag);
|
||||
|
||||
std::list<ByteString> Finish();
|
||||
};
|
||||
}
|
19
src/client/http/ReportSaveRequest.cpp
Normal file
19
src/client/http/ReportSaveRequest.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "ReportSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
ReportSaveRequest::ReportSaveRequest(int saveID, String message) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Report.json?ID=", saveID, "&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
AddPostData(FormData{
|
||||
{ "Reason", message.ToUtf8() },
|
||||
});
|
||||
}
|
||||
|
||||
void ReportSaveRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
13
src/client/http/ReportSaveRequest.h
Normal file
13
src/client/http/ReportSaveRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class ReportSaveRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
ReportSaveRequest(int saveID, String message);
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
#include "requestmanager/RequestManager.h"
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <json/json.h>
|
||||
|
||||
namespace http
|
||||
{
|
||||
@ -13,12 +15,28 @@ namespace http
|
||||
|
||||
Request::~Request()
|
||||
{
|
||||
if (handle->state != RequestHandle::ready)
|
||||
bool tryUnregister;
|
||||
{
|
||||
std::lock_guard lk(handle->stateMx);
|
||||
tryUnregister = handle->state == RequestHandle::running;
|
||||
}
|
||||
if (tryUnregister)
|
||||
{
|
||||
// At this point it may have already finished and been unregistered but that's ok,
|
||||
// attempting to unregister a request multiple times is allowed. We only do the
|
||||
// state-checking dance so we don't wake up RequestManager if we don't have to.
|
||||
// In fact, we could just not unregister requests here at all, they'd just run to
|
||||
// completion and be unregistered later. All this does is cancel them early.
|
||||
RequestManager::Ref().UnregisterRequest(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void Request::FailEarly(ByteString error)
|
||||
{
|
||||
assert(handle->state == RequestHandle::ready);
|
||||
handle->failEarly = error;
|
||||
}
|
||||
|
||||
void Request::Verb(ByteString newVerb)
|
||||
{
|
||||
assert(handle->state == RequestHandle::ready);
|
||||
@ -84,18 +102,36 @@ namespace http
|
||||
return handle->responseHeaders;
|
||||
}
|
||||
|
||||
std::pair<int, ByteString> Request::Finish()
|
||||
void Request::Wait()
|
||||
{
|
||||
std::unique_lock lk(handle->stateMx);
|
||||
if (handle->state == RequestHandle::running)
|
||||
{
|
||||
assert(handle->state == RequestHandle::running);
|
||||
handle->stateCv.wait(lk, [this]() {
|
||||
return handle->state == RequestHandle::done;
|
||||
});
|
||||
}
|
||||
|
||||
int Request::StatusCode() const
|
||||
{
|
||||
{
|
||||
std::unique_lock lk(handle->stateMx);
|
||||
assert(handle->state == RequestHandle::done);
|
||||
}
|
||||
return handle->statusCode;
|
||||
}
|
||||
|
||||
std::pair<int, ByteString> Request::Finish()
|
||||
{
|
||||
{
|
||||
std::unique_lock lk(handle->stateMx);
|
||||
assert(handle->state == RequestHandle::done);
|
||||
}
|
||||
handle->state = RequestHandle::dead;
|
||||
return { handle->statusCode, std::move(handle->responseData) };
|
||||
if (handle->error)
|
||||
{
|
||||
throw RequestError(*handle->error);
|
||||
}
|
||||
return std::pair{ handle->statusCode, std::move(handle->responseData) };
|
||||
}
|
||||
|
||||
void RequestHandle::MarkDone()
|
||||
@ -106,30 +142,13 @@ namespace http
|
||||
state = RequestHandle::done;
|
||||
}
|
||||
stateCv.notify_one();
|
||||
if (error.size())
|
||||
if (error)
|
||||
{
|
||||
std::cerr << error << std::endl;
|
||||
std::cerr << *error << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<int, ByteString> Request::Simple(ByteString uri, FormData postData)
|
||||
{
|
||||
return SimpleAuth(uri, "", "", postData);
|
||||
}
|
||||
|
||||
std::pair<int, ByteString> Request::SimpleAuth(ByteString uri, ByteString ID, ByteString session, FormData postData)
|
||||
{
|
||||
auto request = std::make_unique<Request>(uri);
|
||||
if (!postData.empty())
|
||||
{
|
||||
request->AddPostData(postData);
|
||||
}
|
||||
request->AuthHeaders(ID, session);
|
||||
request->Start();
|
||||
return request->Finish();
|
||||
}
|
||||
|
||||
String StatusText(int ret)
|
||||
const char *StatusText(int ret)
|
||||
{
|
||||
switch (ret)
|
||||
{
|
||||
@ -211,7 +230,69 @@ namespace http
|
||||
case 619: return "SSL: Failed to Load CRL File";
|
||||
case 620: return "SSL: Issuer Check Failed";
|
||||
case 621: return "SSL: Pinned Public Key Mismatch";
|
||||
default: return "Unknown Status Code";
|
||||
}
|
||||
return "Unknown Status Code";
|
||||
}
|
||||
|
||||
void Request::ParseResponse(const ByteString &result, int status, ResponseType responseType)
|
||||
{
|
||||
// no server response, return "Malformed Response"
|
||||
if (status == 200 && !result.size())
|
||||
{
|
||||
status = 603;
|
||||
}
|
||||
if (status == 302)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (status != 200)
|
||||
{
|
||||
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
|
||||
}
|
||||
|
||||
switch (responseType)
|
||||
{
|
||||
case responseOk:
|
||||
if (strncmp(result.c_str(), "OK", 2))
|
||||
{
|
||||
throw RequestError(result);
|
||||
}
|
||||
break;
|
||||
|
||||
case responseJson:
|
||||
{
|
||||
std::istringstream ss(result);
|
||||
Json::Value root;
|
||||
try
|
||||
{
|
||||
ss >> root;
|
||||
// assume everything is fine if an empty [] is returned
|
||||
if (root.size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int status = root.get("Status", 1).asInt();
|
||||
if (status != 1)
|
||||
{
|
||||
throw RequestError(ByteString(root.get("Error", "Unspecified Error").asString()));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
// sometimes the server returns a 200 with the text "Error: 401"
|
||||
if (!strncmp(result.c_str(), "Error: ", 7))
|
||||
{
|
||||
status = ByteString(result.begin() + 7, result.end()).ToNumber<int>();
|
||||
throw RequestError(ByteString::Build("HTTP Error ", status, ": ", http::StatusText(status)));
|
||||
}
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case responseData:
|
||||
// no further processing required
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,18 @@
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
|
||||
namespace http
|
||||
{
|
||||
struct RequestHandle;
|
||||
|
||||
// Thrown by Finish and ParseResponse
|
||||
struct RequestError : public std::runtime_error
|
||||
{
|
||||
using runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class Request
|
||||
{
|
||||
std::shared_ptr<RequestHandle> handle;
|
||||
@ -21,6 +28,8 @@ namespace http
|
||||
Request &operator =(const Request &) = delete;
|
||||
~Request();
|
||||
|
||||
void FailEarly(ByteString error);
|
||||
|
||||
void Verb(ByteString newVerb);
|
||||
void AddHeader(ByteString header);
|
||||
|
||||
@ -32,13 +41,21 @@ namespace http
|
||||
|
||||
std::pair<int, int> CheckProgress() const; // total, done
|
||||
const std::vector<ByteString> &ResponseHeaders() const;
|
||||
void Wait();
|
||||
|
||||
int StatusCode() const; // status
|
||||
std::pair<int, ByteString> Finish(); // status, data
|
||||
|
||||
static std::pair<int, ByteString> Simple(ByteString uri, FormData postData = {});
|
||||
static std::pair<int, ByteString> SimpleAuth(ByteString uri, ByteString ID, ByteString session, FormData postData = {});
|
||||
enum ResponseType
|
||||
{
|
||||
responseOk,
|
||||
responseJson,
|
||||
responseData,
|
||||
};
|
||||
static void ParseResponse(const ByteString &result, int status, ResponseType responseType);
|
||||
|
||||
friend class RequestManager;
|
||||
};
|
||||
|
||||
String StatusText(int code);
|
||||
const char *StatusText(int code);
|
||||
}
|
||||
|
@ -1,29 +1,20 @@
|
||||
#include "SaveUserInfoRequest.h"
|
||||
#include "client/UserInfo.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
SaveUserInfoRequest::SaveUserInfoRequest(UserInfo &info) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Profile.json"))
|
||||
SaveUserInfoRequest::SaveUserInfoRequest(UserInfo info) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Profile.json"), authRequire, true)
|
||||
{
|
||||
AddPostData(FormData{
|
||||
{ "Location", info.location.ToUtf8() },
|
||||
{ "Biography", info.biography.ToUtf8() }
|
||||
{ "Biography", info.biography.ToUtf8() },
|
||||
});
|
||||
}
|
||||
|
||||
bool SaveUserInfoRequest::Finish()
|
||||
void SaveUserInfoRequest::Finish()
|
||||
{
|
||||
auto result = APIRequest::Finish();
|
||||
if (result.document)
|
||||
{
|
||||
return (*result.document)["Status"].asInt() == 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
class UserInfo;
|
||||
#include "client/UserInfo.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class SaveUserInfoRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
SaveUserInfoRequest(UserInfo &info);
|
||||
SaveUserInfoRequest(UserInfo info);
|
||||
|
||||
bool Finish();
|
||||
void Finish();
|
||||
};
|
||||
}
|
||||
|
85
src/client/http/SearchSavesRequest.cpp
Normal file
85
src/client/http/SearchSavesRequest.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "SearchSavesRequest.h"
|
||||
#include "Config.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "Format.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
static ByteString Url(int start, int count, ByteString query, Sort sort, Category category)
|
||||
{
|
||||
ByteStringBuilder builder;
|
||||
builder << SCHEME << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
|
||||
auto appendToQuery = [&query](ByteString str) {
|
||||
if (query.size())
|
||||
{
|
||||
query += " ";
|
||||
}
|
||||
query += str;
|
||||
};
|
||||
switch (sort)
|
||||
{
|
||||
case sortByDate:
|
||||
appendToQuery("sort:date");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
switch (category)
|
||||
{
|
||||
case categoryFavourites:
|
||||
builder << "&Category=Favourites";
|
||||
break;
|
||||
|
||||
case categoryMyOwn:
|
||||
assert(user.UserID);
|
||||
appendToQuery("user:" + user.Username);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (query.size())
|
||||
{
|
||||
builder << "&Search_Query=" << format::URLEncode(query);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
SearchSavesRequest::SearchSavesRequest(int start, int count, ByteString query, Sort sort, Category category) : APIRequest(Url(start, count, query, sort, category), authUse, false)
|
||||
{
|
||||
}
|
||||
|
||||
std::pair<int, std::vector<std::unique_ptr<SaveInfo>>> SearchSavesRequest::Finish()
|
||||
{
|
||||
std::vector<std::unique_ptr<SaveInfo>> saves;
|
||||
auto result = APIRequest::Finish();
|
||||
int count;
|
||||
try
|
||||
{
|
||||
count = result["Count"].asInt();
|
||||
for (auto &save : result["Saves"])
|
||||
{
|
||||
auto saveInfo = std::make_unique<SaveInfo>(
|
||||
save["ID"].asInt(),
|
||||
save["Created"].asInt(),
|
||||
save["Updated"].asInt(),
|
||||
save["ScoreUp"].asInt(),
|
||||
save["ScoreDown"].asInt(),
|
||||
save["Username"].asString(),
|
||||
ByteString(save["Name"].asString()).FromUtf8()
|
||||
);
|
||||
saveInfo->Version = save["Version"].asInt();
|
||||
saveInfo->SetPublished(save["Published"].asBool());
|
||||
saves.push_back(std::move(saveInfo));
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return std::pair{ count, std::move(saves) };
|
||||
}
|
||||
}
|
15
src/client/http/SearchSavesRequest.h
Normal file
15
src/client/http/SearchSavesRequest.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/Search.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class SearchSavesRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
SearchSavesRequest(int start, int count, ByteString query, Sort sort, Category category);
|
||||
|
||||
std::pair<int, std::vector<std::unique_ptr<SaveInfo>>> Finish();
|
||||
};
|
||||
}
|
42
src/client/http/SearchTagsRequest.cpp
Normal file
42
src/client/http/SearchTagsRequest.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "SearchTagsRequest.h"
|
||||
#include "Config.h"
|
||||
#include "Format.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
static ByteString Url(int start, int count, ByteString query)
|
||||
{
|
||||
ByteStringBuilder builder;
|
||||
builder << SCHEME << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
|
||||
if (query.size())
|
||||
{
|
||||
builder << "&Search_Query=" << format::URLEncode(query);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
SearchTagsRequest::SearchTagsRequest(int start, int count, ByteString query) : APIRequest(Url(start, count, query), authOmit, false)
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::pair<ByteString, int>> SearchTagsRequest::Finish()
|
||||
{
|
||||
std::vector<std::pair<ByteString, int>> tags;
|
||||
auto result = APIRequest::Finish();
|
||||
try
|
||||
{
|
||||
for (auto &tag : result["Tags"])
|
||||
{
|
||||
tags.push_back({
|
||||
tag["Tag"].asString(),
|
||||
tag["Count"].asInt(),
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
}
|
13
src/client/http/SearchTagsRequest.h
Normal file
13
src/client/http/SearchTagsRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class SearchTagsRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
SearchTagsRequest(int start, int count, ByteString query);
|
||||
|
||||
std::vector<std::pair<ByteString, int>> Finish();
|
||||
};
|
||||
}
|
102
src/client/http/StartupRequest.cpp
Normal file
102
src/client/http/StartupRequest.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include "StartupRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
// TODO: update Client::messageOfTheDay
|
||||
StartupRequest::StartupRequest(bool newAlternate) :
|
||||
Request(ByteString::Build(SCHEME, newAlternate ? UPDATESERVER : SERVER, "/Startup.json")),
|
||||
alternate(newAlternate)
|
||||
{
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
if (user.UserID)
|
||||
{
|
||||
if (alternate)
|
||||
{
|
||||
// Cursed
|
||||
AuthHeaders(user.Username, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StartupInfo StartupRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseJson);
|
||||
StartupInfo startupInfo;
|
||||
try
|
||||
{
|
||||
Json::Value document;
|
||||
std::istringstream ss(data);
|
||||
ss >> document;
|
||||
startupInfo.sessionGood = document["Session"].asBool();
|
||||
startupInfo.messageOfTheDay = ByteString(document["MessageOfTheDay"].asString()).FromUtf8();
|
||||
for (auto ¬ification : document["Notifications"])
|
||||
{
|
||||
startupInfo.notifications.push_back({
|
||||
ByteString(notification["Text"].asString()).FromUtf8(),
|
||||
notification["Link"].asString()
|
||||
});
|
||||
}
|
||||
if constexpr (!IGNORE_UPDATES)
|
||||
{
|
||||
auto &versions = document["Updates"];
|
||||
auto parseUpdate = [this, &versions, &startupInfo](ByteString key, UpdateInfo::Channel channel, std::function<bool (int)> updateAvailableFunc) {
|
||||
if (!versions.isMember(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto &info = versions[key];
|
||||
auto getOr = [&info](ByteString key, int defaultValue) {
|
||||
if (!info.isMember(key))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
return info[key].asInt();
|
||||
};
|
||||
auto build = getOr(key == "Snapshot" ? "Snapshot" : "Build", -1);
|
||||
if (!updateAvailableFunc(build))
|
||||
{
|
||||
return;
|
||||
}
|
||||
startupInfo.updateInfo = UpdateInfo{
|
||||
channel,
|
||||
ByteString::Build(SCHEME, alternate ? UPDATESERVER : SERVER, info["File"].asString()),
|
||||
ByteString(info["Changelog"].asString()).FromUtf8(),
|
||||
getOr("Major", -1),
|
||||
getOr("Minor", -1),
|
||||
build,
|
||||
};
|
||||
};
|
||||
if constexpr (SNAPSHOT || MOD)
|
||||
{
|
||||
parseUpdate("Snapshot", UpdateInfo::channelSnapshot, [](int build) {
|
||||
return build > SNAPSHOT_ID;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
parseUpdate("Stable", UpdateInfo::channelStable, [](int build) {
|
||||
return build > BUILD_NUM;
|
||||
});
|
||||
if (!startupInfo.updateInfo.has_value())
|
||||
{
|
||||
parseUpdate("Beta", UpdateInfo::channelBeta, [](int build) {
|
||||
return build > BUILD_NUM;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
throw RequestError("Could not read response: " + ByteString(ex.what()));
|
||||
}
|
||||
return startupInfo;
|
||||
}
|
||||
}
|
16
src/client/http/StartupRequest.h
Normal file
16
src/client/http/StartupRequest.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
#include "client/StartupInfo.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class StartupRequest : public Request
|
||||
{
|
||||
bool alternate;
|
||||
|
||||
public:
|
||||
StartupRequest(bool newAlternate);
|
||||
|
||||
StartupInfo Finish();
|
||||
};
|
||||
}
|
17
src/client/http/UnpublishSaveRequest.cpp
Normal file
17
src/client/http/UnpublishSaveRequest.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include "UnpublishSaveRequest.h"
|
||||
#include "client/Client.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
UnpublishSaveRequest::UnpublishSaveRequest(int saveID) :
|
||||
APIRequest(ByteString::Build(SCHEME, SERVER, "/Browse/Delete.json?ID=", saveID, "&Mode=Unpublish&Key=", Client::Ref().GetAuthUser().SessionKey), authRequire, true)
|
||||
{
|
||||
}
|
||||
|
||||
void UnpublishSaveRequest::Finish()
|
||||
{
|
||||
APIRequest::Finish();
|
||||
}
|
||||
}
|
||||
|
13
src/client/http/UnpublishSaveRequest.h
Normal file
13
src/client/http/UnpublishSaveRequest.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "APIRequest.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class UnpublishSaveRequest : public APIRequest
|
||||
{
|
||||
public:
|
||||
UnpublishSaveRequest(int saveID);
|
||||
|
||||
void Finish();
|
||||
};
|
||||
}
|
0
src/client/http/UpdateRequest.cpp
Normal file
0
src/client/http/UpdateRequest.cpp
Normal file
0
src/client/http/UpdateRequest.h
Normal file
0
src/client/http/UpdateRequest.h
Normal file
49
src/client/http/UploadSaveRequest.cpp
Normal file
49
src/client/http/UploadSaveRequest.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "UploadSaveRequest.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "Config.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
UploadSaveRequest::UploadSaveRequest(const SaveInfo &saveInfo) : Request(ByteString::Build(SCHEME, SERVER, "/Save.api"))
|
||||
{
|
||||
auto [ fromNewerVersion, gameData ] = saveInfo.GetGameSave()->Serialise();
|
||||
if (!gameData.size())
|
||||
{
|
||||
FailEarly("Cannot serialize game save");
|
||||
return;
|
||||
}
|
||||
else if (ALLOW_FAKE_NEWER_VERSION && fromNewerVersion && saveInfo.GetPublished())
|
||||
{
|
||||
FailEarly("Cannot publish save, incompatible with latest release version");
|
||||
return;
|
||||
}
|
||||
auto user = Client::Ref().GetAuthUser();
|
||||
if (!user.UserID)
|
||||
{
|
||||
FailEarly("Not authenticated");
|
||||
return;
|
||||
}
|
||||
AuthHeaders(ByteString::Build(user.UserID), user.SessionID);
|
||||
AddPostData(FormData{
|
||||
{ "Name", saveInfo.GetName().ToUtf8() },
|
||||
{ "Description", saveInfo.GetDescription().ToUtf8() },
|
||||
{ "Data:save.bin", ByteString(gameData.begin(), gameData.end()) },
|
||||
{ "Publish", saveInfo.GetPublished() ? "Public" : "Private" },
|
||||
{ "Key", user.SessionKey },
|
||||
});
|
||||
}
|
||||
|
||||
int UploadSaveRequest::Finish()
|
||||
{
|
||||
auto [ status, data ] = Request::Finish();
|
||||
ParseResponse(data, status, responseOk);
|
||||
int saveID = ByteString(data.begin() + 3, data.end()).ToNumber<int>();
|
||||
if (!saveID)
|
||||
{
|
||||
throw RequestError("Server did not return Save ID");
|
||||
}
|
||||
return saveID;
|
||||
}
|
||||
}
|
15
src/client/http/UploadSaveRequest.h
Normal file
15
src/client/http/UploadSaveRequest.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "Request.h"
|
||||
|
||||
class SaveInfo;
|
||||
|
||||
namespace http
|
||||
{
|
||||
class UploadSaveRequest : public Request
|
||||
{
|
||||
public:
|
||||
UploadSaveRequest(const SaveInfo &saveInfo);
|
||||
|
||||
int Finish();
|
||||
};
|
||||
}
|
@ -5,6 +5,25 @@ client_files += files(
|
||||
'Request.cpp',
|
||||
'SaveUserInfoRequest.cpp',
|
||||
'ThumbnailRequest.cpp',
|
||||
'AddTagRequest.cpp',
|
||||
'RemoveTagRequest.cpp',
|
||||
'GetSaveRequest.cpp',
|
||||
'PublishSaveRequest.cpp',
|
||||
'UnpublishSaveRequest.cpp',
|
||||
'ReportSaveRequest.cpp',
|
||||
'FavouriteSaveRequest.cpp',
|
||||
'AddCommentRequest.cpp',
|
||||
'DeleteSaveRequest.cpp',
|
||||
'LoginRequest.cpp',
|
||||
'GetSaveDataRequest.cpp',
|
||||
'ExecVoteRequest.cpp',
|
||||
'UploadSaveRequest.cpp',
|
||||
'StartupRequest.cpp',
|
||||
'UpdateRequest.cpp',
|
||||
'SearchSavesRequest.cpp',
|
||||
'SearchTagsRequest.cpp',
|
||||
'GetCommentsRequest.cpp',
|
||||
'LogoutRequest.cpp',
|
||||
)
|
||||
|
||||
subdir('requestmanager')
|
||||
|
@ -22,6 +22,13 @@ namespace http
|
||||
|
||||
void RequestManager::RegisterRequest(Request &request)
|
||||
{
|
||||
if (request.handle->failEarly)
|
||||
{
|
||||
request.handle->error = request.handle->failEarly.value();
|
||||
request.handle->statusCode = 600;
|
||||
request.handle->MarkDone();
|
||||
return;
|
||||
}
|
||||
if (disableNetwork)
|
||||
{
|
||||
request.handle->statusCode = 604;
|
||||
|
@ -285,16 +285,20 @@ namespace http
|
||||
void RequestManager::RegisterRequestImpl(Request &request)
|
||||
{
|
||||
auto manager = static_cast<RequestManagerImpl *>(this);
|
||||
{
|
||||
std::lock_guard lk(manager->sharedStateMx);
|
||||
manager->requestHandlesToRegister.push_back(request.handle);
|
||||
}
|
||||
curl_multi_wakeup(manager->curlMulti);
|
||||
}
|
||||
|
||||
void RequestManager::UnregisterRequestImpl(Request &request)
|
||||
{
|
||||
auto manager = static_cast<RequestManagerImpl *>(this);
|
||||
{
|
||||
std::lock_guard lk(manager->sharedStateMx);
|
||||
manager->requestHandlesToUnregister.push_back(request.handle);
|
||||
}
|
||||
curl_multi_wakeup(manager->curlMulti);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <optional>
|
||||
|
||||
namespace http
|
||||
{
|
||||
@ -42,7 +43,8 @@ namespace http
|
||||
int statusCode = 0;
|
||||
ByteString responseData;
|
||||
std::vector<ByteString> responseHeaders;
|
||||
ByteString error;
|
||||
std::optional<ByteString> error;
|
||||
std::optional<ByteString> failEarly;
|
||||
|
||||
RequestHandle(CtorTag)
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ client_files = files(
|
||||
'ThumbnailRendererTask.cpp',
|
||||
'Client.cpp',
|
||||
'GameSave.cpp',
|
||||
'User.cpp',
|
||||
)
|
||||
|
||||
subdir('http')
|
||||
|
@ -63,6 +63,7 @@
|
||||
|
||||
#include "Config.h"
|
||||
#include <SDL.h>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef GetUserName
|
||||
# undef GetUserName // dammit windows
|
||||
@ -699,6 +700,7 @@ bool GameController::KeyRelease(int key, int scan, bool repeat, bool shift, bool
|
||||
|
||||
void GameController::Tick()
|
||||
{
|
||||
gameModel->Tick();
|
||||
if(firstTick)
|
||||
{
|
||||
commandInterface->Init();
|
||||
@ -1440,17 +1442,10 @@ void GameController::FrameStep()
|
||||
|
||||
void GameController::Vote(int direction)
|
||||
{
|
||||
if(gameModel->GetSave() && gameModel->GetUser().UserID && gameModel->GetSave()->GetID())
|
||||
{
|
||||
try
|
||||
if (gameModel->GetSave() && gameModel->GetUser().UserID && gameModel->GetSave()->GetID())
|
||||
{
|
||||
gameModel->SetVote(direction);
|
||||
}
|
||||
catch(GameModelException & ex)
|
||||
{
|
||||
new ErrorMessage("Error while voting", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameController::ChangeBrush()
|
||||
@ -1536,7 +1531,7 @@ void GameController::NotifyAuthUserChanged(Client * sender)
|
||||
gameModel->SetUser(newUser);
|
||||
}
|
||||
|
||||
void GameController::NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification)
|
||||
void GameController::NotifyNewNotification(Client * sender, ServerNotification notification)
|
||||
{
|
||||
class LinkNotification : public Notification
|
||||
{
|
||||
@ -1550,7 +1545,7 @@ void GameController::NotifyNewNotification(Client * sender, std::pair<String, By
|
||||
Platform::OpenURI(link);
|
||||
}
|
||||
};
|
||||
gameModel->AddNotification(new LinkNotification(notification.second, notification.first));
|
||||
gameModel->AddNotification(new LinkNotification(notification.link, notification.text));
|
||||
}
|
||||
|
||||
void GameController::NotifyUpdateAvailable(Client * sender)
|
||||
@ -1564,7 +1559,13 @@ void GameController::NotifyUpdateAvailable(Client * sender)
|
||||
|
||||
void Action() override
|
||||
{
|
||||
UpdateInfo info = Client::Ref().GetUpdateInfo();
|
||||
auto optinfo = Client::Ref().GetUpdateInfo();
|
||||
if (!optinfo.has_value())
|
||||
{
|
||||
std::cerr << "odd, the update has disappeared" << std::endl;
|
||||
return;
|
||||
}
|
||||
UpdateInfo info = optinfo.value();
|
||||
StringBuilder updateMessage;
|
||||
if (Platform::CanUpdate())
|
||||
{
|
||||
@ -1593,36 +1594,41 @@ void GameController::NotifyUpdateAvailable(Client * sender)
|
||||
}
|
||||
|
||||
updateMessage << "\nNew version:\n ";
|
||||
if (info.Type == UpdateInfo::Beta)
|
||||
if (info.channel == UpdateInfo::channelBeta)
|
||||
{
|
||||
updateMessage << info.Major << "." << info.Minor << " Beta, Build " << info.Build;
|
||||
updateMessage << info.major << "." << info.minor << " Beta, Build " << info.build;
|
||||
}
|
||||
else if (info.Type == UpdateInfo::Snapshot)
|
||||
else if (info.channel == UpdateInfo::channelSnapshot)
|
||||
{
|
||||
if constexpr (MOD)
|
||||
{
|
||||
updateMessage << "Mod version " << info.Time;
|
||||
updateMessage << "Mod version " << info.build;
|
||||
}
|
||||
else
|
||||
{
|
||||
updateMessage << "Snapshot " << info.Time;
|
||||
updateMessage << "Snapshot " << info.build;
|
||||
}
|
||||
}
|
||||
else if(info.Type == UpdateInfo::Stable)
|
||||
else if(info.channel == UpdateInfo::channelStable)
|
||||
{
|
||||
updateMessage << info.Major << "." << info.Minor << " Stable, Build " << info.Build;
|
||||
updateMessage << info.major << "." << info.minor << " Stable, Build " << info.build;
|
||||
}
|
||||
|
||||
if (info.Changelog.length())
|
||||
updateMessage << "\n\nChangelog:\n" << info.Changelog;
|
||||
if (info.changeLog.length())
|
||||
updateMessage << "\n\nChangelog:\n" << info.changeLog;
|
||||
|
||||
new ConfirmPrompt("Run Updater", updateMessage.Build(), { [this] { c->RunUpdater(); } });
|
||||
new ConfirmPrompt("Run Updater", updateMessage.Build(), { [this, info] { c->RunUpdater(info); } });
|
||||
}
|
||||
};
|
||||
|
||||
switch(sender->GetUpdateInfo().Type)
|
||||
auto optinfo = sender->GetUpdateInfo();
|
||||
if (!optinfo.has_value())
|
||||
{
|
||||
case UpdateInfo::Snapshot:
|
||||
return;
|
||||
}
|
||||
switch(optinfo.value().channel)
|
||||
{
|
||||
case UpdateInfo::channelSnapshot:
|
||||
if constexpr (MOD)
|
||||
{
|
||||
gameModel->AddNotification(new UpdateNotification(this, "A new mod update is available - click here to update"));
|
||||
@ -1632,10 +1638,10 @@ void GameController::NotifyUpdateAvailable(Client * sender)
|
||||
gameModel->AddNotification(new UpdateNotification(this, "A new snapshot is available - click here to update"));
|
||||
}
|
||||
break;
|
||||
case UpdateInfo::Stable:
|
||||
case UpdateInfo::channelStable:
|
||||
gameModel->AddNotification(new UpdateNotification(this, "A new version is available - click here to update"));
|
||||
break;
|
||||
case UpdateInfo::Beta:
|
||||
case UpdateInfo::channelBeta:
|
||||
gameModel->AddNotification(new UpdateNotification(this, "A new beta is available - click here to update"));
|
||||
break;
|
||||
}
|
||||
@ -1646,25 +1652,16 @@ void GameController::RemoveNotification(Notification * notification)
|
||||
gameModel->RemoveNotification(notification);
|
||||
}
|
||||
|
||||
void GameController::RunUpdater()
|
||||
void GameController::RunUpdater(UpdateInfo info)
|
||||
{
|
||||
if (Platform::CanUpdate())
|
||||
{
|
||||
Exit();
|
||||
new UpdateActivity();
|
||||
new UpdateActivity(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
ByteString file;
|
||||
if constexpr (USE_UPDATESERVER)
|
||||
{
|
||||
file = ByteString::Build(SCHEME, UPDATESERVER, Client::Ref().GetUpdateInfo().File);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = ByteString::Build(SCHEME, SERVER, Client::Ref().GetUpdateInfo().File);
|
||||
}
|
||||
Platform::OpenURI(file);
|
||||
Platform::OpenURI(info.file);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "client/ClientListener.h"
|
||||
#include "client/StartupInfo.h"
|
||||
#include "gui/interface/Point.h"
|
||||
#include "gui/interface/Colour.h"
|
||||
#include "simulation/Sign.h"
|
||||
@ -181,8 +182,8 @@ public:
|
||||
|
||||
void NotifyUpdateAvailable(Client * sender) override;
|
||||
void NotifyAuthUserChanged(Client * sender) override;
|
||||
void NotifyNewNotification(Client * sender, std::pair<String, ByteString> notification) override;
|
||||
void RunUpdater();
|
||||
void NotifyNewNotification(Client * sender, ServerNotification notification) override;
|
||||
void RunUpdater(UpdateInfo info);
|
||||
bool GetMouseClickRequired();
|
||||
|
||||
void RemoveCustomGOLType(const ByteString &identifier);
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include "client/GameSave.h"
|
||||
#include "client/SaveFile.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/http/ExecVoteRequest.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "graphics/Renderer.h"
|
||||
#include "simulation/Air.h"
|
||||
@ -30,6 +31,7 @@
|
||||
#include "simulation/ToolClasses.h"
|
||||
#include "gui/game/DecorationTool.h"
|
||||
#include "gui/interface/Engine.h"
|
||||
#include "gui/dialogues/ErrorMessage.h"
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
@ -780,18 +782,33 @@ void GameModel::SetUndoHistoryLimit(unsigned int undoHistoryLimit_)
|
||||
|
||||
void GameModel::SetVote(int direction)
|
||||
{
|
||||
if(currentSave)
|
||||
queuedVote = direction;
|
||||
}
|
||||
|
||||
void GameModel::Tick()
|
||||
{
|
||||
if (execVoteRequest && execVoteRequest->CheckDone())
|
||||
{
|
||||
RequestStatus status = Client::Ref().ExecVote(currentSave->GetID(), direction);
|
||||
if(status == RequestOkay)
|
||||
try
|
||||
{
|
||||
currentSave->vote = direction;
|
||||
execVoteRequest->Finish();
|
||||
currentSave->vote = execVoteRequest->Direction();
|
||||
notifySaveChanged();
|
||||
}
|
||||
else
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
throw GameModelException("Could not vote: "+Client::Ref().GetLastError());
|
||||
new ErrorMessage("Error while voting", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
execVoteRequest.reset();
|
||||
}
|
||||
if (!execVoteRequest && queuedVote)
|
||||
{
|
||||
if (currentSave)
|
||||
{
|
||||
execVoteRequest = std::make_unique<http::ExecVoteRequest>(currentSave->GetID(), *queuedVote);
|
||||
execVoteRequest->Start();
|
||||
}
|
||||
queuedVote.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
class Menu;
|
||||
class Tool;
|
||||
@ -21,6 +22,11 @@ class Snapshot;
|
||||
struct SnapshotDelta;
|
||||
class GameSave;
|
||||
|
||||
namespace http
|
||||
{
|
||||
class ExecVoteRequest;
|
||||
};
|
||||
|
||||
class ToolSelection
|
||||
{
|
||||
public:
|
||||
@ -40,6 +46,8 @@ struct HistoryEntry
|
||||
|
||||
class GameModel
|
||||
{
|
||||
std::unique_ptr<http::ExecVoteRequest> execVoteRequest;
|
||||
|
||||
private:
|
||||
std::vector<Notification*> notifications;
|
||||
//int clipboardSize;
|
||||
@ -119,10 +127,14 @@ private:
|
||||
|
||||
void SaveToSimParameters(const GameSave &saveData);
|
||||
|
||||
std::optional<int> queuedVote;
|
||||
|
||||
public:
|
||||
GameModel();
|
||||
~GameModel();
|
||||
|
||||
void Tick();
|
||||
|
||||
void SetEdgeMode(int edgeMode);
|
||||
int GetEdgeMode();
|
||||
void SetTemperatureScale(int temperatureScale);
|
||||
|
@ -1426,8 +1426,7 @@ void GameView::OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl,
|
||||
c->ReloadSim();
|
||||
break;
|
||||
case SDL_SCANCODE_A:
|
||||
if ((Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator
|
||||
|| Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin) && ctrl)
|
||||
if (Client::Ref().GetAuthUser().UserElevation != User::ElevationNone && ctrl)
|
||||
{
|
||||
ByteString authorString = Client::Ref().GetAuthorInfo().toStyledString();
|
||||
new InformationMessage("Save authorship info", authorString.FromUtf8(), true);
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "LoginController.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
|
||||
#include "client/http/LoginRequest.h"
|
||||
#include "client/http/LogoutRequest.h"
|
||||
#include "LoginView.h"
|
||||
#include "LoginModel.h"
|
||||
#include "Controller.h"
|
||||
@ -23,15 +23,19 @@ void LoginController::Login(ByteString username, ByteString password)
|
||||
loginModel->Login(username, password);
|
||||
}
|
||||
|
||||
User LoginController::GetUser()
|
||||
void LoginController::Logout()
|
||||
{
|
||||
return loginModel->GetUser();
|
||||
loginModel->Logout();
|
||||
}
|
||||
|
||||
void LoginController::Tick()
|
||||
{
|
||||
loginModel->Tick();
|
||||
}
|
||||
|
||||
void LoginController::Exit()
|
||||
{
|
||||
loginView->CloseActiveWindow();
|
||||
Client::Ref().SetAuthUser(loginModel->GetUser());
|
||||
if (onDone)
|
||||
onDone();
|
||||
HasExited = true;
|
||||
|
@ -14,8 +14,9 @@ public:
|
||||
bool HasExited;
|
||||
LoginController(std::function<void ()> onDone = nullptr);
|
||||
void Login(ByteString username, ByteString password);
|
||||
void Logout();
|
||||
void Tick();
|
||||
void Exit();
|
||||
LoginView * GetView() { return loginView; }
|
||||
User GetUser();
|
||||
virtual ~LoginController();
|
||||
~LoginController();
|
||||
};
|
||||
|
@ -1,39 +1,32 @@
|
||||
#include "LoginModel.h"
|
||||
|
||||
#include "LoginView.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
|
||||
LoginModel::LoginModel():
|
||||
currentUser(0, "")
|
||||
{
|
||||
|
||||
}
|
||||
#include "client/http/LoginRequest.h"
|
||||
#include "client/http/LogoutRequest.h"
|
||||
|
||||
void LoginModel::Login(ByteString username, ByteString password)
|
||||
{
|
||||
if (username.Contains("@"))
|
||||
{
|
||||
statusText = "Use your Powder Toy account to log in, not your email. If you don't have a Powder Toy account, you can create one at https://powdertoy.co.uk/Register.html";
|
||||
loginStatus = false;
|
||||
loginStatus = loginIdle;
|
||||
notifyStatusChanged();
|
||||
return;
|
||||
}
|
||||
statusText = "Logging in...";
|
||||
loginStatus = false;
|
||||
loginStatus = loginWorking;
|
||||
notifyStatusChanged();
|
||||
LoginStatus status = Client::Ref().Login(username, password, currentUser);
|
||||
switch(status)
|
||||
{
|
||||
case LoginOkay:
|
||||
statusText = "Logged in";
|
||||
loginStatus = true;
|
||||
break;
|
||||
case LoginError:
|
||||
statusText = Client::Ref().GetLastError();
|
||||
break;
|
||||
}
|
||||
loginRequest = std::make_unique<http::LoginRequest>(username, password);
|
||||
loginRequest->Start();
|
||||
}
|
||||
|
||||
void LoginModel::Logout()
|
||||
{
|
||||
statusText = "Logging out...";
|
||||
loginStatus = loginWorking;
|
||||
notifyStatusChanged();
|
||||
logoutRequest = std::make_unique<http::LogoutRequest>();
|
||||
logoutRequest->Start();
|
||||
}
|
||||
|
||||
void LoginModel::AddObserver(LoginView * observer)
|
||||
@ -46,14 +39,47 @@ String LoginModel::GetStatusText()
|
||||
return statusText;
|
||||
}
|
||||
|
||||
User LoginModel::GetUser()
|
||||
void LoginModel::Tick()
|
||||
{
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
bool LoginModel::GetStatus()
|
||||
{
|
||||
return loginStatus;
|
||||
if (loginRequest && loginRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto info = loginRequest->Finish();
|
||||
auto &client = Client::Ref();
|
||||
client.SetAuthUser(info.user);
|
||||
for (auto &item : info.notifications)
|
||||
{
|
||||
client.AddServerNotification(item);
|
||||
}
|
||||
statusText = "Logged in";
|
||||
loginStatus = loginSucceeded;
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
statusText = ByteString(ex.what()).FromUtf8();
|
||||
loginStatus = loginIdle;
|
||||
}
|
||||
notifyStatusChanged();
|
||||
loginRequest.reset();
|
||||
}
|
||||
if (logoutRequest && logoutRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
logoutRequest->Finish();
|
||||
auto &client = Client::Ref();
|
||||
client.SetAuthUser(User(0, ""));
|
||||
statusText = "Logged out";
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
statusText = ByteString(ex.what()).FromUtf8();
|
||||
}
|
||||
loginStatus = loginIdle;
|
||||
notifyStatusChanged();
|
||||
logoutRequest.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void LoginModel::notifyStatusChanged()
|
||||
@ -64,6 +90,7 @@ void LoginModel::notifyStatusChanged()
|
||||
}
|
||||
}
|
||||
|
||||
LoginModel::~LoginModel() {
|
||||
LoginModel::~LoginModel()
|
||||
{
|
||||
// Satisfy std::unique_ptr
|
||||
}
|
||||
|
||||
|
@ -2,21 +2,41 @@
|
||||
#include "common/String.h"
|
||||
#include "client/User.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class LoginRequest;
|
||||
class LogoutRequest;
|
||||
}
|
||||
|
||||
enum LoginStatus
|
||||
{
|
||||
loginIdle,
|
||||
loginWorking,
|
||||
loginSucceeded,
|
||||
};
|
||||
|
||||
class LoginView;
|
||||
class LoginModel
|
||||
{
|
||||
std::unique_ptr<http::LoginRequest> loginRequest;
|
||||
std::unique_ptr<http::LogoutRequest> logoutRequest;
|
||||
std::vector<LoginView*> observers;
|
||||
String statusText;
|
||||
bool loginStatus;
|
||||
LoginStatus loginStatus = loginIdle;
|
||||
void notifyStatusChanged();
|
||||
User currentUser;
|
||||
|
||||
public:
|
||||
LoginModel();
|
||||
void Login(ByteString username, ByteString password);
|
||||
void Logout();
|
||||
void AddObserver(LoginView * observer);
|
||||
String GetStatusText();
|
||||
bool GetStatus();
|
||||
LoginStatus GetStatus() const
|
||||
{
|
||||
return loginStatus;
|
||||
}
|
||||
void Tick();
|
||||
User GetUser();
|
||||
virtual ~LoginModel();
|
||||
~LoginModel();
|
||||
};
|
||||
|
@ -1,18 +1,13 @@
|
||||
#include "LoginView.h"
|
||||
|
||||
#include "LoginModel.h"
|
||||
#include "LoginController.h"
|
||||
|
||||
#include "graphics/Graphics.h"
|
||||
#include "gui/interface/Button.h"
|
||||
#include "gui/interface/Label.h"
|
||||
#include "gui/interface/Textbox.h"
|
||||
#include "gui/Style.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
|
||||
#include "Misc.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
LoginView::LoginView():
|
||||
@ -39,11 +34,15 @@ LoginView::LoginView():
|
||||
loginButton->Appearance.HorizontalAlign = ui::Appearance::AlignRight;
|
||||
loginButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
|
||||
loginButton->Appearance.TextInactive = style::Colour::ConfirmButton;
|
||||
loginButton->SetActionCallback({ [this] { c->Login(usernameField->GetText().ToUtf8(), passwordField->GetText().ToUtf8()); } });
|
||||
loginButton->SetActionCallback({ [this] {
|
||||
c->Login(usernameField->GetText().ToUtf8(), passwordField->GetText().ToUtf8());
|
||||
} });
|
||||
AddComponent(cancelButton);
|
||||
cancelButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
cancelButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
|
||||
cancelButton->SetActionCallback({ [this] { c->Exit(); } });
|
||||
cancelButton->SetActionCallback({ [this] {
|
||||
c->Logout();
|
||||
} });
|
||||
AddComponent(titleLabel);
|
||||
titleLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
titleLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
|
||||
@ -85,12 +84,17 @@ void LoginView::NotifyStatusChanged(LoginModel * sender)
|
||||
targetSize.Y = 87;
|
||||
infoLabel->SetText(sender->GetStatusText());
|
||||
infoLabel->AutoHeight();
|
||||
auto notWorking = sender->GetStatus() != loginWorking;
|
||||
loginButton->Enabled = notWorking;
|
||||
cancelButton->Enabled = notWorking && Client::Ref().GetAuthUser().UserID;
|
||||
usernameField->Enabled = notWorking;
|
||||
passwordField->Enabled = notWorking;
|
||||
if (sender->GetStatusText().length())
|
||||
{
|
||||
targetSize.Y += infoLabel->Size.Y+2;
|
||||
infoLabel->Visible = true;
|
||||
}
|
||||
if(sender->GetStatus())
|
||||
if (sender->GetStatus() == loginSucceeded)
|
||||
{
|
||||
c->Exit();
|
||||
}
|
||||
@ -98,6 +102,7 @@ void LoginView::NotifyStatusChanged(LoginModel * sender)
|
||||
|
||||
void LoginView::OnTick(float dt)
|
||||
{
|
||||
c->Tick();
|
||||
//if(targetSize != Size)
|
||||
{
|
||||
ui::Point difference = targetSize-Size;
|
||||
|
@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
|
||||
class SaveComment
|
||||
{
|
||||
public:
|
||||
int authorID;
|
||||
ByteString authorName;
|
||||
ByteString authorNameFormatted;
|
||||
String comment;
|
||||
SaveComment(int userID, ByteString username, ByteString usernameFormatted, String commentText):
|
||||
authorID(userID), authorName(username), authorNameFormatted(usernameFormatted), comment(commentText)
|
||||
{
|
||||
}
|
||||
SaveComment(const SaveComment & comment):
|
||||
authorID(comment.authorID), authorName(comment.authorName), authorNameFormatted(comment.authorNameFormatted), comment(comment.comment)
|
||||
{
|
||||
}
|
||||
SaveComment(const SaveComment * comment):
|
||||
authorID(comment->authorID), authorName(comment->authorName), authorNameFormatted(comment->authorNameFormatted), comment(comment->comment)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
@ -6,6 +6,10 @@
|
||||
#include "client/Client.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "client/http/GetSaveRequest.h"
|
||||
#include "client/http/GetSaveDataRequest.h"
|
||||
#include "client/http/GetCommentsRequest.h"
|
||||
#include "client/http/FavouriteSaveRequest.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "graphics/Graphics.h"
|
||||
#include "gui/dialogues/ErrorMessage.h"
|
||||
@ -51,30 +55,6 @@ void PreviewController::Update()
|
||||
}
|
||||
}
|
||||
|
||||
bool PreviewController::SubmitComment(String comment)
|
||||
{
|
||||
if(comment.length() < 4)
|
||||
{
|
||||
new ErrorMessage("Error", "Comment is too short");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestStatus status = Client::Ref().AddComment(saveId, comment);
|
||||
if(status != RequestOkay)
|
||||
{
|
||||
new ErrorMessage("Error submitting comment", Client::Ref().GetLastError());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
previewModel->CommentAdded();
|
||||
previewModel->UpdateComments(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PreviewController::ShowLogin()
|
||||
{
|
||||
loginWindow = new LoginController();
|
||||
@ -106,32 +86,11 @@ void PreviewController::DoOpen()
|
||||
previewModel->SetDoOpen(true);
|
||||
}
|
||||
|
||||
void PreviewController::Report(String message)
|
||||
{
|
||||
if(Client::Ref().ReportSave(saveId, message) == RequestOkay)
|
||||
{
|
||||
Exit();
|
||||
new InformationMessage("Information", "Report submitted", false);
|
||||
}
|
||||
else
|
||||
new ErrorMessage("Error", "Unable to file report: " + Client::Ref().GetLastError());
|
||||
}
|
||||
|
||||
void PreviewController::FavouriteSave()
|
||||
{
|
||||
if(previewModel->GetSaveInfo() && Client::Ref().GetAuthUser().UserID)
|
||||
if (previewModel->GetSaveInfo() && Client::Ref().GetAuthUser().UserID)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(previewModel->GetSaveInfo()->Favourite)
|
||||
previewModel->SetFavourite(false);
|
||||
else
|
||||
previewModel->SetFavourite(true);
|
||||
}
|
||||
catch (PreviewModelException & e)
|
||||
{
|
||||
new ErrorMessage("Error", ByteString(e.what()).FromUtf8());
|
||||
}
|
||||
previewModel->SetFavourite(!previewModel->GetSaveInfo()->Favourite);
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +120,12 @@ bool PreviewController::PrevCommentPage()
|
||||
return false;
|
||||
}
|
||||
|
||||
void PreviewController::CommentAdded()
|
||||
{
|
||||
previewModel->CommentAdded();
|
||||
previewModel->UpdateComments(1);
|
||||
}
|
||||
|
||||
void PreviewController::Exit()
|
||||
{
|
||||
previewView->CloseActiveWindow();
|
||||
|
@ -23,7 +23,6 @@ public:
|
||||
void Exit();
|
||||
void DoOpen();
|
||||
void OpenInBrowser();
|
||||
void Report(String message);
|
||||
void ShowLogin();
|
||||
bool GetDoOpen();
|
||||
const SaveInfo *GetSaveInfo() const;
|
||||
@ -31,10 +30,10 @@ public:
|
||||
PreviewView * GetView() { return previewView; }
|
||||
void Update();
|
||||
void FavouriteSave();
|
||||
bool SubmitComment(String comment);
|
||||
|
||||
bool NextCommentPage();
|
||||
bool PrevCommentPage();
|
||||
void CommentAdded();
|
||||
|
||||
virtual ~PreviewController();
|
||||
};
|
||||
|
@ -1,46 +1,27 @@
|
||||
#include "PreviewModel.h"
|
||||
#include "client/http/Request.h"
|
||||
#include "client/http/GetSaveDataRequest.h"
|
||||
#include "client/http/GetSaveRequest.h"
|
||||
#include "client/http/GetCommentsRequest.h"
|
||||
#include "client/http/FavouriteSaveRequest.h"
|
||||
#include "Format.h"
|
||||
#include "Misc.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "gui/dialogues/ErrorMessage.h"
|
||||
#include "gui/preview/Comment.h"
|
||||
#include "PreviewModelException.h"
|
||||
#include "PreviewView.h"
|
||||
#include "Config.h"
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
PreviewModel::PreviewModel():
|
||||
doOpen(false),
|
||||
canOpen(true),
|
||||
saveData(NULL),
|
||||
saveComments(NULL),
|
||||
commentBoxEnabled(false),
|
||||
commentsLoaded(false),
|
||||
commentsTotal(0),
|
||||
commentsPageNumber(1)
|
||||
{
|
||||
}
|
||||
|
||||
PreviewModel::~PreviewModel()
|
||||
{
|
||||
delete saveData;
|
||||
ClearComments();
|
||||
}
|
||||
constexpr auto commentsPerPage = 20;
|
||||
|
||||
void PreviewModel::SetFavourite(bool favourite)
|
||||
{
|
||||
if (saveInfo)
|
||||
{
|
||||
if (Client::Ref().FavouriteSave(saveInfo->id, favourite) == RequestOkay)
|
||||
saveInfo->Favourite = favourite;
|
||||
else if (favourite)
|
||||
throw PreviewModelException("Error, could not fav. the save: " + Client::Ref().GetLastError());
|
||||
else
|
||||
throw PreviewModelException("Error, could not unfav. the save: " + Client::Ref().GetLastError());
|
||||
notifySaveChanged();
|
||||
queuedFavourite = favourite;
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,37 +45,22 @@ void PreviewModel::UpdateSave(int saveID, int saveDate)
|
||||
this->saveDate = saveDate;
|
||||
|
||||
saveInfo.reset();
|
||||
if (saveData)
|
||||
{
|
||||
delete saveData;
|
||||
saveData = NULL;
|
||||
}
|
||||
ClearComments();
|
||||
saveData.reset();
|
||||
saveComments.reset();
|
||||
notifySaveChanged();
|
||||
notifySaveCommentsChanged();
|
||||
|
||||
ByteString url;
|
||||
if (saveDate)
|
||||
url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
|
||||
else
|
||||
url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
|
||||
saveDataDownload = std::make_unique<http::Request>(url);
|
||||
saveDataDownload = std::make_unique<http::GetSaveDataRequest>(saveID, saveDate);
|
||||
saveDataDownload->Start();
|
||||
|
||||
url = ByteString::Build(SCHEME, SERVER , "/Browse/View.json?ID=", saveID);
|
||||
if (saveDate)
|
||||
url += ByteString::Build("&Date=", saveDate);
|
||||
saveInfoDownload = std::make_unique<http::Request>(url);
|
||||
saveInfoDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
|
||||
saveInfoDownload = std::make_unique<http::GetSaveRequest>(saveID, saveDate);
|
||||
saveInfoDownload->Start();
|
||||
|
||||
if (!GetDoOpen())
|
||||
{
|
||||
commentsLoaded = false;
|
||||
|
||||
url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
|
||||
commentsDownload = std::make_unique<http::Request>(url);
|
||||
commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
|
||||
commentsDownload = std::make_unique<http::GetCommentsRequest>(saveID, (commentsPageNumber - 1) * commentsPerPage, commentsPerPage);
|
||||
commentsDownload->Start();
|
||||
}
|
||||
}
|
||||
@ -131,7 +97,7 @@ int PreviewModel::GetCommentsPageNum()
|
||||
|
||||
int PreviewModel::GetCommentsPageCount()
|
||||
{
|
||||
return std::max(1, (int)(ceil(commentsTotal/20.0f)));
|
||||
return std::max(1, ceilDiv(commentsTotal, commentsPerPage).first);
|
||||
}
|
||||
|
||||
bool PreviewModel::GetCommentsLoaded()
|
||||
@ -144,14 +110,12 @@ void PreviewModel::UpdateComments(int pageNumber)
|
||||
if (commentsLoaded)
|
||||
{
|
||||
commentsLoaded = false;
|
||||
ClearComments();
|
||||
saveComments.reset();
|
||||
|
||||
commentsPageNumber = pageNumber;
|
||||
if (!GetDoOpen())
|
||||
{
|
||||
ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
|
||||
commentsDownload = std::make_unique<http::Request>(url);
|
||||
commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
|
||||
commentsDownload = std::make_unique<http::GetCommentsRequest>(saveID, (commentsPageNumber - 1) * commentsPerPage, commentsPerPage);
|
||||
commentsDownload->Start();
|
||||
}
|
||||
|
||||
@ -189,175 +153,105 @@ void PreviewModel::OnSaveReady()
|
||||
notifySaveCommentsChanged();
|
||||
}
|
||||
|
||||
void PreviewModel::ClearComments()
|
||||
{
|
||||
if (saveComments)
|
||||
{
|
||||
for (size_t i = 0; i < saveComments->size(); i++)
|
||||
delete saveComments->at(i);
|
||||
saveComments->clear();
|
||||
delete saveComments;
|
||||
saveComments = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool PreviewModel::ParseSaveInfo(ByteString &saveInfoResponse)
|
||||
{
|
||||
saveInfo.reset();
|
||||
|
||||
try // how does this differ from Client::GetSave?
|
||||
{
|
||||
std::istringstream dataStream(saveInfoResponse);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
int tempID = objDocument["ID"].asInt();
|
||||
int tempScoreUp = objDocument["ScoreUp"].asInt();
|
||||
int tempScoreDown = objDocument["ScoreDown"].asInt();
|
||||
int tempMyScore = objDocument["ScoreMine"].asInt();
|
||||
ByteString tempUsername = objDocument["Username"].asString();
|
||||
String tempName = ByteString(objDocument["Name"].asString()).FromUtf8();
|
||||
String tempDescription = ByteString(objDocument["Description"].asString()).FromUtf8();
|
||||
int tempCreatedDate = objDocument["DateCreated"].asInt();
|
||||
int tempUpdatedDate = objDocument["Date"].asInt();
|
||||
bool tempPublished = objDocument["Published"].asBool();
|
||||
bool tempFavourite = objDocument["Favourite"].asBool();
|
||||
int tempComments = objDocument["Comments"].asInt();
|
||||
int tempViews = objDocument["Views"].asInt();
|
||||
int tempVersion = objDocument["Version"].asInt();
|
||||
|
||||
Json::Value tagsArray = objDocument["Tags"];
|
||||
std::list<ByteString> tempTags;
|
||||
for (Json::UInt j = 0; j < tagsArray.size(); j++)
|
||||
tempTags.push_back(tagsArray[j].asString());
|
||||
|
||||
auto newSaveInfo = std::make_unique<SaveInfo>(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp,
|
||||
tempScoreDown, tempMyScore, tempUsername, tempName,
|
||||
tempDescription, tempPublished, tempTags);
|
||||
newSaveInfo->Comments = tempComments;
|
||||
newSaveInfo->Favourite = tempFavourite;
|
||||
newSaveInfo->Views = tempViews;
|
||||
newSaveInfo->Version = tempVersion;
|
||||
|
||||
// This is a workaround for a bug on the TPT server where the wrong 404 save is returned
|
||||
// Redownload the .cps file for a fixed version of the 404 save
|
||||
if (tempID == 404 && this->saveID != 404)
|
||||
{
|
||||
delete saveData;
|
||||
saveData = NULL;
|
||||
saveDataDownload = std::make_unique<http::Request>(ByteString::Build(STATICSCHEME, STATICSERVER, "/2157797.cps"));
|
||||
saveDataDownload->Start();
|
||||
}
|
||||
saveInfo = std::move(newSaveInfo);
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreviewModel::ParseComments(ByteString &commentsResponse)
|
||||
{
|
||||
ClearComments();
|
||||
saveComments = new std::vector<SaveComment*>();
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(commentsResponse);
|
||||
Json::Value commentsArray;
|
||||
dataStream >> commentsArray;
|
||||
|
||||
for (Json::UInt j = 0; j < commentsArray.size(); j++)
|
||||
{
|
||||
int userID = ByteString(commentsArray[j]["UserID"].asString()).ToNumber<int>();
|
||||
ByteString username = commentsArray[j]["Username"].asString();
|
||||
ByteString formattedUsername = commentsArray[j]["FormattedUsername"].asString();
|
||||
if (formattedUsername == "jacobot")
|
||||
formattedUsername = "\bt" + formattedUsername;
|
||||
String comment = ByteString(commentsArray[j]["Text"].asString()).FromUtf8();
|
||||
saveComments->push_back(new SaveComment(userID, username, formattedUsername, comment));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewModel::Update()
|
||||
{
|
||||
auto triggerOnSaveReady = false;
|
||||
if (saveDataDownload && saveDataDownload->CheckDone())
|
||||
{
|
||||
auto [ status, ret ] = saveDataDownload->Finish();
|
||||
|
||||
ByteString nothing;
|
||||
Client::Ref().ParseServerReturn(nothing, status, true);
|
||||
if (status == 200 && ret.size())
|
||||
try
|
||||
{
|
||||
delete saveData;
|
||||
saveData = new std::vector<char>(ret.begin(), ret.end());
|
||||
if (saveInfo && saveData)
|
||||
OnSaveReady();
|
||||
saveData = saveDataDownload->Finish();
|
||||
triggerOnSaveReady = true;
|
||||
}
|
||||
else
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
auto why = ByteString(ex.what()).FromUtf8();
|
||||
for (size_t i = 0; i < observers.size(); i++)
|
||||
{
|
||||
observers[i]->SaveLoadingError(Client::Ref().GetLastError());
|
||||
observers[i]->SaveLoadingError(why);
|
||||
}
|
||||
}
|
||||
saveDataDownload.reset();
|
||||
}
|
||||
|
||||
if (saveInfoDownload && saveInfoDownload->CheckDone())
|
||||
{
|
||||
auto [ status, ret ] = saveInfoDownload->Finish();
|
||||
|
||||
ByteString nothing;
|
||||
Client::Ref().ParseServerReturn(nothing, status, true);
|
||||
if (status == 200 && ret.size())
|
||||
try
|
||||
{
|
||||
if (ParseSaveInfo(ret))
|
||||
saveInfo = saveInfoDownload->Finish();
|
||||
triggerOnSaveReady = true;
|
||||
// This is a workaround for a bug on the TPT server where the wrong 404 save is returned
|
||||
// Redownload the .cps file for a fixed version of the 404 save
|
||||
if (saveInfo->GetID() == 404 && saveID != 404)
|
||||
{
|
||||
if (saveInfo && saveData)
|
||||
OnSaveReady();
|
||||
saveData.reset();
|
||||
saveDataDownload = std::make_unique<http::GetSaveDataRequest>(2157797, 0);
|
||||
saveDataDownload->Start();
|
||||
}
|
||||
else
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
auto why = ByteString(ex.what()).FromUtf8();
|
||||
for (size_t i = 0; i < observers.size(); i++)
|
||||
observers[i]->SaveLoadingError("Could not parse save info");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < observers.size(); i++)
|
||||
observers[i]->SaveLoadingError(Client::Ref().GetLastError());
|
||||
observers[i]->SaveLoadingError(why);
|
||||
}
|
||||
}
|
||||
saveInfoDownload.reset();
|
||||
}
|
||||
if (triggerOnSaveReady && saveInfo && saveData)
|
||||
{
|
||||
OnSaveReady();
|
||||
}
|
||||
|
||||
if (commentsDownload && commentsDownload->CheckDone())
|
||||
{
|
||||
auto [ status, ret ] = commentsDownload->Finish();
|
||||
ClearComments();
|
||||
|
||||
ByteString nothing;
|
||||
Client::Ref().ParseServerReturn(nothing, status, true);
|
||||
if (status == 200 && ret.size())
|
||||
ParseComments(ret);
|
||||
|
||||
try
|
||||
{
|
||||
saveComments = commentsDownload->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
// TODO: handle
|
||||
}
|
||||
commentsLoaded = true;
|
||||
notifySaveCommentsChanged();
|
||||
notifyCommentsPageChanged();
|
||||
|
||||
commentsDownload.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SaveComment*> * PreviewModel::GetComments()
|
||||
{
|
||||
return saveComments;
|
||||
if (favouriteSaveRequest && favouriteSaveRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
favouriteSaveRequest->Finish();
|
||||
if (saveInfo)
|
||||
{
|
||||
saveInfo->Favourite = favouriteSaveRequest->Favourite();
|
||||
notifySaveChanged();
|
||||
}
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
if (favouriteSaveRequest->Favourite())
|
||||
{
|
||||
throw PreviewModelException("Error, could not fav. the save: " + ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw PreviewModelException("Error, could not unfav. the save: " + ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
}
|
||||
favouriteSaveRequest.reset();
|
||||
}
|
||||
if (!favouriteSaveRequest && queuedFavourite)
|
||||
{
|
||||
if (saveInfo)
|
||||
{
|
||||
favouriteSaveRequest = std::make_unique<http::FavouriteSaveRequest>(saveInfo->id, *queuedFavourite);
|
||||
favouriteSaveRequest->Start();
|
||||
}
|
||||
queuedFavourite.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewModel::notifySaveChanged()
|
||||
|
@ -1,47 +1,54 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include "client/Comment.h"
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class Request;
|
||||
class GetSaveDataRequest;
|
||||
class GetSaveRequest;
|
||||
class GetCommentsRequest;
|
||||
class FavouriteSaveRequest;
|
||||
}
|
||||
|
||||
class PreviewView;
|
||||
class SaveInfo;
|
||||
class SaveComment;
|
||||
class PreviewModel
|
||||
{
|
||||
bool doOpen;
|
||||
bool canOpen;
|
||||
bool doOpen = false;
|
||||
bool canOpen = true;
|
||||
std::vector<PreviewView*> observers;
|
||||
std::unique_ptr<SaveInfo> saveInfo;
|
||||
std::vector<char> * saveData;
|
||||
std::vector<SaveComment*> * saveComments;
|
||||
std::optional<std::vector<char>> saveData;
|
||||
std::optional<std::vector<Comment>> saveComments;
|
||||
void notifySaveChanged();
|
||||
void notifySaveCommentsChanged();
|
||||
void notifyCommentsPageChanged();
|
||||
void notifyCommentBoxEnabledChanged();
|
||||
|
||||
std::unique_ptr<http::Request> saveDataDownload;
|
||||
std::unique_ptr<http::Request> saveInfoDownload;
|
||||
std::unique_ptr<http::Request> commentsDownload;
|
||||
std::unique_ptr<http::GetSaveDataRequest> saveDataDownload;
|
||||
std::unique_ptr<http::GetSaveRequest> saveInfoDownload;
|
||||
std::unique_ptr<http::GetCommentsRequest> commentsDownload;
|
||||
std::unique_ptr<http::FavouriteSaveRequest> favouriteSaveRequest;
|
||||
int saveID;
|
||||
int saveDate;
|
||||
|
||||
bool commentBoxEnabled;
|
||||
bool commentsLoaded;
|
||||
int commentsTotal;
|
||||
int commentsPageNumber;
|
||||
bool commentBoxEnabled = false;
|
||||
bool commentsLoaded = false;
|
||||
int commentsTotal = 0;
|
||||
int commentsPageNumber = 1;
|
||||
|
||||
std::optional<bool> queuedFavourite;
|
||||
|
||||
public:
|
||||
PreviewModel();
|
||||
~PreviewModel();
|
||||
|
||||
const SaveInfo *GetSaveInfo() const;
|
||||
std::unique_ptr<SaveInfo> TakeSaveInfo();
|
||||
std::vector<SaveComment*> * GetComments();
|
||||
const std::vector<Comment> *GetComments() const
|
||||
{
|
||||
return saveComments ? &*saveComments : nullptr;
|
||||
}
|
||||
|
||||
bool GetCommentBoxEnabled();
|
||||
void SetCommentBoxEnabled(bool enabledState);
|
||||
@ -59,7 +66,6 @@ public:
|
||||
bool GetCanOpen();
|
||||
void SetDoOpen(bool doOpen);
|
||||
void Update();
|
||||
void ClearComments();
|
||||
void OnSaveReady();
|
||||
bool ParseSaveInfo(ByteString &saveInfoResponse);
|
||||
bool ParseComments(ByteString &commentsResponse);
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "client/Client.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/http/AddCommentRequest.h"
|
||||
#include "client/http/ReportSaveRequest.h"
|
||||
|
||||
#include "gui/dialogues/TextPrompt.h"
|
||||
#include "gui/profile/ProfileActivity.h"
|
||||
@ -17,12 +19,12 @@
|
||||
#include "gui/interface/Textbox.h"
|
||||
#include "gui/interface/Engine.h"
|
||||
#include "gui/dialogues/ErrorMessage.h"
|
||||
#include "gui/dialogues/InformationMessage.h"
|
||||
#include "gui/interface/Point.h"
|
||||
#include "gui/interface/Window.h"
|
||||
#include "gui/Style.h"
|
||||
|
||||
#include "common/tpt-rand.h"
|
||||
#include "Comment.h"
|
||||
#include "Format.h"
|
||||
#include "Misc.h"
|
||||
|
||||
@ -57,6 +59,7 @@ PreviewView::PreviewView(std::unique_ptr<VideoBuffer> newSavePreview):
|
||||
favButton = new ui::Button(ui::Point(50, Size.Y-19), ui::Point(51, 19), "Fav");
|
||||
favButton->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
favButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
|
||||
favButton->SetTogglable(true);
|
||||
favButton->SetIcon(IconFavourite);
|
||||
favButton->SetActionCallback({ [this] { c->FavouriteSave(); } });
|
||||
favButton->Enabled = Client::Ref().GetAuthUser().UserID?true:false;
|
||||
@ -68,7 +71,12 @@ PreviewView::PreviewView(std::unique_ptr<VideoBuffer> newSavePreview):
|
||||
reportButton->SetIcon(IconReport);
|
||||
reportButton->SetActionCallback({ [this] {
|
||||
new TextPrompt("Report Save", "Things to consider when reporting:\n\bw1)\bg When reporting stolen saves, please include the ID of the original save.\n\bw2)\bg Do not ask for saves to be removed from front page unless they break the rules.\n\bw3)\bg You may report saves for comments or tags too (including your own saves)", "", "[reason]", true, { [this](String const &resultText) {
|
||||
c->Report(resultText);
|
||||
if (reportSaveRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
reportSaveRequest = std::make_unique<http::ReportSaveRequest>(c->SaveID(), resultText);
|
||||
reportSaveRequest->Start();
|
||||
} });
|
||||
} });
|
||||
reportButton->Enabled = Client::Ref().GetAuthUser().UserID?true:false;
|
||||
@ -222,7 +230,12 @@ void PreviewView::CheckComment()
|
||||
if (!commentWarningLabel)
|
||||
return;
|
||||
String text = addCommentBox->GetText().ToLower();
|
||||
if (!userIsAuthor && (text.Contains("stolen") || text.Contains("copied")))
|
||||
if (addCommentRequest)
|
||||
{
|
||||
commentWarningLabel->SetText("Submitting comment...");
|
||||
commentHelpText = true;
|
||||
}
|
||||
else if (!userIsAuthor && (text.Contains("stolen") || text.Contains("copied")))
|
||||
{
|
||||
if (!commentHelpText)
|
||||
{
|
||||
@ -375,6 +388,37 @@ void PreviewView::OnTick(float dt)
|
||||
ErrorMessage::Blocking("Error loading save", doErrorMessage);
|
||||
c->Exit();
|
||||
}
|
||||
|
||||
if (reportSaveRequest && reportSaveRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
reportSaveRequest->Finish();
|
||||
c->Exit();
|
||||
new InformationMessage("Information", "Report submitted", false);
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
new ErrorMessage("Error", "Unable to file report: " + ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
reportSaveRequest.reset();
|
||||
}
|
||||
if (addCommentRequest && addCommentRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
addCommentBox->SetText("");
|
||||
c->CommentAdded();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
new ErrorMessage("Error submitting comment", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
submitCommentButton->Enabled = true;
|
||||
commentBoxAutoHeight();
|
||||
addCommentRequest.reset();
|
||||
CheckComment();
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewView::OnTryExit(ExitMethod method)
|
||||
@ -448,16 +492,16 @@ void PreviewView::NotifySaveChanged(PreviewModel * sender)
|
||||
if(save->Favourite)
|
||||
{
|
||||
favButton->Enabled = true;
|
||||
favButton->SetText("Unfav");
|
||||
favButton->SetToggleState(true);
|
||||
}
|
||||
else if(Client::Ref().GetAuthUser().UserID)
|
||||
{
|
||||
favButton->Enabled = true;
|
||||
favButton->SetText("Fav");
|
||||
favButton->SetToggleState(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
favButton->SetText("Fav");
|
||||
favButton->SetToggleState(false);
|
||||
favButton->Enabled = false;
|
||||
}
|
||||
|
||||
@ -477,6 +521,7 @@ void PreviewView::NotifySaveChanged(PreviewModel * sender)
|
||||
saveNameLabel->SetText("");
|
||||
authorDateLabel->SetText("");
|
||||
saveDescriptionLabel->SetText("");
|
||||
favButton->SetToggleState(false);
|
||||
favButton->Enabled = false;
|
||||
if (!sender->GetCanOpen())
|
||||
openButton->Enabled = false;
|
||||
@ -485,21 +530,22 @@ void PreviewView::NotifySaveChanged(PreviewModel * sender)
|
||||
|
||||
void PreviewView::submitComment()
|
||||
{
|
||||
if(addCommentBox)
|
||||
if (addCommentBox)
|
||||
{
|
||||
String comment = addCommentBox->GetText();
|
||||
if (comment.length() < 4)
|
||||
{
|
||||
new ErrorMessage("Error", "Comment is too short");
|
||||
return;
|
||||
}
|
||||
|
||||
submitCommentButton->Enabled = false;
|
||||
addCommentBox->SetText("");
|
||||
addCommentBox->SetPlaceholder("Submitting comment"); //This doesn't appear to ever show since no separate thread is created
|
||||
FocusComponent(NULL);
|
||||
|
||||
if (!c->SubmitComment(comment))
|
||||
addCommentBox->SetText(comment);
|
||||
addCommentRequest = std::make_unique<http::AddCommentRequest>(c->SaveID(), comment);
|
||||
addCommentRequest->Start();
|
||||
|
||||
addCommentBox->SetPlaceholder("Add comment");
|
||||
submitCommentButton->Enabled = true;
|
||||
|
||||
commentBoxAutoHeight();
|
||||
CheckComment();
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,7 +610,7 @@ void PreviewView::NotifyCommentsPageChanged(PreviewModel * sender)
|
||||
|
||||
void PreviewView::NotifyCommentsChanged(PreviewModel * sender)
|
||||
{
|
||||
std::vector<SaveComment*> * comments = sender->GetComments();
|
||||
auto commentsPtr = sender->GetComments();
|
||||
|
||||
for (size_t i = 0; i < commentComponents.size(); i++)
|
||||
{
|
||||
@ -575,8 +621,9 @@ void PreviewView::NotifyCommentsChanged(PreviewModel * sender)
|
||||
commentTextComponents.clear();
|
||||
commentsPanel->InnerSize = ui::Point(0, 0);
|
||||
|
||||
if (comments)
|
||||
if (commentsPtr)
|
||||
{
|
||||
auto &comments = *commentsPtr;
|
||||
for (size_t i = 0; i < commentComponents.size(); i++)
|
||||
{
|
||||
commentsPanel->RemoveChild(commentComponents[i]);
|
||||
@ -589,11 +636,11 @@ void PreviewView::NotifyCommentsChanged(PreviewModel * sender)
|
||||
ui::Label * tempUsername;
|
||||
ui::Label * tempComment;
|
||||
ui::AvatarButton * tempAvatar;
|
||||
for (size_t i = 0; i < comments->size(); i++)
|
||||
for (size_t i = 0; i < comments.size(); i++)
|
||||
{
|
||||
if (showAvatars)
|
||||
{
|
||||
tempAvatar = new ui::AvatarButton(ui::Point(2, currentY+7), ui::Point(26, 26), comments->at(i)->authorName);
|
||||
tempAvatar = new ui::AvatarButton(ui::Point(2, currentY+7), ui::Point(26, 26), comments[i].authorName);
|
||||
tempAvatar->SetActionCallback({ [tempAvatar] {
|
||||
if (tempAvatar->GetUsername().size() > 0)
|
||||
{
|
||||
@ -604,25 +651,38 @@ void PreviewView::NotifyCommentsChanged(PreviewModel * sender)
|
||||
commentsPanel->AddChild(tempAvatar);
|
||||
}
|
||||
|
||||
auto authorNameFormatted = comments[i].authorName.FromUtf8();
|
||||
if (comments[i].authorElevation != User::ElevationNone || comments[i].authorName == "jacobot")
|
||||
{
|
||||
authorNameFormatted = "\bt" + authorNameFormatted;
|
||||
}
|
||||
else if (comments[i].authorIsBanned)
|
||||
{
|
||||
authorNameFormatted = "\bg" + authorNameFormatted;
|
||||
}
|
||||
else if (Client::Ref().GetAuthUser().UserID && Client::Ref().GetAuthUser().Username == comments[i].authorName)
|
||||
{
|
||||
authorNameFormatted = "\bo" + authorNameFormatted;
|
||||
}
|
||||
else if (sender->GetSaveInfo() && sender->GetSaveInfo()->GetUserName() == comments[i].authorName)
|
||||
{
|
||||
authorNameFormatted = "\bl" + authorNameFormatted;
|
||||
}
|
||||
if (showAvatars)
|
||||
tempUsername = new ui::Label(ui::Point(31, currentY+3), ui::Point(Size.X-((XRES/2) + 13 + 26), 16), comments->at(i)->authorNameFormatted.FromUtf8());
|
||||
tempUsername = new ui::Label(ui::Point(31, currentY+3), ui::Point(Size.X-((XRES/2) + 13 + 26), 16), authorNameFormatted);
|
||||
else
|
||||
tempUsername = new ui::Label(ui::Point(5, currentY+3), ui::Point(Size.X-((XRES/2) + 13), 16), comments->at(i)->authorNameFormatted.FromUtf8());
|
||||
tempUsername = new ui::Label(ui::Point(5, currentY+3), ui::Point(Size.X-((XRES/2) + 13), 16), authorNameFormatted);
|
||||
tempUsername->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
tempUsername->Appearance.VerticalAlign = ui::Appearance::AlignBottom;
|
||||
if (Client::Ref().GetAuthUser().UserID && Client::Ref().GetAuthUser().Username == comments->at(i)->authorName)
|
||||
tempUsername->SetTextColour(ui::Colour(255, 255, 100));
|
||||
else if (sender->GetSaveInfo() && sender->GetSaveInfo()->GetUserName() == comments->at(i)->authorName)
|
||||
tempUsername->SetTextColour(ui::Colour(255, 100, 100));
|
||||
currentY += 16;
|
||||
|
||||
commentComponents.push_back(tempUsername);
|
||||
commentsPanel->AddChild(tempUsername);
|
||||
|
||||
if (showAvatars)
|
||||
tempComment = new ui::Label(ui::Point(31, currentY+5), ui::Point(Size.X-((XRES/2) + 13 + 26), -1), comments->at(i)->comment);
|
||||
tempComment = new ui::Label(ui::Point(31, currentY+5), ui::Point(Size.X-((XRES/2) + 13 + 26), -1), comments[i].content);
|
||||
else
|
||||
tempComment = new ui::Label(ui::Point(5, currentY+5), ui::Point(Size.X-((XRES/2) + 13), -1), comments->at(i)->comment);
|
||||
tempComment = new ui::Label(ui::Point(5, currentY+5), ui::Point(Size.X-((XRES/2) + 13), -1), comments[i].content);
|
||||
tempComment->SetMultiline(true);
|
||||
tempComment->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
|
||||
tempComment->Appearance.VerticalAlign = ui::Appearance::AlignTop;
|
||||
|
@ -5,6 +5,12 @@
|
||||
#include "common/String.h"
|
||||
#include "gui/interface/Window.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class AddCommentRequest;
|
||||
class ReportSaveRequest;
|
||||
}
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class Button;
|
||||
@ -64,6 +70,10 @@ class PreviewView: public ui::Window
|
||||
void submitComment();
|
||||
bool CheckSwearing(String text);
|
||||
void CheckComment();
|
||||
|
||||
std::unique_ptr<http::AddCommentRequest> addCommentRequest;
|
||||
std::unique_ptr<http::ReportSaveRequest> reportSaveRequest;
|
||||
|
||||
public:
|
||||
void AttachController(PreviewController * controller);
|
||||
PreviewView(std::unique_ptr<VideoBuffer> newSavePreviev);
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "ProfileActivity.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/http/SaveUserInfoRequest.h"
|
||||
#include "client/http/GetUserInfoRequest.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "gui/Style.h"
|
||||
#include "gui/interface/AvatarButton.h"
|
||||
@ -200,30 +202,30 @@ void ProfileActivity::OnTick(float dt)
|
||||
|
||||
if (saveUserInfoRequest && saveUserInfoRequest->CheckDone())
|
||||
{
|
||||
auto SaveUserInfoStatus = saveUserInfoRequest->Finish();
|
||||
if (SaveUserInfoStatus)
|
||||
try
|
||||
{
|
||||
saveUserInfoRequest->Finish();
|
||||
Exit();
|
||||
}
|
||||
else
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
doError = true;
|
||||
doErrorMessage = "Could not save user info: " + Client::Ref().GetLastError();
|
||||
doErrorMessage = "Could not save user info: " + ByteString(ex.what()).FromUtf8();
|
||||
}
|
||||
saveUserInfoRequest.reset();
|
||||
}
|
||||
if (getUserInfoRequest && getUserInfoRequest->CheckDone())
|
||||
{
|
||||
auto getUserInfoResult = getUserInfoRequest->Finish();
|
||||
if (getUserInfoResult)
|
||||
try
|
||||
{
|
||||
auto userInfo = getUserInfoRequest->Finish();
|
||||
loading = false;
|
||||
setUserInfo(*getUserInfoResult);
|
||||
setUserInfo(userInfo);
|
||||
}
|
||||
else
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
doError = true;
|
||||
doErrorMessage = "Could not load user info: " + Client::Ref().GetLastError();
|
||||
doErrorMessage = "Could not load user info: " + ByteString(ex.what()).FromUtf8();
|
||||
}
|
||||
getUserInfoRequest.reset();
|
||||
}
|
||||
|
@ -2,10 +2,14 @@
|
||||
#include "common/String.h"
|
||||
#include "Activity.h"
|
||||
#include "client/UserInfo.h"
|
||||
#include "client/http/SaveUserInfoRequest.h"
|
||||
#include "client/http/GetUserInfoRequest.h"
|
||||
#include <memory>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class SaveUserInfoRequest;
|
||||
class GetUserInfoRequest;
|
||||
}
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class Label;
|
||||
|
@ -1,7 +1,5 @@
|
||||
#include "ServerSaveActivity.h"
|
||||
|
||||
#include "graphics/Graphics.h"
|
||||
|
||||
#include "gui/interface/Label.h"
|
||||
#include "gui/interface/Textbox.h"
|
||||
#include "gui/interface/Button.h"
|
||||
@ -10,13 +8,11 @@
|
||||
#include "gui/dialogues/SaveIDMessage.h"
|
||||
#include "gui/dialogues/ConfirmPrompt.h"
|
||||
#include "gui/dialogues/InformationMessage.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
#include "client/ThumbnailRendererTask.h"
|
||||
#include "client/GameSave.h"
|
||||
|
||||
#include "client/http/UploadSaveRequest.h"
|
||||
#include "tasks/Task.h"
|
||||
|
||||
#include "gui/Style.h"
|
||||
|
||||
class SaveUploadTask: public Task
|
||||
@ -36,7 +32,19 @@ class SaveUploadTask: public Task
|
||||
bool doWork() override
|
||||
{
|
||||
notifyProgress(-1);
|
||||
return Client::Ref().UploadSave(save) == RequestOkay;
|
||||
auto uploadSaveRequest = std::make_unique<http::UploadSaveRequest>(save);
|
||||
uploadSaveRequest->Start();
|
||||
uploadSaveRequest->Wait();
|
||||
try
|
||||
{
|
||||
save.SetID(uploadSaveRequest->Finish());
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
notifyError(ByteString(ex.what()).FromUtf8());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
@ -168,7 +176,7 @@ void ServerSaveActivity::NotifyDone(Task * task)
|
||||
if(!task->GetSuccess())
|
||||
{
|
||||
Exit();
|
||||
new ErrorMessage("Error", Client::Ref().GetLastError());
|
||||
new ErrorMessage("Error", task->GetError());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -182,25 +190,21 @@ void ServerSaveActivity::NotifyDone(Task * task)
|
||||
|
||||
void ServerSaveActivity::Save()
|
||||
{
|
||||
if(nameField->GetText().length())
|
||||
if (!nameField->GetText().length())
|
||||
{
|
||||
new ErrorMessage("Error", "You must specify a save name.");
|
||||
return;
|
||||
}
|
||||
if(Client::Ref().GetAuthUser().Username != save->GetUserName() && publishedCheckbox->GetChecked())
|
||||
{
|
||||
new ConfirmPrompt("Publish", "This save was created by " + save->GetUserName().FromUtf8() + ", you're about to publish this under your own name; If you haven't been given permission by the author to do so, please uncheck the publish box, otherwise continue", { [this] {
|
||||
Exit();
|
||||
saveUpload();
|
||||
} });
|
||||
}
|
||||
else
|
||||
{
|
||||
Exit();
|
||||
saveUpload();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
new ErrorMessage("Error", "You must specify a save name.");
|
||||
}
|
||||
}
|
||||
|
||||
void ServerSaveActivity::AddAuthorInfo()
|
||||
@ -223,6 +227,7 @@ void ServerSaveActivity::AddAuthorInfo()
|
||||
|
||||
void ServerSaveActivity::saveUpload()
|
||||
{
|
||||
okayButton->Enabled = false;
|
||||
save->SetName(nameField->GetText());
|
||||
save->SetDescription(descriptionField->GetText());
|
||||
save->SetPublished(publishedCheckbox->GetChecked());
|
||||
@ -234,16 +239,8 @@ void ServerSaveActivity::saveUpload()
|
||||
save->SetGameSave(std::move(gameSave));
|
||||
}
|
||||
AddAuthorInfo();
|
||||
|
||||
if(Client::Ref().UploadSave(*save) != RequestOkay)
|
||||
{
|
||||
new ErrorMessage("Error", "Upload failed with error:\n"+Client::Ref().GetLastError());
|
||||
}
|
||||
else if (onUploaded)
|
||||
{
|
||||
new SaveIDMessage(save->GetID());
|
||||
onUploaded(std::move(save));
|
||||
}
|
||||
uploadSaveRequest = std::make_unique<http::UploadSaveRequest>(*save);
|
||||
uploadSaveRequest->Start();
|
||||
}
|
||||
|
||||
void ServerSaveActivity::Exit()
|
||||
@ -364,6 +361,26 @@ void ServerSaveActivity::OnTick(float dt)
|
||||
}
|
||||
}
|
||||
|
||||
if (uploadSaveRequest && uploadSaveRequest->CheckDone())
|
||||
{
|
||||
okayButton->Enabled = true;
|
||||
try
|
||||
{
|
||||
save->SetID(uploadSaveRequest->Finish());
|
||||
Exit();
|
||||
new SaveIDMessage(save->GetID());
|
||||
if (onUploaded)
|
||||
{
|
||||
onUploaded(std::move(save));
|
||||
}
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
new ErrorMessage("Error", "Upload failed with error:\n" + ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
uploadSaveRequest.reset();
|
||||
}
|
||||
|
||||
if(saveUploadTask)
|
||||
saveUploadTask->Poll();
|
||||
}
|
||||
|
@ -13,6 +13,11 @@
|
||||
|
||||
#include "save_online.png.h"
|
||||
|
||||
namespace http
|
||||
{
|
||||
class UploadSaveRequest;
|
||||
}
|
||||
|
||||
namespace ui
|
||||
{
|
||||
class Label;
|
||||
@ -25,6 +30,8 @@ class Task;
|
||||
class VideoBuffer;
|
||||
class ServerSaveActivity: public WindowActivity, public TaskListener
|
||||
{
|
||||
std::unique_ptr<http::UploadSaveRequest> uploadSaveRequest;
|
||||
|
||||
using OnUploaded = std::function<void (std::unique_ptr<SaveInfo>)>;
|
||||
std::unique_ptr<PlaneAdapter<std::vector<pixel_rgba>>> saveToServerImage = format::PixelsFromPNG(
|
||||
std::vector<char>(save_online_png, save_online_png + save_online_png_size)
|
||||
|
@ -7,6 +7,12 @@
|
||||
#include "client/Client.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "client/http/DeleteSaveRequest.h"
|
||||
#include "client/http/PublishSaveRequest.h"
|
||||
#include "client/http/UnpublishSaveRequest.h"
|
||||
#include "client/http/FavouriteSaveRequest.h"
|
||||
#include "client/http/SearchSavesRequest.h"
|
||||
#include "client/http/SearchTagsRequest.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "common/tpt-minmax.h"
|
||||
#include "graphics/Graphics.h"
|
||||
@ -132,13 +138,13 @@ void SearchController::SetPageRelative(int offset)
|
||||
|
||||
void SearchController::ChangeSort()
|
||||
{
|
||||
if(searchModel->GetSort() == "new")
|
||||
if(searchModel->GetSort() == http::sortByDate)
|
||||
{
|
||||
searchModel->SetSort("best");
|
||||
searchModel->SetSort(http::sortByVotes);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchModel->SetSort("new");
|
||||
searchModel->SetSort(http::sortByDate);
|
||||
}
|
||||
searchModel->UpdateSaveList(1, searchModel->GetLastQuery());
|
||||
}
|
||||
@ -183,7 +189,7 @@ void SearchController::SelectAllSaves()
|
||||
if (!Client::Ref().GetAuthUser().UserID)
|
||||
return;
|
||||
if (searchModel->GetShowOwn() ||
|
||||
Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator ||
|
||||
Client::Ref().GetAuthUser().UserElevation == User::ElevationMod ||
|
||||
Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin)
|
||||
searchModel->SelectAllSaves();
|
||||
|
||||
@ -245,9 +251,16 @@ void SearchController::removeSelectedC()
|
||||
for (size_t i = 0; i < saves.size(); i++)
|
||||
{
|
||||
notifyStatus(String::Build("Deleting save [", saves[i], "] ..."));
|
||||
if (Client::Ref().DeleteSave(saves[i])!=RequestOkay)
|
||||
auto deleteSaveRequest = std::make_unique<http::DeleteSaveRequest>(saves[i]);
|
||||
deleteSaveRequest->Start();
|
||||
deleteSaveRequest->Wait();
|
||||
try
|
||||
{
|
||||
notifyError(String::Build("Failed to delete [", saves[i], "]: ", Client::Ref().GetLastError()));
|
||||
deleteSaveRequest->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
notifyError(String::Build("Failed to delete [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
|
||||
c->Refresh();
|
||||
return false;
|
||||
}
|
||||
@ -286,37 +299,49 @@ void SearchController::unpublishSelectedC(bool publish)
|
||||
public:
|
||||
UnpublishSavesTask(std::vector<int> saves_, SearchController *c_, bool publish_) { saves = saves_; c = c_; publish = publish_; }
|
||||
|
||||
bool PublishSave(int saveID)
|
||||
void PublishSave(int saveID)
|
||||
{
|
||||
notifyStatus(String::Build("Publishing save [", saveID, "]"));
|
||||
if (Client::Ref().PublishSave(saveID) != RequestOkay)
|
||||
return false;
|
||||
return true;
|
||||
auto publishSaveRequest = std::make_unique<http::PublishSaveRequest>(saveID);
|
||||
publishSaveRequest->Start();
|
||||
publishSaveRequest->Wait();
|
||||
publishSaveRequest->Finish();
|
||||
}
|
||||
|
||||
bool UnpublishSave(int saveID)
|
||||
void UnpublishSave(int saveID)
|
||||
{
|
||||
notifyStatus(String::Build("Unpublishing save [", saveID, "]"));
|
||||
if (Client::Ref().UnpublishSave(saveID) != RequestOkay)
|
||||
return false;
|
||||
return true;
|
||||
auto unpublishSaveRequest = std::make_unique<http::UnpublishSaveRequest>(saveID);
|
||||
unpublishSaveRequest->Start();
|
||||
unpublishSaveRequest->Wait();
|
||||
unpublishSaveRequest->Finish();
|
||||
}
|
||||
|
||||
bool doWork() override
|
||||
{
|
||||
bool ret;
|
||||
for (size_t i = 0; i < saves.size(); i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (publish)
|
||||
ret = PublishSave(saves[i]);
|
||||
{
|
||||
PublishSave(saves[i]);
|
||||
}
|
||||
else
|
||||
ret = UnpublishSave(saves[i]);
|
||||
if (!ret)
|
||||
{
|
||||
UnpublishSave(saves[i]);
|
||||
}
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
if (publish) // uses html page so error message will be spam
|
||||
{
|
||||
notifyError(String::Build("Failed to publish [", saves[i], "], is this save yours?"));
|
||||
}
|
||||
else
|
||||
notifyError(String::Build("Failed to unpublish [", saves[i], "]: " + Client::Ref().GetLastError()));
|
||||
{
|
||||
notifyError(String::Build("Failed to unpublish [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
|
||||
}
|
||||
c->Refresh();
|
||||
return false;
|
||||
}
|
||||
@ -343,9 +368,16 @@ void SearchController::FavouriteSelected()
|
||||
for (size_t i = 0; i < saves.size(); i++)
|
||||
{
|
||||
notifyStatus(String::Build("Favouring save [", saves[i], "]"));
|
||||
if (Client::Ref().FavouriteSave(saves[i], true)!=RequestOkay)
|
||||
auto favouriteSaveRequest = std::make_unique<http::FavouriteSaveRequest>(saves[i], true);
|
||||
favouriteSaveRequest->Start();
|
||||
favouriteSaveRequest->Wait();
|
||||
try
|
||||
{
|
||||
notifyError(String::Build("Failed to favourite [", saves[i], "]: " + Client::Ref().GetLastError()));
|
||||
favouriteSaveRequest->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
notifyError(String::Build("Failed to favourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
|
||||
return false;
|
||||
}
|
||||
notifyProgress((i + 1) * 100 / saves.size());
|
||||
@ -364,9 +396,16 @@ void SearchController::FavouriteSelected()
|
||||
for (size_t i = 0; i < saves.size(); i++)
|
||||
{
|
||||
notifyStatus(String::Build("Unfavouring save [", saves[i], "]"));
|
||||
if (Client::Ref().FavouriteSave(saves[i], false)!=RequestOkay)
|
||||
auto unfavouriteSaveRequest = std::make_unique<http::FavouriteSaveRequest>(saves[i], false);
|
||||
unfavouriteSaveRequest->Start();
|
||||
unfavouriteSaveRequest->Wait();
|
||||
try
|
||||
{
|
||||
notifyError(String::Build("Failed to unfavourite [", saves[i], "]: " + Client::Ref().GetLastError()));
|
||||
unfavouriteSaveRequest->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
notifyError(String::Build("Failed to unfavourite [", saves[i], "]: ", ByteString(ex.what()).FromAscii()));
|
||||
return false;
|
||||
}
|
||||
notifyProgress((i + 1) * 100 / saves.size());
|
||||
|
@ -4,12 +4,14 @@
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/GameSave.h"
|
||||
#include "client/Client.h"
|
||||
#include "client/http/SearchSavesRequest.h"
|
||||
#include "client/http/SearchTagsRequest.h"
|
||||
#include "common/tpt-minmax.h"
|
||||
#include <thread>
|
||||
#include <cmath>
|
||||
|
||||
SearchModel::SearchModel():
|
||||
currentSort("best"),
|
||||
currentSort(http::sortByVotes),
|
||||
currentPage(1),
|
||||
resultCount(0),
|
||||
showOwn(false),
|
||||
@ -28,81 +30,26 @@ bool SearchModel::GetShowTags()
|
||||
return showTags;
|
||||
}
|
||||
|
||||
void SearchModel::BeginSearchSaves(int start, int count, String query, ByteString sort, ByteString category)
|
||||
void SearchModel::BeginSearchSaves(int start, int count, String query, http::Sort sort, http::Category category)
|
||||
{
|
||||
lastError = "";
|
||||
resultCount = 0;
|
||||
ByteStringBuilder urlStream;
|
||||
ByteString data;
|
||||
urlStream << SCHEME << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
|
||||
if(query.length() || sort.length())
|
||||
{
|
||||
urlStream << "&Search_Query=";
|
||||
if(query.length())
|
||||
urlStream << format::URLEncode(query.ToUtf8());
|
||||
if(sort == "date")
|
||||
{
|
||||
if(query.length())
|
||||
urlStream << format::URLEncode(" ");
|
||||
urlStream << format::URLEncode("sort:") << format::URLEncode(sort);
|
||||
}
|
||||
}
|
||||
if(category.length())
|
||||
{
|
||||
urlStream << "&Category=" << format::URLEncode(category);
|
||||
}
|
||||
searchSaves = std::make_unique<http::Request>(urlStream.Build());
|
||||
auto authUser = Client::Ref().GetAuthUser();
|
||||
if (authUser.UserID)
|
||||
{
|
||||
searchSaves->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
|
||||
}
|
||||
searchSaves = std::make_unique<http::SearchSavesRequest>(start, count, query.ToUtf8(), sort, category);
|
||||
searchSaves->Start();
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<SaveInfo>> SearchModel::EndSearchSaves()
|
||||
{
|
||||
std::vector<std::unique_ptr<SaveInfo>> saveArray;
|
||||
auto [ dataStatus, data ] = searchSaves->Finish();
|
||||
searchSaves.reset();
|
||||
auto &client = Client::Ref();
|
||||
client.ParseServerReturn(data, dataStatus, true);
|
||||
if (dataStatus == 200 && data.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
resultCount = objDocument["Count"].asInt();
|
||||
Json::Value savesArray = objDocument["Saves"];
|
||||
for (Json::UInt j = 0; j < savesArray.size(); j++)
|
||||
std::tie(resultCount, saveArray) = searchSaves->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
int tempID = savesArray[j]["ID"].asInt();
|
||||
int tempCreatedDate = savesArray[j]["Created"].asInt();
|
||||
int tempUpdatedDate = savesArray[j]["Updated"].asInt();
|
||||
int tempScoreUp = savesArray[j]["ScoreUp"].asInt();
|
||||
int tempScoreDown = savesArray[j]["ScoreDown"].asInt();
|
||||
ByteString tempUsername = savesArray[j]["Username"].asString();
|
||||
String tempName = ByteString(savesArray[j]["Name"].asString()).FromUtf8();
|
||||
int tempVersion = savesArray[j]["Version"].asInt();
|
||||
bool tempPublished = savesArray[j]["Published"].asBool();
|
||||
auto tempSaveInfo = std::make_unique<SaveInfo>(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp, tempScoreDown, tempUsername, tempName);
|
||||
tempSaveInfo->Version = tempVersion;
|
||||
tempSaveInfo->SetPublished(tempPublished);
|
||||
saveArray.push_back(std::move(tempSaveInfo));
|
||||
}
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = client.GetLastError();
|
||||
lastError = ByteString(ex.what()).FromUtf8();
|
||||
}
|
||||
searchSaves.reset();
|
||||
return saveArray;
|
||||
}
|
||||
|
||||
@ -117,40 +64,22 @@ void SearchModel::BeginGetTags(int start, int count, String query)
|
||||
if(query.length())
|
||||
urlStream << format::URLEncode(query.ToUtf8());
|
||||
}
|
||||
getTags = std::make_unique<http::Request>(urlStream.Build());
|
||||
getTags = std::make_unique<http::SearchTagsRequest>(start, count, query.ToUtf8());
|
||||
getTags->Start();
|
||||
}
|
||||
|
||||
std::vector<std::pair<ByteString, int>> SearchModel::EndGetTags()
|
||||
{
|
||||
std::vector<std::pair<ByteString, int>> tagArray;
|
||||
auto [ dataStatus, data ] = getTags->Finish();
|
||||
getTags.reset();
|
||||
if(dataStatus == 200 && data.size())
|
||||
{
|
||||
try
|
||||
{
|
||||
std::istringstream dataStream(data);
|
||||
Json::Value objDocument;
|
||||
dataStream >> objDocument;
|
||||
|
||||
Json::Value tagsArray = objDocument["Tags"];
|
||||
for (Json::UInt j = 0; j < tagsArray.size(); j++)
|
||||
tagArray = getTags->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
int tagCount = tagsArray[j]["Count"].asInt();
|
||||
ByteString tag = tagsArray[j]["Tag"].asString();
|
||||
tagArray.push_back(std::pair<ByteString, int>(tag, tagCount));
|
||||
}
|
||||
}
|
||||
catch (std::exception & e)
|
||||
{
|
||||
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError = http::StatusText(dataStatus);
|
||||
lastError = ByteString(ex.what()).FromUtf8();
|
||||
}
|
||||
getTags.reset();
|
||||
return tagArray;
|
||||
}
|
||||
|
||||
@ -166,7 +95,7 @@ bool SearchModel::UpdateSaveList(int pageNumber, String query)
|
||||
//resultCount = 0;
|
||||
currentPage = pageNumber;
|
||||
|
||||
if(pageNumber == 1 && !showOwn && !showFavourite && currentSort == "best" && query == "")
|
||||
if(pageNumber == 1 && !showOwn && !showFavourite && currentSort == http::sortByVotes && query == "")
|
||||
SetShowTags(true);
|
||||
else
|
||||
SetShowTags(false);
|
||||
@ -182,12 +111,16 @@ bool SearchModel::UpdateSaveList(int pageNumber, String query)
|
||||
BeginGetTags(0, 24, "");
|
||||
}
|
||||
|
||||
ByteString category = "";
|
||||
if(showFavourite)
|
||||
category = "Favourites";
|
||||
if(showOwn && Client::Ref().GetAuthUser().UserID)
|
||||
category = "by:"+Client::Ref().GetAuthUser().Username;
|
||||
BeginSearchSaves((currentPage-1)*20, 20, lastQuery, currentSort=="new"?"date":"votes", category);
|
||||
auto category = http::categoryNone;
|
||||
if (showFavourite)
|
||||
{
|
||||
category = http::categoryFavourites;
|
||||
}
|
||||
if (showOwn && Client::Ref().GetAuthUser().UserID)
|
||||
{
|
||||
category = http::categoryMyOwn;
|
||||
}
|
||||
BeginSearchSaves((currentPage-1)*20, 20, lastQuery, currentSort, category);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -363,7 +296,7 @@ void SearchModel::notifySelectedChanged()
|
||||
|
||||
int SearchModel::GetPageCount()
|
||||
{
|
||||
if (!showOwn && !showFavourite && currentSort == "best" && lastQuery == "")
|
||||
if (!showOwn && !showFavourite && currentSort == http::sortByVotes && lastQuery == "")
|
||||
return std::max(1, (int)(ceil(resultCount/20.0f))+1); //add one for front page (front page saves are repeated twice)
|
||||
else
|
||||
return std::max(1, (int)(ceil(resultCount/20.0f)));
|
||||
|
@ -1,26 +1,32 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include "client/http/Request.h"
|
||||
#include "client/Search.h"
|
||||
#include "Config.h"
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class SearchSavesRequest;
|
||||
class SearchTagsRequest;
|
||||
}
|
||||
|
||||
class SaveInfo;
|
||||
class SearchView;
|
||||
class SearchModel
|
||||
{
|
||||
private:
|
||||
std::unique_ptr<http::Request> searchSaves;
|
||||
void BeginSearchSaves(int start, int count, String query, ByteString sort, ByteString category);
|
||||
std::unique_ptr<http::SearchSavesRequest> searchSaves;
|
||||
void BeginSearchSaves(int start, int count, String query, http::Sort sort, http::Category category);
|
||||
std::vector<std::unique_ptr<SaveInfo>> EndSearchSaves();
|
||||
|
||||
void BeginGetTags(int start, int count, String query);
|
||||
std::vector<std::pair<ByteString, int>> EndGetTags();
|
||||
std::unique_ptr<http::Request> getTags;
|
||||
std::unique_ptr<http::SearchTagsRequest> getTags;
|
||||
|
||||
std::unique_ptr<SaveInfo> loadedSave;
|
||||
ByteString currentSort;
|
||||
http::Sort currentSort;
|
||||
String lastQuery;
|
||||
String lastError;
|
||||
std::vector<int> selected;
|
||||
@ -55,8 +61,8 @@ public:
|
||||
int GetPageCount();
|
||||
int GetPageNum() { return currentPage; }
|
||||
String GetLastQuery() { return lastQuery; }
|
||||
void SetSort(ByteString sort) { if(!searchSaves) { currentSort = sort; } notifySortChanged(); }
|
||||
ByteString GetSort() { return currentSort; }
|
||||
void SetSort(http::Sort sort) { if(!searchSaves) { currentSort = sort; } notifySortChanged(); }
|
||||
http::Sort GetSort() { return currentSort; }
|
||||
void SetShowOwn(bool show) { if(!searchSaves) { if(show!=showOwn) { showOwn = show; } } notifyShowOwnChanged(); }
|
||||
bool GetShowOwn() { return showOwn; }
|
||||
void SetShowFavourite(bool show) { if(show!=showFavourite && !searchSaves) { showFavourite = show; } notifyShowFavouriteChanged(); }
|
||||
|
@ -211,7 +211,7 @@ void SearchView::Search(String query)
|
||||
|
||||
void SearchView::NotifySortChanged(SearchModel * sender)
|
||||
{
|
||||
if(sender->GetSort() == "best")
|
||||
if(sender->GetSort() == http::sortByVotes)
|
||||
{
|
||||
sortButton->SetToggleState(false);
|
||||
sortButton->SetText("By votes");
|
||||
@ -228,7 +228,7 @@ void SearchView::NotifySortChanged(SearchModel * sender)
|
||||
void SearchView::NotifyShowOwnChanged(SearchModel * sender)
|
||||
{
|
||||
ownButton->SetToggleState(sender->GetShowOwn());
|
||||
if(sender->GetShowOwn() || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator)
|
||||
if(sender->GetShowOwn() || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationMod)
|
||||
{
|
||||
unpublishSelected->Enabled = true;
|
||||
removeSelected->Enabled = true;
|
||||
@ -248,7 +248,7 @@ void SearchView::NotifyShowFavouriteChanged(SearchModel * sender)
|
||||
unpublishSelected->Enabled = false;
|
||||
removeSelected->Enabled = false;
|
||||
}
|
||||
else if(sender->GetShowOwn() || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator)
|
||||
else if(sender->GetShowOwn() || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationMod)
|
||||
{
|
||||
unpublishSelected->Enabled = true;
|
||||
removeSelected->Enabled = true;
|
||||
@ -323,7 +323,7 @@ void SearchView::CheckAccess()
|
||||
favButton->Enabled = true;
|
||||
favouriteSelected->Enabled = true;
|
||||
|
||||
if (Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator)
|
||||
if (Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationMod)
|
||||
{
|
||||
unpublishSelected->Enabled = true;
|
||||
removeSelected->Enabled = true;
|
||||
@ -569,7 +569,7 @@ void SearchView::NotifySaveListChanged(SearchModel * sender)
|
||||
});
|
||||
if(Client::Ref().GetAuthUser().UserID)
|
||||
saveButton->SetSelectable(true);
|
||||
if (saves[i]->GetUserName() == Client::Ref().GetAuthUser().Username || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator)
|
||||
if (saves[i]->GetUserName() == Client::Ref().GetAuthUser().Username || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationMod)
|
||||
saveButton->SetShowVotes(true);
|
||||
saveButtons.push_back(saveButton);
|
||||
AddComponent(saveButton);
|
||||
|
@ -1,8 +1,8 @@
|
||||
#include "TagsController.h"
|
||||
|
||||
#include "TagsModel.h"
|
||||
#include "TagsView.h"
|
||||
|
||||
#include "client/http/AddTagRequest.h"
|
||||
#include "client/http/RemoveTagRequest.h"
|
||||
#include "gui/interface/Engine.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "Controller.h"
|
||||
@ -36,6 +36,11 @@ void TagsController::AddTag(ByteString tag)
|
||||
tagsModel->AddTag(tag);
|
||||
}
|
||||
|
||||
void TagsController::Tick()
|
||||
{
|
||||
tagsModel->Tick();
|
||||
}
|
||||
|
||||
void TagsController::Exit()
|
||||
{
|
||||
tagsView->CloseActiveWindow();
|
||||
|
@ -18,5 +18,6 @@ public:
|
||||
void RemoveTag(ByteString tag);
|
||||
void AddTag(ByteString tag);
|
||||
void Exit();
|
||||
void Tick();
|
||||
virtual ~TagsController();
|
||||
};
|
||||
|
@ -1,13 +1,15 @@
|
||||
#include "TagsModel.h"
|
||||
|
||||
#include "TagsView.h"
|
||||
#include "TagsModelException.h"
|
||||
|
||||
#include "client/Client.h"
|
||||
#include "client/SaveInfo.h"
|
||||
#include "client/http/AddTagRequest.h"
|
||||
#include "client/http/RemoveTagRequest.h"
|
||||
#include "gui/dialogues/ErrorMessage.h"
|
||||
|
||||
void TagsModel::SetSave(SaveInfo *newSave /* non-owning */)
|
||||
{
|
||||
queuedTags.clear();
|
||||
this->save = newSave;
|
||||
notifyTagsChanged();
|
||||
}
|
||||
@ -17,40 +19,73 @@ SaveInfo *TagsModel::GetSave() // non-owning
|
||||
return save;
|
||||
}
|
||||
|
||||
void TagsModel::RemoveTag(ByteString tag)
|
||||
void TagsModel::Tick()
|
||||
{
|
||||
if(save)
|
||||
auto triggerTags = false;
|
||||
std::list<ByteString> tags;
|
||||
if (addTagRequest && addTagRequest->CheckDone())
|
||||
{
|
||||
std::list<ByteString> * tags = Client::Ref().RemoveTag(save->GetID(), tag);
|
||||
if(tags)
|
||||
try
|
||||
{
|
||||
save->SetTags(std::list<ByteString>(*tags));
|
||||
tags = addTagRequest->Finish();
|
||||
triggerTags = true;
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
new ErrorMessage("Could not add tag", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
addTagRequest.reset();
|
||||
}
|
||||
if (removeTagRequest && removeTagRequest->CheckDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
tags = removeTagRequest->Finish();
|
||||
triggerTags = true;
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
new ErrorMessage("Could not remove tag", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
removeTagRequest.reset();
|
||||
}
|
||||
if (triggerTags)
|
||||
{
|
||||
if (save)
|
||||
{
|
||||
save->SetTags(tags);
|
||||
}
|
||||
notifyTagsChanged();
|
||||
delete tags;
|
||||
}
|
||||
if (!addTagRequest && !removeTagRequest && !queuedTags.empty())
|
||||
{
|
||||
auto it = queuedTags.begin();
|
||||
auto [ tag, add ] = *it;
|
||||
queuedTags.erase(it);
|
||||
if (save)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
addTagRequest = std::make_unique<http::AddTagRequest>(save->GetID(), tag);
|
||||
addTagRequest->Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw TagsModelException(Client::Ref().GetLastError());
|
||||
removeTagRequest = std::make_unique<http::RemoveTagRequest>(save->GetID(), tag);
|
||||
removeTagRequest->Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TagsModel::RemoveTag(ByteString tag)
|
||||
{
|
||||
queuedTags[tag] = false;
|
||||
}
|
||||
|
||||
void TagsModel::AddTag(ByteString tag)
|
||||
{
|
||||
if(save)
|
||||
{
|
||||
std::list<ByteString> * tags = Client::Ref().AddTag(save->GetID(), tag);
|
||||
if(tags)
|
||||
{
|
||||
save->SetTags(std::list<ByteString>(*tags));
|
||||
notifyTagsChanged();
|
||||
delete tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw TagsModelException(Client::Ref().GetLastError());
|
||||
}
|
||||
}
|
||||
queuedTags[tag] = true;
|
||||
}
|
||||
|
||||
void TagsModel::AddObserver(TagsView * observer)
|
||||
|
@ -1,11 +1,22 @@
|
||||
#pragma once
|
||||
#include "common/String.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace http
|
||||
{
|
||||
class AddTagRequest;
|
||||
class RemoveTagRequest;
|
||||
}
|
||||
|
||||
class SaveInfo;
|
||||
|
||||
class TagsView;
|
||||
class TagsModel {
|
||||
std::unique_ptr<http::AddTagRequest> addTagRequest;
|
||||
std::unique_ptr<http::RemoveTagRequest> removeTagRequest;
|
||||
std::map<ByteString, bool> queuedTags;
|
||||
SaveInfo *save = nullptr; // non-owning
|
||||
std::vector<TagsView*> observers;
|
||||
void notifyTagsChanged();
|
||||
@ -15,4 +26,5 @@ public:
|
||||
void RemoveTag(ByteString tag);
|
||||
void AddTag(ByteString tag);
|
||||
SaveInfo *GetSave(); // non-owning
|
||||
void Tick();
|
||||
};
|
||||
|
@ -49,6 +49,11 @@ TagsView::TagsView():
|
||||
AddComponent(title);
|
||||
}
|
||||
|
||||
void TagsView::OnTick(float dt)
|
||||
{
|
||||
c->Tick();
|
||||
}
|
||||
|
||||
void TagsView::OnDraw()
|
||||
{
|
||||
Graphics * g = GetGraphics();
|
||||
@ -76,7 +81,7 @@ void TagsView::NotifyTagsChanged(TagsModel * sender)
|
||||
tags.push_back(tempLabel);
|
||||
AddComponent(tempLabel);
|
||||
|
||||
if(sender->GetSave()->GetUserName() == Client::Ref().GetAuthUser().Username || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationModerator)
|
||||
if(sender->GetSave()->GetUserName() == Client::Ref().GetAuthUser().Username || Client::Ref().GetAuthUser().UserElevation == User::ElevationAdmin || Client::Ref().GetAuthUser().UserElevation == User::ElevationMod)
|
||||
{
|
||||
ui::Button * tempButton = new ui::Button(ui::Point(15, 37+(16*i)), ui::Point(11, 12));
|
||||
tempButton->Appearance.icon = IconDelete;
|
||||
@ -85,14 +90,7 @@ void TagsView::NotifyTagsChanged(TagsModel * sender)
|
||||
tempButton->Appearance.HorizontalAlign = ui::Appearance::AlignCentre;
|
||||
tempButton->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
|
||||
tempButton->SetActionCallback({ [this, tag] {
|
||||
try
|
||||
{
|
||||
c->RemoveTag(tag);
|
||||
}
|
||||
catch(TagsModelException & ex)
|
||||
{
|
||||
new ErrorMessage("Could not remove tag", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
} });
|
||||
tags.push_back(tempButton);
|
||||
AddComponent(tempButton);
|
||||
@ -125,13 +123,6 @@ void TagsView::addTag()
|
||||
new ErrorMessage("Tag not long enough", "Must be at least 4 letters");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
c->AddTag(tagInput->GetText().ToUtf8());
|
||||
}
|
||||
catch(TagsModelException & ex)
|
||||
{
|
||||
new ErrorMessage("Could not add tag", ByteString(ex.what()).FromUtf8());
|
||||
}
|
||||
tagInput->SetText("");
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class TagsView: public ui::Window {
|
||||
public:
|
||||
TagsView();
|
||||
void OnDraw() override;
|
||||
void OnTick(float dt) override;
|
||||
void AttachController(TagsController * c_) { c = c_; }
|
||||
void OnKeyPress(int key, int scan, bool repeat, bool shift, bool ctrl, bool alt) override;
|
||||
void NotifyTagsChanged(TagsModel * sender);
|
||||
|
@ -1,7 +1,6 @@
|
||||
#include "UpdateActivity.h"
|
||||
#include "client/http/Request.h"
|
||||
#include "prefs/GlobalPrefs.h"
|
||||
#include "client/Client.h"
|
||||
#include "common/platform/Platform.h"
|
||||
#include "tasks/Task.h"
|
||||
#include "tasks/TaskWindow.h"
|
||||
@ -53,7 +52,16 @@ private:
|
||||
Platform::Millisleep(1);
|
||||
}
|
||||
|
||||
auto [ status, data ] = request->Finish();
|
||||
int status;
|
||||
ByteString data;
|
||||
try
|
||||
{
|
||||
std::tie(status, data) = request->Finish();
|
||||
}
|
||||
catch (const http::RequestError &ex)
|
||||
{
|
||||
return niceNotifyError("Could not download update: " + String::Build("Server responded with Status ", ByteString(ex.what()).FromAscii()));
|
||||
}
|
||||
if (status!=200)
|
||||
{
|
||||
return niceNotifyError("Could not download update: " + String::Build("Server responded with Status ", status));
|
||||
@ -107,9 +115,9 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
UpdateActivity::UpdateActivity() {
|
||||
ByteString file = ByteString::Build(SCHEME, USE_UPDATESERVER ? UPDATESERVER : SERVER, Client::Ref().GetUpdateInfo().File);
|
||||
updateDownloadTask = new UpdateDownloadTask(file, this);
|
||||
UpdateActivity::UpdateActivity(UpdateInfo info)
|
||||
{
|
||||
updateDownloadTask = new UpdateDownloadTask(info.file, this);
|
||||
updateWindow = new TaskWindow("Downloading update...", updateDownloadTask, true);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "client/StartupInfo.h"
|
||||
|
||||
class Task;
|
||||
class TaskWindow;
|
||||
@ -7,7 +8,7 @@ class UpdateActivity
|
||||
Task * updateDownloadTask;
|
||||
TaskWindow * updateWindow;
|
||||
public:
|
||||
UpdateActivity();
|
||||
UpdateActivity(UpdateInfo info);
|
||||
virtual ~UpdateActivity();
|
||||
void Exit();
|
||||
virtual void NotifyDone(Task * sender);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user