diff --git a/src/client/Client.h b/src/client/Client.h index 26ff4503df..6e92b563dd 100644 --- a/src/client/Client.h +++ b/src/client/Client.h @@ -6,7 +6,7 @@ #include #include "Config.h" -#include "Singleton.h" +#include "common/Singleton.h" #include "User.h" #include "UserInfo.h" diff --git a/src/client/Download.cpp b/src/client/Download.cpp new file mode 100644 index 0000000000..ac5a45d17c --- /dev/null +++ b/src/client/Download.cpp @@ -0,0 +1,151 @@ +#include +#include "Download.h" +#include "DownloadManager.h" +#include "http.h" + +Download::Download(std::string uri_, bool keepAlive): + http(NULL), + keepAlive(keepAlive), + downloadData(NULL), + downloadSize(0), + downloadStatus(0), + downloadFinished(false), + downloadCanceled(false), + downloadStarted(false), + postData(""), + postDataBoundary(""), + userID(""), + userSession("") +{ + uri = std::string(uri_); + DownloadManager::Ref().AddDownload(this); +} + +// called by download thread itself if download was canceled +Download::~Download() +{ + if (http && (keepAlive || downloadCanceled)) + http_async_req_close(http); + if (downloadData) + free(downloadData); +} + +// add post data to a request +void Download::AddPostData(std::map data) +{ + postDataBoundary = FindBoundary(data, ""); + postData = GetMultipartMessage(data, postDataBoundary); +} +void Download::AddPostData(std::pair data) +{ + std::map postData; + postData.insert(data); + AddPostData(postData); +} + +// add userID and sessionID headers to the download. Must be done after download starts for some reason +void Download::AuthHeaders(std::string ID, std::string session) +{ + if (ID != "0") + userID = ID; + userSession = session; +} + +// start the download thread +void Download::Start() +{ + if (CheckStarted() || CheckDone()) + return; + http = http_async_req_start(http, uri.c_str(), postData.c_str(), postData.length(), keepAlive ? 1 : 0); + // add the necessary headers + if (userID.length() || userSession.length()) + http_auth_headers(http, userID.c_str(), NULL, userSession.c_str()); + if (postDataBoundary.length()) + http_add_multipart_header(http, postDataBoundary); + DownloadManager::Ref().Lock(); + downloadStarted = true; + DownloadManager::Ref().Unlock(); +} + +// for persistent connections (keepAlive = true), reuse the open connection to make another request +bool Download::Reuse(std::string newuri) +{ + if (!keepAlive || !CheckDone() || CheckCanceled()) + { + return false; + } + uri = std::string(newuri); + DownloadManager::Ref().Lock(); + downloadFinished = false; + DownloadManager::Ref().Unlock(); + Start(); + DownloadManager::Ref().EnsureRunning(); + return true; +} + +// finish the download (if called before the download is done, this will block) +char* Download::Finish(int *length, int *status) +{ + if (CheckCanceled()) + return NULL; // shouldn't happen but just in case + while (!CheckDone()); // block + DownloadManager::Ref().Lock(); + downloadStarted = false; + if (length) + *length = downloadSize; + if (status) + *status = downloadStatus; + char *ret = downloadData; + downloadData = NULL; + if (!keepAlive) + downloadCanceled = true; + DownloadManager::Ref().Unlock(); + return ret; +} + +// returns the download size and progress (if the download has the correct length headers) +void Download::CheckProgress(int *total, int *done) +{ + DownloadManager::Ref().Lock(); + if (!downloadFinished && http) + http_async_get_length(http, total, done); + else + *total = *done = 0; + DownloadManager::Ref().Unlock(); +} + +// returns true if the download has finished +bool Download::CheckDone() +{ + DownloadManager::Ref().Lock(); + bool ret = downloadFinished; + DownloadManager::Ref().Unlock(); + return ret; +} + +// returns true if the download was canceled +bool Download::CheckCanceled() +{ + DownloadManager::Ref().Lock(); + bool ret = downloadCanceled; + DownloadManager::Ref().Unlock(); + return ret; +} + +// returns true if the download is running +bool Download::CheckStarted() +{ + DownloadManager::Ref().Lock(); + bool ret = downloadStarted; + DownloadManager::Ref().Unlock(); + return ret; + +} + +// cancels the download, the download thread will delete the Download* when it finishes (do not use Download in any way after canceling) +void Download::Cancel() +{ + DownloadManager::Ref().Lock(); + downloadCanceled = true; + DownloadManager::Ref().Unlock(); +} diff --git a/src/client/Download.h b/src/client/Download.h new file mode 100644 index 0000000000..9db636a7e4 --- /dev/null +++ b/src/client/Download.h @@ -0,0 +1,47 @@ +#ifndef DOWNLOAD_H +#define DOWNLOAD_H +#include +#include + +class DownloadManager; +class Download +{ + std::string uri; + void *http; + bool keepAlive; + + char *downloadData; + int downloadSize; + int downloadStatus; + + std::string postData; + std::string postDataBoundary; + + std::string userID; + std::string userSession; + + volatile bool downloadFinished; + volatile bool downloadCanceled; + volatile bool downloadStarted; + +public: + Download(std::string uri, bool keepAlive = false); + ~Download(); + + void AddPostData(std::map data); + void AddPostData(std::pair data); + void AuthHeaders(std::string ID, std::string session); + void Start(); + bool Reuse(std::string newuri); + char* Finish(int *length, int *status); + void Cancel(); + + void CheckProgress(int *total, int *done); + bool CheckDone(); + bool CheckCanceled(); + bool CheckStarted(); + + friend class DownloadManager; +}; + +#endif diff --git a/src/client/DownloadManager.cpp b/src/client/DownloadManager.cpp new file mode 100644 index 0000000000..7be451f6a9 --- /dev/null +++ b/src/client/DownloadManager.cpp @@ -0,0 +1,145 @@ +#include "DownloadManager.h" +#include "Download.h" +#include "http.h" +#include "Config.h" +#include "Platform.h" + +DownloadManager::DownloadManager(): + threadStarted(false), + lastUsed(time(NULL)), + managerRunning(false), + managerShutdown(false), + downloads(NULL), + downloadsAddQueue(NULL) +{ + pthread_mutex_init(&downloadLock, NULL); + pthread_mutex_init(&downloadAddLock, NULL); +} + +DownloadManager::~DownloadManager() +{ + +} + +void DownloadManager::Shutdown() +{ + pthread_mutex_lock(&downloadLock); + pthread_mutex_lock(&downloadAddLock); + for (std::vector::iterator iter = downloads.begin(); iter != downloads.end(); ++iter) + { + Download *download = (*iter); + if (download->http) + http_force_close(download->http); + download->downloadCanceled = true; + delete download; + } + downloads.clear(); + downloadsAddQueue.clear(); + managerShutdown = true; + pthread_mutex_unlock(&downloadAddLock); + pthread_mutex_unlock(&downloadLock); + pthread_join(downloadThread, NULL); +} + +//helper function for download +TH_ENTRY_POINT void* DownloadManagerHelper(void* obj) +{ + DownloadManager *temp = (DownloadManager*)obj; + temp->Update(); + return NULL; +} + +void DownloadManager::Start() +{ + managerRunning = true; + lastUsed = time(NULL); + pthread_create(&downloadThread, NULL, &DownloadManagerHelper, this); +} + +void DownloadManager::Update() +{ + unsigned int numActiveDownloads; + while (!managerShutdown) + { + pthread_mutex_lock(&downloadAddLock); + if (downloadsAddQueue.size()) + { + for (size_t i = 0; i < downloadsAddQueue.size(); i++) + { + downloads.push_back(downloadsAddQueue[i]); + } + downloadsAddQueue.clear(); + } + pthread_mutex_unlock(&downloadAddLock); + if (downloads.size()) + { + numActiveDownloads = 0; + pthread_mutex_lock(&downloadLock); + for (size_t i = 0; i < downloads.size(); i++) + { + Download *download = downloads[i]; + if (download->downloadCanceled) + { + if (download->http && download->downloadStarted) + http_force_close(download->http); + delete download; + downloads.erase(downloads.begin()+i); + i--; + } + else if (download->downloadStarted && !download->downloadFinished) + { + if (http_async_req_status(download->http) != 0) + { + download->downloadData = http_async_req_stop(download->http, &download->downloadStatus, &download->downloadSize); + download->downloadFinished = true; + if (!download->keepAlive) + download->http = NULL; + } + lastUsed = time(NULL); + numActiveDownloads++; + } + } + pthread_mutex_unlock(&downloadLock); + } + if (time(NULL) > lastUsed+http_timeout*2 && !numActiveDownloads) + { + pthread_mutex_lock(&downloadLock); + managerRunning = false; + pthread_mutex_unlock(&downloadLock); + return; + } + Platform::Millisleep(1); + } +} + +void DownloadManager::EnsureRunning() +{ + pthread_mutex_lock(&downloadLock); + if (!managerRunning) + { + if (threadStarted) + pthread_join(downloadThread, NULL); + else + threadStarted = true; + Start(); + } + pthread_mutex_unlock(&downloadLock); +} + +void DownloadManager::AddDownload(Download *download) +{ + pthread_mutex_lock(&downloadAddLock); + downloadsAddQueue.push_back(download); + pthread_mutex_unlock(&downloadAddLock); + EnsureRunning(); +} + +void DownloadManager::Lock() +{ + pthread_mutex_lock(&downloadAddLock); +} + +void DownloadManager::Unlock() +{ + pthread_mutex_unlock(&downloadAddLock); +} \ No newline at end of file diff --git a/src/client/DownloadManager.h b/src/client/DownloadManager.h new file mode 100644 index 0000000000..d6c42cbf87 --- /dev/null +++ b/src/client/DownloadManager.h @@ -0,0 +1,39 @@ +#ifndef DOWNLOADMANAGER_H +#define DOWNLOADMANAGER_H +#include "common/tpt-thread.h" +#include +#include +#include "common/Singleton.h" + +class Download; +class DownloadManager : public Singleton +{ +private: + pthread_t downloadThread; + pthread_mutex_t downloadLock; + pthread_mutex_t downloadAddLock; + bool threadStarted; + + int lastUsed; + volatile bool managerRunning; + volatile bool managerShutdown; + std::vector downloads; + std::vector downloadsAddQueue; + + void Start(); +public: + DownloadManager(); + ~DownloadManager(); + + void Shutdown(); + void Update(); + void EnsureRunning(); + + void AddDownload(Download *download); + void RemoveDownload(int id); + + void Lock(); + void Unlock(); +}; + +#endif // DOWNLOADMANAGER_H diff --git a/src/client/HTTP.cpp b/src/client/HTTP.cpp index e882930002..477d498824 100644 --- a/src/client/HTTP.cpp +++ b/src/client/HTTP.cpp @@ -48,6 +48,7 @@ #include #endif +#include "client/DownloadManager.h" #include "Config.h" #include "Misc.h" #include "HTTP.h" @@ -79,7 +80,6 @@ typedef SSIZE_T ssize_t; char * userAgent; static int http_up = 0; -static long http_timeout = 15; static int http_use_proxy = 0; static struct sockaddr_in http_proxy; @@ -208,6 +208,7 @@ void http_done(void) #ifdef WIN WSACleanup(); #endif + DownloadManager::Ref().Shutdown(); http_up = 0; } @@ -245,6 +246,12 @@ struct http_ctx void *http_async_req_start(void *ctx, const char *uri, const char *data, int dlen, int keep) { struct http_ctx *cx = (http_ctx *)ctx; + if (cx && time(NULL) - cx->last > http_timeout) + { + http_force_close(ctx); + http_async_req_close(ctx); + ctx = NULL; + } if (!ctx) { ctx = calloc(1, sizeof(struct http_ctx)); @@ -550,14 +557,18 @@ int http_async_req_status(void *ctx) tmp = send(cx->fd, cx->tbuf+cx->tptr, cx->tlen-cx->tptr, 0); if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR) goto fail; - if (tmp!=PERROR) + if (tmp!=PERROR && tmp) { cx->tptr += tmp; if (cx->tptr == cx->tlen) { cx->tptr = 0; cx->tlen = 0; - free(cx->tbuf); + if (cx->tbuf) + { + free(cx->tbuf); + cx->tbuf = NULL; + } cx->state = HTS_RECV; } cx->last = now; @@ -569,7 +580,7 @@ int http_async_req_status(void *ctx) tmp = recv(cx->fd, buf, CHUNK, 0); if (tmp==PERROR && PERRNO!=PEAGAIN && PERRNO!=PEINTR) goto fail; - if (tmp!=PERROR) + if (tmp!=PERROR && tmp) { for (i=0; itxd = NULL; cx->txdl = 0; } + if (cx->tbuf) + { + free(cx->tbuf); + cx->tbuf = NULL; + } if (cx->hbuf) { free(cx->hbuf); @@ -675,6 +691,12 @@ void http_async_get_length(void *ctx, int *total, int *done) *total = cx->contlen; } +void http_force_close(void *ctx) +{ + struct http_ctx *cx = (struct http_ctx*)ctx; + cx->state = HTS_DONE; +} + void http_async_req_close(void *ctx) { struct http_ctx *cx = (http_ctx *)ctx; @@ -710,7 +732,7 @@ void http_auth_headers(void *ctx, const char *user, const char *pass, const char unsigned char hash[16]; struct md5_context md5; - if (user) + if (user && strlen(user)) { if (pass) { @@ -730,7 +752,7 @@ void http_auth_headers(void *ctx, const char *user, const char *pass, const char http_async_add_header(ctx, "X-Auth-Hash", tmp); free(tmp); } - if (session_id) + if (session_id && strlen(session_id)) { http_async_add_header(ctx, "X-Auth-User-Id", user); http_async_add_header(ctx, "X-Auth-Session-Key", session_id); @@ -775,6 +797,9 @@ const char *http_ret_text(int ret) { switch (ret) { + case 0: + return "Status code 0 (bug?)"; + case 100: return "Continue"; case 101: @@ -908,6 +933,98 @@ const char *http_ret_text(int ret) return "Unknown Status Code"; } } + +// Find the boundary used in the multipart POST request +// the boundary is a string that never appears in any of the parts, ex. 'A92' +// keeps looking recursively until it finds one +std::string FindBoundary(std::map parts, std::string boundary) +{ + // we only look for a-zA-Z0-9 chars + unsigned int map[62]; + size_t blen = boundary.length(); + std::fill(&map[0], &map[62], 0); + for (std::map::iterator iter = parts.begin(); iter != parts.end(); iter++) + { + // loop through every character in each part and search for the substring, adding 1 to map for every character found (character after the substring) + for (ssize_t j = 0; j < (ssize_t)((*iter).second.length())-blen; j++) + if (!blen || (*iter).second.substr(j, blen) == boundary) + { + unsigned char ch = (*iter).second[j+blen]; + if (ch >= '0' && ch <= '9') + map[ch-'0']++; + else if (ch >= 'A' && ch <= 'Z') + map[ch-'A'+10]++; + else if (ch >= 'a' && ch <= 'z') + map[ch-'a'+36]++; + } + } + // find which next character occurs the least (preferably it occurs 0 times which means we have a match) + unsigned int lowest = 0; + for (unsigned int i = 1; i < 62; i++) + { + if (!map[lowest]) + break; + if (map[i] < map[lowest]) + lowest = i; + } + + // add the least frequent character to our boundary + if (lowest < 10) + boundary += '0'+lowest; + else if (lowest < 36) + boundary += 'A'+(lowest-10); + else + boundary += 'a'+(lowest-36); + + if (map[lowest]) + return FindBoundary(parts, boundary); + else + return boundary; +} + +// Generates a MIME multipart message to be used in POST requests +// see https://en.wikipedia.org/wiki/MIME#Multipart_messages +// this function used in Download class, and eventually all http requests +std::string GetMultipartMessage(std::map parts, std::string boundary) +{ + std::stringstream data; + + // loop through each part, adding it + for (std::map::iterator iter = parts.begin(); iter != parts.end(); iter++) + { + std::string name = (*iter).first; + std::string value = (*iter).second; + + data << "--" << boundary << "\r\n"; + data << "Content-transfer-encoding: binary" << "\r\n"; + + // colon p + size_t colonP = name.find(':'); + if (colonP != name.npos) + { + // used to upload files (save data) + data << "content-disposition: form-data; name=\"" << name.substr(0, colonP) << "\""; + data << "filename=\"" << name.substr(colonP+1) << "\""; + } + else + { + data << "content-disposition: form-data; name=\"" << name << "\""; + } + data << "\r\n\r\n"; + data << value; + data << "\r\n"; + } + data << "--" << boundary << "--\r\n"; + return data.str(); +} + +// add the header needed to make POSTS work +void http_add_multipart_header(void *ctx, std::string boundary) +{ + std::string header = "multipart/form-data, boundary=" + boundary; + http_async_add_header(ctx, "Content-type", header.c_str()); +} + char *http_multipart_post(const char *uri, const char *const *names, const char *const *parts, size_t *plens, const char *user, const char *pass, const char *session_id, int *ret, int *len) { void *ctx; diff --git a/src/client/HTTP.h b/src/client/HTTP.h index 8761093fe5..e927284511 100644 --- a/src/client/HTTP.h +++ b/src/client/HTTP.h @@ -20,7 +20,11 @@ #ifndef HTTP_H #define HTTP_H +#include +#include + static const char hexChars[] = "0123456789abcdef"; +static long http_timeout = 15; void http_init(char *proxy); void http_done(void); @@ -37,7 +41,11 @@ int http_async_req_status(void *ctx); void http_async_get_length(void *ctx, int *total, int *done); char *http_async_req_stop(void *ctx, int *ret, int *len); void http_async_req_close(void *ctx); +void http_force_close(void *ctx); +std::string FindBoundary(std::map, std::string boundary); +std::string GetMultipartMessage(std::map, std::string boundary); +void http_add_multipart_header(void *ctx, std::string boundary); char *http_multipart_post(const char *uri, const char *const *names, const char *const *parts, size_t *plens, const char *user, const char *pass, const char * session_id, int *ret, int *len); void *http_multipart_post_async(const char *uri, const char *const *names, const char *const *parts, int *plens, const char *user, const char *pass, const char *session_id); diff --git a/src/client/requestbroker/RequestBroker.h b/src/client/requestbroker/RequestBroker.h index c44c2f673f..b020d427a3 100644 --- a/src/client/requestbroker/RequestBroker.h +++ b/src/client/requestbroker/RequestBroker.h @@ -7,7 +7,7 @@ #include "common/tpt-thread.h" #include "Config.h" -#include "Singleton.h" +#include "common/Singleton.h" class GameSave; class VideoBuffer; diff --git a/src/Singleton.h b/src/common/Singleton.h similarity index 100% rename from src/Singleton.h rename to src/common/Singleton.h diff --git a/src/gui/game/GameController.cpp b/src/gui/game/GameController.cpp index 45b4823f7d..062c621ab6 100644 --- a/src/gui/game/GameController.cpp +++ b/src/gui/game/GameController.cpp @@ -68,11 +68,11 @@ class GameController::SaveOpenCallback: public ControllerCallback SaveOpenCallback(GameController * cc_) { cc = cc_; } virtual void ControllerExit() { - if(cc->activePreview->GetDoOpen() && cc->activePreview->GetSave()) + if(cc->activePreview->GetDoOpen() && cc->activePreview->GetSaveInfo()) { try { - cc->LoadSave(cc->activePreview->GetSave()); + cc->LoadSave(cc->activePreview->GetSaveInfo()); } catch(GameModelException & ex) { diff --git a/src/gui/interface/Engine.h b/src/gui/interface/Engine.h index 76e0be8e0b..43feda41c6 100644 --- a/src/gui/interface/Engine.h +++ b/src/gui/interface/Engine.h @@ -1,7 +1,7 @@ #pragma once #include -#include "Singleton.h" +#include "common/Singleton.h" #include "graphics/Graphics.h" #include "Window.h" diff --git a/src/gui/preview/PreviewController.cpp b/src/gui/preview/PreviewController.cpp index ceccfcc93e..35748cd925 100644 --- a/src/gui/preview/PreviewController.cpp +++ b/src/gui/preview/PreviewController.cpp @@ -58,12 +58,13 @@ PreviewController::PreviewController(int saveID, bool instant, ControllerCallbac void PreviewController::Update() { - if(loginWindow && loginWindow->HasExited == true) + previewModel->Update(); + if (loginWindow && loginWindow->HasExited == true) { delete loginWindow; loginWindow = NULL; } - if(previewModel->GetDoOpen() && previewModel->GetSave() && previewModel->GetSave()->GetGameSave()) + if (previewModel->GetDoOpen() && previewModel->GetSaveInfo() && previewModel->GetSaveInfo()->GetGameSave()) { Exit(); } @@ -104,9 +105,9 @@ void PreviewController::NotifyAuthUserChanged(Client * sender) previewModel->SetCommentBoxEnabled(sender->GetAuthUser().ID); } -SaveInfo * PreviewController::GetSave() +SaveInfo * PreviewController::GetSaveInfo() { - return previewModel->GetSave(); + return previewModel->GetSaveInfo(); } bool PreviewController::GetDoOpen() @@ -132,11 +133,11 @@ void PreviewController::Report(std::string message) void PreviewController::FavouriteSave() { - if(previewModel->GetSave() && Client::Ref().GetAuthUser().ID) + if(previewModel->GetSaveInfo() && Client::Ref().GetAuthUser().ID) { try { - if(previewModel->GetSave()->Favourite) + if(previewModel->GetSaveInfo()->Favourite) previewModel->SetFavourite(false); else previewModel->SetFavourite(true); diff --git a/src/gui/preview/PreviewController.h b/src/gui/preview/PreviewController.h index 800ae0dae1..8e148cb1f5 100644 --- a/src/gui/preview/PreviewController.h +++ b/src/gui/preview/PreviewController.h @@ -30,7 +30,7 @@ class PreviewController: public ClientListener { void Report(std::string message); void ShowLogin(); bool GetDoOpen(); - SaveInfo * GetSave(); + SaveInfo * GetSaveInfo(); PreviewView * GetView() { return previewView; } void Update(); void FavouriteSave(); diff --git a/src/gui/preview/PreviewModel.cpp b/src/gui/preview/PreviewModel.cpp index 9133fd154c..4825433589 100644 --- a/src/gui/preview/PreviewModel.cpp +++ b/src/gui/preview/PreviewModel.cpp @@ -1,5 +1,6 @@ #include #include "PreviewModel.h" +#include "Format.h" #include "client/Client.h" #include "client/GameSave.h" #include "gui/dialogues/ErrorMessage.h" @@ -8,7 +9,7 @@ PreviewModel::PreviewModel(): doOpen(false), canOpen(true), - save(NULL), + saveInfo(NULL), saveData(NULL), saveComments(NULL), commentBoxEnabled(false), @@ -21,10 +22,10 @@ PreviewModel::PreviewModel(): void PreviewModel::SetFavourite(bool favourite) { - if(save) + if (saveInfo) { - if (Client::Ref().FavouriteSave(save->id, favourite) == RequestOkay) - save->Favourite = favourite; + 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 @@ -49,37 +50,48 @@ void PreviewModel::SetCommentBoxEnabled(bool enabledState) void PreviewModel::UpdateSave(int saveID, int saveDate) { - this->tSaveID = saveID; - this->tSaveDate = saveDate; + this->saveID = saveID; + this->saveDate = saveDate; - if (save) + if (saveInfo) { - delete save; - save = NULL; + delete saveInfo; + saveInfo = NULL; } if (saveData) { delete saveData; saveData = NULL; } - if (saveComments) - { - for (size_t i = 0; i < saveComments->size(); i++) - delete saveComments->at(i); - saveComments->clear(); - delete saveComments; - saveComments = NULL; - } + ClearComments(); notifySaveChanged(); notifySaveCommentsChanged(); - RequestBroker::Ref().Start(Client::Ref().GetSaveDataAsync(saveID, saveDate), this, 1); - RequestBroker::Ref().Start(Client::Ref().GetSaveAsync(saveID, saveDate), this, 2); + std::stringstream urlStream; + if (saveDate) + urlStream << "http://" << STATICSERVER << "/" << saveID << "_" << saveDate << ".cps"; + else + urlStream << "http://" << STATICSERVER << "/" << saveID << ".cps"; + saveDataDownload = new Download(urlStream.str()); + saveDataDownload->Start(); + + urlStream.str(""); + urlStream << "http://" << SERVER << "/Browse/View.json?ID=" << saveID; + if (saveDate) + urlStream << "&Date=" << saveDate; + saveInfoDownload = new Download(urlStream.str()); + saveInfoDownload->AuthHeaders(format::NumberToString(Client::Ref().GetAuthUser().ID), Client::Ref().GetAuthUser().SessionID); + saveInfoDownload->Start(); if (!GetDoOpen()) { commentsLoaded = false; - RequestBroker::Ref().Start(Client::Ref().GetCommentsAsync(saveID, (commentsPageNumber-1)*20, 20), this, 3); + + urlStream.str(""); + urlStream << "http://" << SERVER << "/Browse/Comments.json?ID=" << saveID << "&Start=" << (commentsPageNumber-1)*20 << "&Count=20"; + commentsDownload = new Download(urlStream.str()); + commentsDownload->AuthHeaders(format::NumberToString(Client::Ref().GetAuthUser().ID), Client::Ref().GetAuthUser().SessionID); + commentsDownload->Start(); } } @@ -98,9 +110,9 @@ bool PreviewModel::GetCanOpen() return canOpen; } -SaveInfo * PreviewModel::GetSave() +SaveInfo * PreviewModel::GetSaveInfo() { - return save; + return saveInfo; } int PreviewModel::GetCommentsPageNum() @@ -123,18 +135,17 @@ void PreviewModel::UpdateComments(int pageNumber) if (commentsLoaded) { commentsLoaded = false; - if (saveComments) - { - for (size_t i = 0; i < saveComments->size(); i++) - delete saveComments->at(i); - saveComments->clear(); - delete saveComments; - saveComments = NULL; - } + ClearComments(); commentsPageNumber = pageNumber; if (!GetDoOpen()) - RequestBroker::Ref().Start(Client::Ref().GetCommentsAsync(tSaveID, (commentsPageNumber-1)*20, 20), this, 3); + { + std::stringstream urlStream; + urlStream << "http://" << SERVER << "/Browse/Comments.json?ID=" << saveID << "&Start=" << (commentsPageNumber-1)*20 << "&Count=20"; + commentsDownload = new Download(urlStream.str()); + commentsDownload->AuthHeaders(format::NumberToString(Client::Ref().GetAuthUser().ID).c_str(), Client::Ref().GetAuthUser().SessionID.c_str()); + commentsDownload->Start(); + } notifySaveCommentsChanged(); notifyCommentsPageChanged(); @@ -143,93 +154,186 @@ void PreviewModel::UpdateComments(int pageNumber) void PreviewModel::CommentAdded() { - if (save) - save->Comments++; + if (saveInfo) + saveInfo->Comments++; commentsTotal++; } -void PreviewModel::OnResponseReady(void * object, int identifier) +void PreviewModel::OnSaveReady() { - if (identifier == 1) + commentsTotal = saveInfo->Comments; + try { - delete saveData; - saveData = (std::vector*)object; + GameSave *gameSave = new GameSave(*saveData); + if (gameSave->fromNewerVersion) + new ErrorMessage("This save is from a newer version", "Please update TPT in game or at http://powdertoy.co.uk"); + saveInfo->SetGameSave(gameSave); } - if (identifier == 2) + catch(ParseException &e) { - delete save; - save = (SaveInfo*)object; + new ErrorMessage("Error", e.what()); + canOpen = false; } - if (identifier == 3) - { - if (saveComments) - { - for (size_t i = 0; i < saveComments->size(); i++) - delete saveComments->at(i); - saveComments->clear(); - delete saveComments; - saveComments = NULL; - } - saveComments = (std::vector*)object; - commentsLoaded = true; + notifySaveChanged(); + notifyCommentsPageChanged(); + //make sure author name comments are red + if (commentsLoaded) notifySaveCommentsChanged(); - notifyCommentsPageChanged(); +} + +void PreviewModel::ClearComments() +{ + if (saveComments) + { + for (size_t i = 0; i < saveComments->size(); i++) + delete saveComments->at(i); + saveComments->clear(); + delete saveComments; + saveComments = NULL; } +} - if (identifier == 1 || identifier == 2) +bool PreviewModel::ParseSaveInfo(char * saveInfoResponse) +{ + delete saveInfo; + + try + { + 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(); + std::string tempUsername = objDocument["Username"].asString(); + std::string tempName = objDocument["Name"].asString(); + std::string tempDescription = objDocument["Description"].asString(); + int tempDate = 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 tempTags; + for (Json::UInt j = 0; j < tagsArray.size(); j++) + tempTags.push_back(tagsArray[j].asString()); + + saveInfo = new SaveInfo(tempID, tempDate, tempScoreUp, tempScoreDown, + tempMyScore, tempUsername, tempName, tempDescription, + tempPublished, tempTags); + saveInfo->Comments = tempComments; + saveInfo->Favourite = tempFavourite; + saveInfo->Views = tempViews; + saveInfo->Version = tempVersion; + return true; + } + catch (std::exception &e) + { + saveInfo = NULL; + return false; + } +} + +bool PreviewModel::ParseComments(char *commentsResponse) +{ + ClearComments(); + saveComments = new std::vector(); + try { - if (save && saveData) + std::istringstream dataStream(commentsResponse); + Json::Value commentsArray; + dataStream >> commentsArray; + + for (Json::UInt j = 0; j < commentsArray.size(); j++) { - commentsTotal = save->Comments; - try - { - GameSave *gameSave = new GameSave(*saveData); - if (gameSave->fromNewerVersion) - new ErrorMessage("This save is from a newer version", "Please update TPT in game or at http://powdertoy.co.uk"); - save->SetGameSave(gameSave); - } - catch(ParseException &e) - { - new ErrorMessage("Error", e.what()); - canOpen = false; - } - notifySaveChanged(); - notifyCommentsPageChanged(); - //make sure author name comments are red - if (commentsLoaded) - notifySaveCommentsChanged(); + int userID = format::StringToNumber(commentsArray[j]["UserID"].asString()); + std::string username = commentsArray[j]["Username"].asString(); + std::string formattedUsername = commentsArray[j]["FormattedUsername"].asString(); + if (formattedUsername == "jacobot") + formattedUsername = "\bt" + formattedUsername; + std::string comment = commentsArray[j]["Text"].asString(); + saveComments->push_back(new SaveComment(userID, username, formattedUsername, comment)); } + return true; + } + catch (std::exception &e) + { + return false; } } -void PreviewModel::OnResponseFailed(int identifier) +void PreviewModel::Update() { - if (identifier == 3) + if (saveDataDownload && saveDataDownload->CheckDone()) { - if (saveComments) + int status, length; + char *ret = saveDataDownload->Finish(&length, &status); + + Client::Ref().ParseServerReturn(NULL, status, true); + if (status == 200 && ret) { - for (size_t i = 0; i < saveComments->size(); i++) - delete saveComments->at(i); - saveComments->clear(); - delete saveComments; - saveComments = NULL; + delete saveData; + saveData = new std::vector(ret, ret+length); + if (saveInfo && saveData) + OnSaveReady(); } - saveComments = NULL; - commentsLoaded = true; - notifySaveCommentsChanged(); + else + { + for (size_t i = 0; i < observers.size(); i++) + { + observers[i]->SaveLoadingError(Client::Ref().GetLastError()); + } + } + saveDataDownload = NULL; } - else + + if (saveInfoDownload && saveInfoDownload->CheckDone()) { - for (size_t i = 0; i < observers.size(); i++) + int status; + char *ret = saveInfoDownload->Finish(NULL, &status); + + Client::Ref().ParseServerReturn(NULL, status, true); + if (status == 200 && ret) { - observers[i]->SaveLoadingError(Client::Ref().GetLastError()); + if (ParseSaveInfo(ret)) + { + if (saveInfo && saveData) + OnSaveReady(); + } + else + { + 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()); } + saveInfoDownload = NULL; } -} -void PreviewModel::Update() -{ + if (commentsDownload && commentsDownload->CheckDone()) + { + int status; + char *ret = commentsDownload->Finish(NULL, &status); + ClearComments(); + + Client::Ref().ParseServerReturn(NULL, status, true); + if (status == 200 && ret) + ParseComments(ret); + commentsLoaded = true; + notifySaveCommentsChanged(); + notifyCommentsPageChanged(); + + commentsDownload = NULL; + } } std::vector * PreviewModel::GetComments() @@ -281,14 +385,13 @@ void PreviewModel::AddObserver(PreviewView * observer) PreviewModel::~PreviewModel() { - RequestBroker::Ref().DetachRequestListener(this); - delete save; + if (saveDataDownload) + saveDataDownload->Cancel(); + if (saveInfoDownload) + saveInfoDownload->Cancel(); + if (commentsDownload) + commentsDownload->Cancel(); + delete saveInfo; delete saveData; - if (saveComments) - { - for (size_t i = 0; i < saveComments->size(); i++) - delete saveComments->at(i); - saveComments->clear(); - delete saveComments; - } + ClearComments(); } diff --git a/src/gui/preview/PreviewModel.h b/src/gui/preview/PreviewModel.h index 7cfce046aa..33a62b5c60 100644 --- a/src/gui/preview/PreviewModel.h +++ b/src/gui/preview/PreviewModel.h @@ -3,21 +3,20 @@ #include #include -#include "common/tpt-thread.h" #include "PreviewView.h" #include "client/SaveInfo.h" #include "gui/preview/Comment.h" #include "gui/search/Thumbnail.h" -#include "client/requestbroker/RequestListener.h" +#include "client/Download.h" using namespace std; class PreviewView; -class PreviewModel: RequestListener { +class PreviewModel { bool doOpen; bool canOpen; vector observers; - SaveInfo * save; + SaveInfo * saveInfo; std::vector * saveData; std::vector * saveComments; void notifySaveChanged(); @@ -25,11 +24,12 @@ class PreviewModel: RequestListener { void notifyCommentsPageChanged(); void notifyCommentBoxEnabledChanged(); - //Background retrieval - int tSaveID; - int tSaveDate; + Download * saveDataDownload; + Download * saveInfoDownload; + Download * commentsDownload; + int saveID; + int saveDate; - // bool commentBoxEnabled; bool commentsLoaded; int commentsTotal; @@ -37,7 +37,7 @@ class PreviewModel: RequestListener { public: PreviewModel(); - SaveInfo * GetSave(); + SaveInfo * GetSaveInfo(); std::vector * GetComments(); bool GetCommentBoxEnabled(); @@ -56,8 +56,10 @@ class PreviewModel: RequestListener { bool GetCanOpen(); void SetDoOpen(bool doOpen); void Update(); - virtual void OnResponseReady(void * object, int identifier); - virtual void OnResponseFailed(int identifier); + void ClearComments(); + void OnSaveReady(); + bool ParseSaveInfo(char * saveInfoResponse); + bool ParseComments(char * commentsResponse); virtual ~PreviewModel(); }; diff --git a/src/gui/preview/PreviewView.cpp b/src/gui/preview/PreviewView.cpp index 1468878e63..10189c62e6 100644 --- a/src/gui/preview/PreviewView.cpp +++ b/src/gui/preview/PreviewView.cpp @@ -415,7 +415,7 @@ void PreviewView::OnKeyPress(int key, Uint16 character, bool shift, bool ctrl, b void PreviewView::NotifySaveChanged(PreviewModel * sender) { - SaveInfo * save = sender->GetSave(); + SaveInfo * save = sender->GetSaveInfo(); delete savePreview; savePreview = NULL; if(save) @@ -598,7 +598,7 @@ void PreviewView::NotifyCommentsChanged(PreviewModel * sender) tempUsername->Appearance.VerticalAlign = ui::Appearance::AlignBottom; if (Client::Ref().GetAuthUser().ID && Client::Ref().GetAuthUser().Username == comments->at(i)->authorName) tempUsername->SetTextColour(ui::Colour(255, 255, 100)); - else if (sender->GetSave() && sender->GetSave()->GetUserName() == comments->at(i)->authorName) + else if (sender->GetSaveInfo() && sender->GetSaveInfo()->GetUserName() == comments->at(i)->authorName) tempUsername->SetTextColour(ui::Colour(255, 100, 100)); currentY += 16; diff --git a/src/gui/search/SearchController.cpp b/src/gui/search/SearchController.cpp index 550dc0b76c..f35765e9c9 100644 --- a/src/gui/search/SearchController.cpp +++ b/src/gui/search/SearchController.cpp @@ -19,9 +19,9 @@ class SearchController::OpenCallback: public ControllerCallback OpenCallback(SearchController * cc_) { cc = cc_; } virtual void ControllerExit() { - if(cc->activePreview->GetDoOpen() && cc->activePreview->GetSave()) + if(cc->activePreview->GetDoOpen() && cc->activePreview->GetSaveInfo()) { - cc->searchModel->SetLoadedSave(cc->activePreview->GetSave()); + cc->searchModel->SetLoadedSave(cc->activePreview->GetSaveInfo()); } else { diff --git a/src/simulation/SaveRenderer.h b/src/simulation/SaveRenderer.h index 6f791d3f82..c3d7b1660c 100644 --- a/src/simulation/SaveRenderer.h +++ b/src/simulation/SaveRenderer.h @@ -3,7 +3,7 @@ #ifdef OGLI #include "graphics/OpenGLHeaders.h" #endif -#include "Singleton.h" +#include "common/Singleton.h" class GameSave; class VideoBuffer;