1 #include "PreviewModel.h"
2
3 #include <cmath>
4 #include <iostream>
5
6 #include "Format.h"
7
8 #include "client/Client.h"
9 #include "client/GameSave.h"
10 #include "client/SaveInfo.h"
11 #include "client/http/Request.h"
12
13 #include "gui/dialogues/ErrorMessage.h"
14 #include "gui/preview/Comment.h"
15
16 #include "PreviewModelException.h"
17 #include "PreviewView.h"
18
PreviewModel()19 PreviewModel::PreviewModel():
20 doOpen(false),
21 canOpen(true),
22 saveInfo(NULL),
23 saveData(NULL),
24 saveComments(NULL),
25 saveDataDownload(NULL),
26 commentsDownload(NULL),
27 commentBoxEnabled(false),
28 commentsLoaded(false),
29 commentsTotal(0),
30 commentsPageNumber(1)
31 {
32
33 }
34
SetFavourite(bool favourite)35 void PreviewModel::SetFavourite(bool favourite)
36 {
37 if (saveInfo)
38 {
39 if (Client::Ref().FavouriteSave(saveInfo->id, favourite) == RequestOkay)
40 saveInfo->Favourite = favourite;
41 else if (favourite)
42 throw PreviewModelException("Error, could not fav. the save: " + Client::Ref().GetLastError());
43 else
44 throw PreviewModelException("Error, could not unfav. the save: " + Client::Ref().GetLastError());
45 notifySaveChanged();
46 }
47 }
48
GetCommentBoxEnabled()49 bool PreviewModel::GetCommentBoxEnabled()
50 {
51 return commentBoxEnabled;
52 }
53
SetCommentBoxEnabled(bool enabledState)54 void PreviewModel::SetCommentBoxEnabled(bool enabledState)
55 {
56 if(enabledState != commentBoxEnabled)
57 {
58 commentBoxEnabled = enabledState;
59 notifyCommentBoxEnabledChanged();
60 }
61 }
62
UpdateSave(int saveID,int saveDate)63 void PreviewModel::UpdateSave(int saveID, int saveDate)
64 {
65 this->saveID = saveID;
66 this->saveDate = saveDate;
67
68 if (saveInfo)
69 {
70 delete saveInfo;
71 saveInfo = NULL;
72 }
73 if (saveData)
74 {
75 delete saveData;
76 saveData = NULL;
77 }
78 ClearComments();
79 notifySaveChanged();
80 notifySaveCommentsChanged();
81
82 ByteString url;
83 if (saveDate)
84 url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, "_", saveDate, ".cps");
85 else
86 url = ByteString::Build(STATICSCHEME, STATICSERVER, "/", saveID, ".cps");
87 saveDataDownload = new http::Request(url);
88 saveDataDownload->Start();
89
90 url = ByteString::Build(SCHEME, SERVER , "/Browse/View.json?ID=", saveID);
91 if (saveDate)
92 url += ByteString::Build("&Date=", saveDate);
93 saveInfoDownload = new http::Request(url);
94 saveInfoDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
95 saveInfoDownload->Start();
96
97 if (!GetDoOpen())
98 {
99 commentsLoaded = false;
100
101 url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
102 commentsDownload = new http::Request(url);
103 commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
104 commentsDownload->Start();
105 }
106 }
107
SetDoOpen(bool doOpen)108 void PreviewModel::SetDoOpen(bool doOpen)
109 {
110 this->doOpen = doOpen;
111 }
112
GetDoOpen()113 bool PreviewModel::GetDoOpen()
114 {
115 return doOpen;
116 }
117
GetCanOpen()118 bool PreviewModel::GetCanOpen()
119 {
120 return canOpen;
121 }
122
GetSaveInfo()123 SaveInfo * PreviewModel::GetSaveInfo()
124 {
125 return saveInfo;
126 }
127
GetCommentsPageNum()128 int PreviewModel::GetCommentsPageNum()
129 {
130 return commentsPageNumber;
131 }
132
GetCommentsPageCount()133 int PreviewModel::GetCommentsPageCount()
134 {
135 return std::max(1, (int)(ceil(commentsTotal/20.0f)));
136 }
137
GetCommentsLoaded()138 bool PreviewModel::GetCommentsLoaded()
139 {
140 return commentsLoaded;
141 }
142
UpdateComments(int pageNumber)143 void PreviewModel::UpdateComments(int pageNumber)
144 {
145 if (commentsLoaded)
146 {
147 commentsLoaded = false;
148 ClearComments();
149
150 commentsPageNumber = pageNumber;
151 if (!GetDoOpen())
152 {
153 ByteString url = ByteString::Build(SCHEME, SERVER, "/Browse/Comments.json?ID=", saveID, "&Start=", (commentsPageNumber-1)*20, "&Count=20");
154 commentsDownload = new http::Request(url);
155 commentsDownload->AuthHeaders(ByteString::Build(Client::Ref().GetAuthUser().UserID), Client::Ref().GetAuthUser().SessionID);
156 commentsDownload->Start();
157 }
158
159 notifySaveCommentsChanged();
160 notifyCommentsPageChanged();
161 }
162 }
163
CommentAdded()164 void PreviewModel::CommentAdded()
165 {
166 if (saveInfo)
167 saveInfo->Comments++;
168 commentsTotal++;
169 }
170
OnSaveReady()171 void PreviewModel::OnSaveReady()
172 {
173 commentsTotal = saveInfo->Comments;
174 try
175 {
176 GameSave *gameSave = new GameSave(*saveData);
177 if (gameSave->fromNewerVersion)
178 new ErrorMessage("This save is from a newer version", "Please update TPT in game or at https://powdertoy.co.uk");
179 saveInfo->SetGameSave(gameSave);
180 }
181 catch(ParseException &e)
182 {
183 new ErrorMessage("Error", ByteString(e.what()).FromUtf8());
184 canOpen = false;
185 }
186 notifySaveChanged();
187 notifyCommentsPageChanged();
188 //make sure author name comments are red
189 if (commentsLoaded)
190 notifySaveCommentsChanged();
191 }
192
ClearComments()193 void PreviewModel::ClearComments()
194 {
195 if (saveComments)
196 {
197 for (size_t i = 0; i < saveComments->size(); i++)
198 delete saveComments->at(i);
199 saveComments->clear();
200 delete saveComments;
201 saveComments = NULL;
202 }
203 }
204
ParseSaveInfo(ByteString & saveInfoResponse)205 bool PreviewModel::ParseSaveInfo(ByteString &saveInfoResponse)
206 {
207 delete saveInfo;
208
209 try
210 {
211 std::istringstream dataStream(saveInfoResponse);
212 Json::Value objDocument;
213 dataStream >> objDocument;
214
215 int tempID = objDocument["ID"].asInt();
216 int tempScoreUp = objDocument["ScoreUp"].asInt();
217 int tempScoreDown = objDocument["ScoreDown"].asInt();
218 int tempMyScore = objDocument["ScoreMine"].asInt();
219 ByteString tempUsername = objDocument["Username"].asString();
220 String tempName = ByteString(objDocument["Name"].asString()).FromUtf8();
221 String tempDescription = ByteString(objDocument["Description"].asString()).FromUtf8();
222 int tempCreatedDate = objDocument["DateCreated"].asInt();
223 int tempUpdatedDate = objDocument["Date"].asInt();
224 bool tempPublished = objDocument["Published"].asBool();
225 bool tempFavourite = objDocument["Favourite"].asBool();
226 int tempComments = objDocument["Comments"].asInt();
227 int tempViews = objDocument["Views"].asInt();
228 int tempVersion = objDocument["Version"].asInt();
229
230 Json::Value tagsArray = objDocument["Tags"];
231 std::list<ByteString> tempTags;
232 for (Json::UInt j = 0; j < tagsArray.size(); j++)
233 tempTags.push_back(tagsArray[j].asString());
234
235 saveInfo = new SaveInfo(tempID, tempCreatedDate, tempUpdatedDate, tempScoreUp,
236 tempScoreDown, tempMyScore, tempUsername, tempName,
237 tempDescription, tempPublished, tempTags);
238 saveInfo->Comments = tempComments;
239 saveInfo->Favourite = tempFavourite;
240 saveInfo->Views = tempViews;
241 saveInfo->Version = tempVersion;
242
243 // This is a workaround for a bug on the TPT server where the wrong 404 save is returned
244 // Redownload the .cps file for a fixed version of the 404 save
245 if (tempID == 404 && this->saveID != 404)
246 {
247 if (saveDataDownload)
248 saveDataDownload->Cancel();
249 delete saveData;
250 saveData = NULL;
251 saveDataDownload = new http::Request(ByteString::Build(STATICSCHEME, STATICSERVER, "/2157797.cps"));
252 saveDataDownload->Start();
253 }
254 return true;
255 }
256 catch (std::exception &e)
257 {
258 saveInfo = NULL;
259 return false;
260 }
261 }
262
ParseComments(ByteString & commentsResponse)263 bool PreviewModel::ParseComments(ByteString &commentsResponse)
264 {
265 ClearComments();
266 saveComments = new std::vector<SaveComment*>();
267 try
268 {
269 std::istringstream dataStream(commentsResponse);
270 Json::Value commentsArray;
271 dataStream >> commentsArray;
272
273 for (Json::UInt j = 0; j < commentsArray.size(); j++)
274 {
275 int userID = ByteString(commentsArray[j]["UserID"].asString()).ToNumber<int>();
276 ByteString username = commentsArray[j]["Username"].asString();
277 ByteString formattedUsername = commentsArray[j]["FormattedUsername"].asString();
278 if (formattedUsername == "jacobot")
279 formattedUsername = "\bt" + formattedUsername;
280 String comment = ByteString(commentsArray[j]["Text"].asString()).FromUtf8();
281 saveComments->push_back(new SaveComment(userID, username, formattedUsername, comment));
282 }
283 return true;
284 }
285 catch (std::exception &e)
286 {
287 return false;
288 }
289 }
290
Update()291 void PreviewModel::Update()
292 {
293 if (saveDataDownload && saveDataDownload->CheckDone())
294 {
295 int status;
296 ByteString ret = saveDataDownload->Finish(&status);
297
298 ByteString nothing;
299 Client::Ref().ParseServerReturn(nothing, status, true);
300 if (status == 200 && ret.size())
301 {
302 delete saveData;
303 saveData = new std::vector<unsigned char>(ret.begin(), ret.end());
304 if (saveInfo && saveData)
305 OnSaveReady();
306 }
307 else
308 {
309 for (size_t i = 0; i < observers.size(); i++)
310 {
311 observers[i]->SaveLoadingError(Client::Ref().GetLastError());
312 }
313 }
314 saveDataDownload = NULL;
315 }
316
317 if (saveInfoDownload && saveInfoDownload->CheckDone())
318 {
319 int status;
320 ByteString ret = saveInfoDownload->Finish(&status);
321
322 ByteString nothing;
323 Client::Ref().ParseServerReturn(nothing, status, true);
324 if (status == 200 && ret.size())
325 {
326 if (ParseSaveInfo(ret))
327 {
328 if (saveInfo && saveData)
329 OnSaveReady();
330 }
331 else
332 {
333 for (size_t i = 0; i < observers.size(); i++)
334 observers[i]->SaveLoadingError("Could not parse save info");
335 }
336 }
337 else
338 {
339 for (size_t i = 0; i < observers.size(); i++)
340 observers[i]->SaveLoadingError(Client::Ref().GetLastError());
341 }
342 saveInfoDownload = NULL;
343 }
344
345 if (commentsDownload && commentsDownload->CheckDone())
346 {
347 int status;
348 ByteString ret = commentsDownload->Finish(&status);
349 ClearComments();
350
351 ByteString nothing;
352 Client::Ref().ParseServerReturn(nothing, status, true);
353 if (status == 200 && ret.size())
354 ParseComments(ret);
355
356 commentsLoaded = true;
357 notifySaveCommentsChanged();
358 notifyCommentsPageChanged();
359
360 commentsDownload = NULL;
361 }
362 }
363
GetComments()364 std::vector<SaveComment*> * PreviewModel::GetComments()
365 {
366 return saveComments;
367 }
368
notifySaveChanged()369 void PreviewModel::notifySaveChanged()
370 {
371 for (size_t i = 0; i < observers.size(); i++)
372 {
373 observers[i]->NotifySaveChanged(this);
374 }
375 }
376
notifyCommentBoxEnabledChanged()377 void PreviewModel::notifyCommentBoxEnabledChanged()
378 {
379 for (size_t i = 0; i < observers.size(); i++)
380 {
381 observers[i]->NotifyCommentBoxEnabledChanged(this);
382 }
383 }
384
notifyCommentsPageChanged()385 void PreviewModel::notifyCommentsPageChanged()
386 {
387 for (size_t i = 0; i < observers.size(); i++)
388 {
389 observers[i]->NotifyCommentsPageChanged(this);
390 }
391 }
392
notifySaveCommentsChanged()393 void PreviewModel::notifySaveCommentsChanged()
394 {
395 for (size_t i = 0; i < observers.size(); i++)
396 {
397 observers[i]->NotifyCommentsChanged(this);
398 }
399 }
400
AddObserver(PreviewView * observer)401 void PreviewModel::AddObserver(PreviewView * observer)
402 {
403 observers.push_back(observer);
404 observer->NotifySaveChanged(this);
405 observer->NotifyCommentsChanged(this);
406 observer->NotifyCommentsPageChanged(this);
407 observer->NotifyCommentBoxEnabledChanged(this);
408 }
409
410
~PreviewModel()411 PreviewModel::~PreviewModel()
412 {
413 if (saveDataDownload)
414 saveDataDownload->Cancel();
415 if (saveInfoDownload)
416 saveInfoDownload->Cancel();
417 if (commentsDownload)
418 commentsDownload->Cancel();
419 delete saveInfo;
420 delete saveData;
421 ClearComments();
422 }
423