Manage search model requests in SearchModel

Also make Client an ExplicitSingleton, and thus fix all known instances of Requests outliving RequestManager.
This commit is contained in:
Tamás Bálint Misius 2023-01-18 16:14:46 +01:00
parent afda2826bf
commit 746dbb0cba
No known key found for this signature in database
GPG Key ID: 5B472A12F6ECA9F2
7 changed files with 158 additions and 242 deletions

View File

@ -976,120 +976,6 @@ SaveFile * Client::LoadSaveFile(ByteString filename)
return file;
}
std::vector<std::pair<ByteString, int> > * Client::GetTags(int start, int count, String query, int & resultCount)
{
lastError = "";
resultCount = 0;
std::vector<std::pair<ByteString, int> > * tagArray = new std::vector<std::pair<ByteString, int> >();
ByteStringBuilder urlStream;
urlStream << SCHEME << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
if(query.length())
{
urlStream << "&Search_Query=";
if(query.length())
urlStream << format::URLEncode(query.ToUtf8());
}
auto [ dataStatus, data ] = http::Request::Simple(urlStream.Build());
if(dataStatus == 200 && data.size())
{
try
{
std::istringstream dataStream(data);
Json::Value objDocument;
dataStream >> objDocument;
resultCount = objDocument["TagTotal"].asInt();
Json::Value tagsArray = objDocument["Tags"];
for (Json::UInt j = 0; j < tagsArray.size(); j++)
{
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);
}
return tagArray;
}
std::vector<SaveInfo*> * Client::SearchSaves(int start, int count, String query, ByteString sort, ByteString category, int & resultCount)
{
lastError = "";
resultCount = 0;
std::vector<SaveInfo*> * saveArray = new std::vector<SaveInfo*>();
ByteStringBuilder urlStream;
ByteString data;
int dataStatus;
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);
}
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());
}
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++)
{
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();
SaveInfo * tempSaveInfo = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp, tempScoreDown, tempUsername, tempName);
tempSaveInfo->Version = tempVersion;
tempSaveInfo->SetPublished(tempPublished);
saveArray->push_back(tempSaveInfo);
}
}
catch (std::exception &e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
}
}
return saveArray;
}
std::list<ByteString> * Client::RemoveTag(int saveID, ByteString tag)
{
lastError = "";

View File

@ -129,8 +129,6 @@ public:
std::vector<char> GetSaveData(int saveID, int saveDate);
LoginStatus Login(ByteString username, ByteString password, User & user);
std::vector<SaveInfo*> * SearchSaves(int start, int count, String query, ByteString sort, ByteString category, int & resultCount);
std::vector<std::pair<ByteString, int> > * GetTags(int start, int count, String query, int & resultCount);
SaveInfo * GetSave(int saveID, int saveDate);
SaveFile * LoadSaveFile(ByteString filename);

View File

@ -14,15 +14,7 @@ namespace http
{
if (handle->state != RequestHandle::ready)
{
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: Client outlives RequestManager, RequestManager doesn't necessarily exist at this point.
// Solution: Check if it does >_> ExplicitSingleton::Exists exists for no other reason than this.
if (RequestManager::Exists())
{
RequestManager::Ref().UnregisterRequest(*this);
}
RequestManager::Ref().UnregisterRequest(*this);
}
}
@ -67,15 +59,7 @@ namespace http
{
assert(handle->state == RequestHandle::ready);
handle->state = RequestHandle::running;
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: Client outlives RequestManager, RequestManager doesn't necessarily exist at this point.
// Solution: Check if it does >_> ExplicitSingleton::Exists exists for no other reason than this.
if (RequestManager::Exists())
{
RequestManager::Ref().RegisterRequest(*this);
}
RequestManager::Ref().RegisterRequest(*this);
}
bool Request::CheckDone() const
@ -201,7 +185,6 @@ namespace http
case 607: return "Connection Refused";
case 608: return "Proxy Server Not Found";
case 609: return "SSL: Invalid Certificate Status";
case 610: return "Cancelled by Shutdown";
case 611: return "Too Many Redirects";
case 612: return "SSL: Connect Error";
case 613: return "SSL: Crypto Engine Not Found";

View File

@ -39,18 +39,6 @@ namespace http
{
{
std::lock_guard lk(sharedStateMx);
if (!running)
{
// TODO: Fix bad design.
// Bad design: Client should not outlive RequestManager because it has its own requests, but
// RequestManager needs Client because Client is also responsible for configuration >_>
// Problem: RequestManager's worker needs all requests to have been unregistered when it exits.
// Solution: Knock out all live requests here on shutdown.
for (auto &requestHandle : requestHandles)
{
requestHandle->statusCode = 610;
}
}
for (auto &requestHandle : requestHandles)
{
if (requestHandle->statusCode)

View File

@ -28,9 +28,4 @@ public:
{
return *Instance();
}
static bool Exists()
{
return Instance();
}
};

View File

@ -2,6 +2,7 @@
#include "SearchView.h"
#include "Format.h"
#include "client/SaveInfo.h"
#include "client/Client.h"
@ -17,14 +18,7 @@ SearchModel::SearchModel():
resultCount(0),
showOwn(false),
showFavourite(false),
showTags(true),
saveListLoaded(false),
updateSaveListWorking(false),
updateSaveListFinished(false),
updateSaveListResult(nullptr),
updateTagListWorking(false),
updateTagListFinished(false),
updateTagListResult(nullptr)
showTags(true)
{
}
@ -38,32 +32,136 @@ bool SearchModel::GetShowTags()
return showTags;
}
void SearchModel::updateSaveListT()
void SearchModel::BeginSearchSaves(int start, int count, String query, ByteString sort, ByteString category)
{
ByteString category = "";
if(showFavourite)
category = "Favourites";
if(showOwn && Client::Ref().GetAuthUser().UserID)
category = "by:"+Client::Ref().GetAuthUser().Username;
std::vector<SaveInfo*> * saveList = Client::Ref().SearchSaves((currentPage-1)*20, 20, lastQuery, currentSort=="new"?"date":"votes", category, thResultCount);
updateSaveListResult = saveList;
updateSaveListFinished = true;
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->Start();
}
void SearchModel::updateTagListT()
std::vector<SaveInfo *> SearchModel::EndSearchSaves()
{
int tagResultCount;
std::vector<std::pair<ByteString, int> > * tagList = Client::Ref().GetTags(0, 24, "", tagResultCount);
std::vector<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;
updateTagListResult = tagList;
updateTagListFinished = true;
resultCount = objDocument["Count"].asInt();
Json::Value savesArray = objDocument["Saves"];
for (Json::UInt j = 0; j < savesArray.size(); j++)
{
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();
SaveInfo * tempSaveInfo = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp, tempScoreDown, tempUsername, tempName);
tempSaveInfo->Version = tempVersion;
tempSaveInfo->SetPublished(tempPublished);
saveArray.push_back(tempSaveInfo);
}
}
catch (std::exception &e)
{
lastError = "Could not read response: " + ByteString(e.what()).FromUtf8();
}
}
else
{
lastError = client.GetLastError();
}
return saveArray;
}
void SearchModel::BeginGetTags(int start, int count, String query)
{
lastError = "";
ByteStringBuilder urlStream;
urlStream << SCHEME << SERVER << "/Browse/Tags.json?Start=" << start << "&Count=" << count;
if(query.length())
{
urlStream << "&Search_Query=";
if(query.length())
urlStream << format::URLEncode(query.ToUtf8());
}
getTags = std::make_unique<http::Request>(urlStream.Build());
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++)
{
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);
}
return tagArray;
}
bool SearchModel::UpdateSaveList(int pageNumber, String query)
{
//Threading
if (!updateSaveListWorking)
if (!searchSaves)
{
lastQuery = query;
lastError = "";
@ -83,16 +181,17 @@ bool SearchModel::UpdateSaveList(int pageNumber, String query)
selected.clear();
notifySelectedChanged();
if(GetShowTags() && !tagList.size() && !updateTagListWorking)
if (GetShowTags() && !tagList.size() && !getTags)
{
updateTagListFinished = false;
updateTagListWorking = true;
std::thread([this]() { updateTagListT(); }).detach();
BeginGetTags(0, 24, "");
}
updateSaveListFinished = false;
updateSaveListWorking = true;
std::thread([this]() { updateSaveListT(); }).detach();
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);
return true;
}
return false;
@ -128,51 +227,19 @@ std::vector<std::pair<ByteString, int> > SearchModel::GetTagList()
void SearchModel::Update()
{
if(updateSaveListWorking)
if (searchSaves && searchSaves->CheckDone())
{
if(updateSaveListFinished)
{
updateSaveListWorking = false;
lastError = "";
saveListLoaded = true;
std::vector<SaveInfo *> *tempSaveList = updateSaveListResult;
updateSaveListResult = nullptr;
if(tempSaveList)
{
saveList = *tempSaveList;
delete tempSaveList;
}
if(!saveList.size())
{
lastError = Client::Ref().GetLastError();
if (lastError == "Unspecified Error")
lastError = "";
}
resultCount = thResultCount;
notifyPageChanged();
notifySaveListChanged();
}
saveListLoaded = true;
lastError = "";
saveList = EndSearchSaves();
notifyPageChanged();
notifySaveListChanged();
}
if(updateTagListWorking)
if (getTags && getTags->CheckDone())
{
if(updateTagListFinished)
{
updateTagListWorking = false;
std::vector<std::pair<ByteString, int>> *tempTagList = updateTagListResult;
updateTagListResult = nullptr;
if(tempTagList)
{
tagList = *tempTagList;
delete tempTagList;
}
notifyTagListChanged();
}
lastError = "";
tagList = EndGetTags();
notifyTagListChanged();
}
}

View File

@ -1,15 +1,24 @@
#pragma once
#include "Config.h"
#include <vector>
#include "common/String.h"
#include "client/http/Request.h"
#include <vector>
#include <atomic>
#include <memory>
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::vector<SaveInfo *> EndSearchSaves();
void BeginGetTags(int start, int count, String query);
std::vector<std::pair<ByteString, int>> EndGetTags();
std::unique_ptr<http::Request> getTags;
SaveInfo * loadedSave;
ByteString currentSort;
String lastQuery;
@ -20,7 +29,6 @@ private:
std::vector<std::pair<ByteString, int> > tagList;
int currentPage;
int resultCount;
int thResultCount;
bool showOwn;
bool showFavourite;
bool showTags;
@ -33,19 +41,10 @@ private:
void notifyShowFavouriteChanged();
//Variables and methods for background save request
bool saveListLoaded;
bool updateSaveListWorking;
std::atomic<bool> updateSaveListFinished;
void updateSaveListT();
std::vector<SaveInfo *> *updateSaveListResult;
bool updateTagListWorking;
std::atomic<bool> updateTagListFinished;
void updateTagListT();
std::vector<std::pair<ByteString, int>> *updateTagListResult;
bool saveListLoaded = false;
public:
SearchModel();
virtual ~SearchModel();
~SearchModel();
void SetShowTags(bool show);
bool GetShowTags();
@ -57,11 +56,11 @@ public:
int GetPageCount();
int GetPageNum() { return currentPage; }
String GetLastQuery() { return lastQuery; }
void SetSort(ByteString sort) { if(!updateSaveListWorking) { currentSort = sort; } notifySortChanged(); }
void SetSort(ByteString sort) { if(!searchSaves) { currentSort = sort; } notifySortChanged(); }
ByteString GetSort() { return currentSort; }
void SetShowOwn(bool show) { if(!updateSaveListWorking) { if(show!=showOwn) { showOwn = show; } } notifyShowOwnChanged(); }
void SetShowOwn(bool show) { if(!searchSaves) { if(show!=showOwn) { showOwn = show; } } notifyShowOwnChanged(); }
bool GetShowOwn() { return showOwn; }
void SetShowFavourite(bool show) { if(show!=showFavourite && !updateSaveListWorking) { showFavourite = show; } notifyShowFavouriteChanged(); }
void SetShowFavourite(bool show) { if(show!=showFavourite && !searchSaves) { showFavourite = show; } notifyShowFavouriteChanged(); }
bool GetShowFavourite() { return showFavourite; }
void SetLoadedSave(SaveInfo * save);
SaveInfo * GetLoadedSave();