Add support for modstore screenshots
[oweals/minetest.git] / src / convert_json.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include <vector>
21 #include <iostream>
22 #include <sstream>
23
24 #include "convert_json.h"
25 #include "mods.h"
26 #include "config.h"
27 #include "log.h"
28
29 #if USE_CURL
30 #include <curl/curl.h>
31
32 static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
33 {
34     ((std::string*)userp)->append((char*)contents, size * nmemb);
35     return size * nmemb;
36 }
37
38 #endif
39
40 Json::Value                 fetchJsonValue(const std::string url,
41                                                                                                         struct curl_slist *chunk) {
42 #if USE_CURL
43         std::string liststring;
44         CURL *curl;
45
46         curl = curl_easy_init();
47         if (curl)
48         {
49                 CURLcode res;
50
51                 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
52                 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
53                 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
54                 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
55
56                 if (chunk != 0)
57                         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
58
59
60                 res = curl_easy_perform(curl);
61                 if (res != CURLE_OK)
62                         errorstream<<"Jsonreader: "<< url <<" not found (" << curl_easy_strerror(res) << ")" <<std::endl;
63                 curl_easy_cleanup(curl);
64         }
65
66         Json::Value root;
67         Json::Reader reader;
68         std::istringstream stream(liststring);
69         if (!liststring.size()) {
70                 return Json::Value();
71         }
72
73         if (!reader.parse( stream, root ) )
74         {
75                 errorstream << "URL: " << url << std::endl;
76                 errorstream << "Failed to parse json data " << reader.getFormattedErrorMessages();
77                 errorstream << "data: \"" << liststring << "\"" << std::endl;
78                 return Json::Value();
79         }
80
81         if (root.isArray()) {
82                 return root;
83         }
84         if ((root["list"].isArray())) {
85                 return root["list"];
86         }
87         else {
88                 return root;
89         }
90 #endif
91         return Json::Value();
92 }
93
94 std::vector<ModStoreMod>    readModStoreList(Json::Value& modlist) {
95         std::vector<ModStoreMod> retval;
96
97         if (modlist.isArray()) {
98                 for (unsigned int i = 0; i < modlist.size(); i++)
99                 {
100                         ModStoreMod toadd;
101                         toadd.valid = true;
102
103                         //id
104                         if (modlist[i]["id"].asString().size()) {
105                                 const char* id_raw = modlist[i]["id"].asString().c_str();
106                                 char* endptr = 0;
107                                 int numbervalue = strtol(id_raw,&endptr,10);
108
109                                 if ((*id_raw != 0) && (*endptr == 0)) {
110                                         toadd.id = numbervalue;
111                                 }
112                         }
113                         else {
114                                 errorstream << "readModStoreList: missing id" << std::endl;
115                                 toadd.valid = false;
116                         }
117
118                         //title
119                         if (modlist[i]["title"].asString().size()) {
120                                 toadd.title = modlist[i]["title"].asString();
121                         }
122                         else {
123                                 errorstream << "readModStoreList: missing title" << std::endl;
124                                 toadd.valid = false;
125                         }
126
127                         //basename
128                         if (modlist[i]["basename"].asString().size()) {
129                                 toadd.basename = modlist[i]["basename"].asString();
130                         }
131                         else {
132                                 errorstream << "readModStoreList: missing basename" << std::endl;
133                                 toadd.valid = false;
134                         }
135
136                         //author
137
138                         //rating
139
140                         //version
141
142                         if (toadd.valid) {
143                                 retval.push_back(toadd);
144                         }
145                 }
146         }
147         return retval;
148 }
149
150 ModStoreModDetails          readModStoreModDetails(Json::Value& details) {
151
152         ModStoreModDetails retval;
153
154         retval.valid = true;
155
156         //version set
157         if (details["version_set"].isArray()) {
158                 for (unsigned int i = 0; i < details["version_set"].size(); i++)
159                 {
160                         ModStoreVersionEntry toadd;
161
162                         if (details["version_set"][i]["id"].asString().size()) {
163                                 const char* id_raw = details["version_set"][i]["id"].asString().c_str();
164                                 char* endptr = 0;
165                                 int numbervalue = strtol(id_raw,&endptr,10);
166
167                                 if ((*id_raw != 0) && (*endptr == 0)) {
168                                         toadd.id = numbervalue;
169                                 }
170                         }
171                         else {
172                                 errorstream << "readModStoreModDetails: missing version_set id" << std::endl;
173                                 retval.valid = false;
174                         }
175
176                         //date
177                         if (details["version_set"][i]["date"].asString().size()) {
178                                 toadd.date = details["version_set"][i]["date"].asString();
179                         }
180
181                         //file
182                         if (details["version_set"][i]["file"].asString().size()) {
183                                 toadd.file = details["version_set"][i]["file"].asString();
184                         }
185                         else {
186                                 errorstream << "readModStoreModDetails: missing version_set file" << std::endl;
187                                 retval.valid = false;
188                         }
189
190                         //approved
191
192                         //mtversion
193
194                         if( retval.valid ) {
195                                 retval.versions.push_back(toadd);
196                         }
197                         else {
198                                 break;
199                         }
200                 }
201         }
202
203         if (retval.versions.size() < 1) {
204                 errorstream << "readModStoreModDetails: not a single version specified!" << std::endl;
205                 retval.valid = false;
206         }
207
208         //categories
209         if (details["categories"].isObject()) {
210                 for (unsigned int i = 0; i < details["categories"].size(); i++) {
211                         ModStoreCategoryInfo toadd;
212
213                         if (details["categories"][i]["id"].asString().size()) {
214
215                                 const char* id_raw = details["categories"][i]["id"].asString().c_str();
216                                 char* endptr = 0;
217                                 int numbervalue = strtol(id_raw,&endptr,10);
218
219                                 if ((*id_raw != 0) && (*endptr == 0)) {
220                                         toadd.id = numbervalue;
221                                 }
222                         }
223                         else {
224                                 errorstream << "readModStoreModDetails: missing categories id" << std::endl;
225                                 retval.valid = false;
226                         }
227                         if (details["categories"][i]["title"].asString().size()) {
228                                 toadd.name = details["categories"][i]["title"].asString();
229                         }
230                         else {
231                                 errorstream << "readModStoreModDetails: missing categories title" << std::endl;
232                                 retval.valid = false;
233                         }
234
235                         if( retval.valid ) {
236                                 retval.categories.push_back(toadd);
237                         }
238                         else {
239                                 break;
240                         }
241                 }
242         }
243
244         //author
245         if (details["author"].isObject()) {
246                 if (details["author"]["id"].asString().size()) {
247
248                         const char* id_raw = details["author"]["id"].asString().c_str();
249                         char* endptr = 0;
250                         int numbervalue = strtol(id_raw,&endptr,10);
251
252                         if ((*id_raw != 0) && (*endptr == 0)) {
253                                 retval.author.id = numbervalue;
254                         }
255                         else {
256                                 errorstream << "readModStoreModDetails: missing author id (convert)" << std::endl;
257                                 retval.valid = false;
258                         }
259                 }
260                 else {
261                         errorstream << "readModStoreModDetails: missing author id" << std::endl;
262                         retval.valid = false;
263                 }
264
265                 if (details["author"]["username"].asString().size()) {
266                         retval.author.username = details["author"]["username"].asString();
267                 }
268                 else {
269                         errorstream << "readModStoreModDetails: missing author username" << std::endl;
270                         retval.valid = false;
271                 }
272         }
273         else {
274                 errorstream << "readModStoreModDetails: missing author" << std::endl;
275                 retval.valid = false;
276         }
277
278         //license
279         if (details["license"].isObject()) {
280                 if (details["license"]["id"].asString().size()) {
281
282                         const char* id_raw = details["license"]["id"].asString().c_str();
283                         char* endptr = 0;
284                         int numbervalue = strtol(id_raw,&endptr,10);
285
286                         if ((*id_raw != 0) && (*endptr == 0)) {
287                                 retval.license.id = numbervalue;
288                         }
289                 }
290                 else {
291                         errorstream << "readModStoreModDetails: missing license id" << std::endl;
292                         retval.valid = false;
293                 }
294
295                 if (details["license"]["short"].asString().size()) {
296                         retval.license.shortinfo = details["license"]["short"].asString();
297                 }
298                 else {
299                         errorstream << "readModStoreModDetails: missing license short" << std::endl;
300                         retval.valid = false;
301                 }
302
303                 if (details["license"]["link"].asString().size()) {
304                         retval.license.url = details["license"]["link"].asString();
305                 }
306
307         }
308
309         //titlepic
310         if (details["titlepic"].isObject()) {
311                 if (details["titlepic"]["id"].asString().size()) {
312
313                         const char* id_raw = details["titlepic"]["id"].asString().c_str();
314                         char* endptr = 0;
315                         int numbervalue = strtol(id_raw,&endptr,10);
316
317                         if ((*id_raw != 0) && (*endptr == 0)) {
318                                 retval.titlepic.id = numbervalue;
319                         }
320                 }
321
322                 if (details["titlepic"]["file"].asString().size()) {
323                         retval.titlepic.file = details["titlepic"]["file"].asString();
324                 }
325
326                 if (details["titlepic"]["desc"].asString().size()) {
327                         retval.titlepic.description = details["titlepic"]["desc"].asString();
328                 }
329
330                 if (details["titlepic"]["mod"].asString().size()) {
331
332                         const char* mod_raw = details["titlepic"]["mod"].asString().c_str();
333                         char* endptr = 0;
334                         int numbervalue = strtol(mod_raw,&endptr,10);
335
336                         if ((*mod_raw != 0) && (*endptr == 0)) {
337                                 retval.titlepic.mod = numbervalue;
338                         }
339                 }
340         }
341
342         //id
343         if (details["id"].asString().size()) {
344
345                 const char* id_raw = details["id"].asString().c_str();
346                 char* endptr = 0;
347                 int numbervalue = strtol(id_raw,&endptr,10);
348
349                 if ((*id_raw != 0) && (*endptr == 0)) {
350                         retval.id = numbervalue;
351                 }
352         }
353         else {
354                 errorstream << "readModStoreModDetails: missing id" << std::endl;
355                 retval.valid = false;
356         }
357
358         //title
359         if (details["title"].asString().size()) {
360                 retval.title = details["title"].asString();
361         }
362         else {
363                 errorstream << "readModStoreModDetails: missing title" << std::endl;
364                 retval.valid = false;
365         }
366
367         //basename
368         if (details["basename"].asString().size()) {
369                 retval.basename = details["basename"].asString();
370         }
371         else {
372                 errorstream << "readModStoreModDetails: missing basename" << std::endl;
373                 retval.valid = false;
374         }
375
376         //description
377         if (details["desc"].asString().size()) {
378                 retval.description = details["desc"].asString();
379         }
380
381         //repository
382         if (details["replink"].asString().size()) {
383                 retval.repository = details["replink"].asString();
384         }
385
386         //value
387         if (details["rating"].asString().size()) {
388
389                 const char* id_raw = details["rating"].asString().c_str();
390                 char* endptr = 0;
391                 float numbervalue = strtof(id_raw,&endptr);
392
393                 if ((*id_raw != 0) && (*endptr == 0)) {
394                         retval.rating = numbervalue;
395                 }
396         }
397         else {
398                 retval.rating = 0.0;
399         }
400
401         //depends
402         if (details["depends"].isArray()) {
403                 //TODO
404         }
405
406         //softdepends
407         if (details["softdep"].isArray()) {
408                 //TODO
409         }
410
411         //screenshot url
412         if (details["screenshot_url"].asString().size()) {
413                 retval.screenshot_url = details["screenshot_url"].asString();
414         }
415
416         return retval;
417 }