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