Fix modstore/favourites hang by adding asynchronous lua job support
[oweals/minetest.git] / builtin / modstore.lua
1 --Minetest
2 --Copyright (C) 2013 sapier
3 --
4 --This program is free software; you can redistribute it and/or modify
5 --it under the terms of the GNU Lesser General Public License as published by
6 --the Free Software Foundation; either version 2.1 of the License, or
7 --(at your option) any later version.
8 --
9 --This program is distributed in the hope that it will be useful,
10 --but WITHOUT ANY WARRANTY; without even the implied warranty of
11 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 --GNU Lesser General Public License for more details.
13 --
14 --You should have received a copy of the GNU Lesser General Public License along
15 --with this program; if not, write to the Free Software Foundation, Inc.,
16 --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 --------------------------------------------------------------------------------
19
20 --modstore implementation
21 modstore = {}
22
23 --------------------------------------------------------------------------------
24 function modstore.init()
25         modstore.tabnames = {}
26
27         table.insert(modstore.tabnames,"dialog_modstore_unsorted")
28         table.insert(modstore.tabnames,"dialog_modstore_search")
29
30         modstore.modsperpage = 5
31
32         modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" ..
33                                                 DIR_DELIM .. "pack" .. DIR_DELIM
34
35         modstore.lastmodtitle = ""
36
37         modstore.current_list = nil
38 end
39
40 --------------------------------------------------------------------------------
41 function modstore.nametoindex(name)
42
43         for i=1,#modstore.tabnames,1 do
44                 if modstore.tabnames[i] == name then
45                         return i
46                 end
47         end
48
49         return 1
50 end
51
52 --------------------------------------------------------------------------------
53 function modstore.gettab(tabname)
54         local retval = ""
55
56         local is_modstore_tab = false
57
58         if tabname == "dialog_modstore_unsorted" then
59                 retval = modstore.getmodlist(modstore.modlist_unsorted)
60                 is_modstore_tab = true
61         end
62
63         if tabname == "dialog_modstore_search" then
64                 retval = modstore.getsearchpage()
65                 is_modstore_tab = true
66         end
67
68         if is_modstore_tab then
69                 return modstore.tabheader(tabname) .. retval
70         end
71
72         if tabname == "modstore_mod_installed" then
73                 return "size[6,2]label[0.25,0.25;Mod(s): " .. modstore.lastmodtitle ..
74                                 " installed successfully]" ..
75                                 "button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]"
76         end
77
78         if tabname == "modstore_downloading" then
79                 return "size[6,2]label[0.25,0.25;Dowloading " .. modstore.lastmodtitle ..
80                                 " please wait]"
81         end
82
83         return ""
84 end
85
86 --------------------------------------------------------------------------------
87 function modstore.tabheader(tabname)
88         local retval  = "size[12,9.25]"
89         retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" ..
90                                 "Unsorted,Search;" ..
91                                 modstore.nametoindex(tabname) .. ";true;false]"
92
93         return retval
94 end
95
96 --------------------------------------------------------------------------------
97 function modstore.handle_buttons(current_tab,fields)
98
99         if fields["modstore_tab"] then
100                 local index = tonumber(fields["modstore_tab"])
101
102                 if index > 0 and
103                         index <= #modstore.tabnames then
104                         return {
105                                         current_tab = modstore.tabnames[index],
106                                         is_dialog = true,
107                                         show_buttons = false
108                         }
109                 end
110
111                 modstore.modlist_page = 0
112         end
113
114         if fields["btn_modstore_page_up"] then
115                 if modstore.current_list ~= nil and modstore.current_list.page > 0 then
116                         modstore.current_list.page = modstore.current_list.page - 1
117                 end
118         end
119
120         if fields["btn_modstore_page_down"] then
121                 if modstore.current_list ~= nil and
122                         modstore.current_list.page <modstore.current_list.pagecount-1 then
123                         modstore.current_list.page = modstore.current_list.page +1
124                 end
125         end
126
127         if fields["btn_hidden_close_download"] then
128                 return {
129                                         current_tab = "modstore_mod_installed",
130                                         is_dialog = true,
131                                         show_buttons = false
132                         }
133         end
134
135         if fields["btn_confirm_mod_successfull"] then
136                 modstore.lastmodtitle = ""
137                 return {
138                                         current_tab = modstore.tabnames[1],
139                                         is_dialog = true,
140                                         show_buttons = false
141                         }
142         end
143
144         for i=1, modstore.modsperpage, 1 do
145                 local installbtn = "btn_install_mod_" .. i
146
147                 if fields[installbtn] then
148                         local modlistentry =
149                                 modstore.current_list.page * modstore.modsperpage + i
150
151                         if modstore.modlist_unsorted.data[modlistentry] ~= nil and
152                                 modstore.modlist_unsorted.data[modlistentry].details ~= nil then
153
154                                 local moddetails = modstore.modlist_unsorted.data[modlistentry].details
155
156                                 if modstore.lastmodtitle ~= "" then
157                                         modstore.lastmodtitle = modstore.lastmodtitle .. ", "
158                                 end
159
160                                 modstore.lastmodtitle = modstore.lastmodtitle .. moddetails.title
161
162                                 engine.handle_async(
163                                         function(param)
164                                                 local fullurl = engine.setting_get("modstore_download_url") ..
165                                                                                 param.moddetails.download_url
166
167                                                 if engine.download_file(fullurl,param.filename) then
168                                                         return {
169                                                                 moddetails = param.moddetails,
170                                                                 filename = param.filename,
171                                                                 successfull = true
172                                                         }
173                                                 else
174                                                         return {
175                                                                 modtitle = param.title,
176                                                                 successfull = false
177                                                         }
178                                                 end
179                                         end,
180                                         {
181                                                 moddetails = moddetails,
182                                                 filename = os.tempfolder() .. ".zip"
183                                         },
184                                         function(result)
185                                                 if result.successfull then
186                                                         modmgr.installmod(result.filename,result.moddetails.basename)
187                                                         os.remove(result.filename)
188                                                 else
189                                                         gamedata.errormessage = "Failed to download " .. result.moddetails.title
190                                                 end
191
192                                                 engine.button_handler({btn_hidden_close_download=true})
193                                         end
194                                 )
195
196                                 return {
197                                         current_tab = "modstore_downloading",
198                                         is_dialog = true,
199                                         show_buttons = false,
200                                         ignore_menu_quit = true
201                                 }
202
203                         else
204                                 gamedata.errormessage =
205                                         "Internal modstore error please leave modstore and reopen! (Sorry)"
206                         end
207                 end
208         end
209 end
210
211 --------------------------------------------------------------------------------
212 function modstore.update_modlist()
213         modstore.modlist_unsorted = {}
214         modstore.modlist_unsorted.data = {}
215         modstore.modlist_unsorted.pagecount = 1
216         modstore.modlist_unsorted.page = 0
217
218         engine.handle_async(
219                 function(param)
220                         return engine.get_modstore_list()
221                 end,
222                 nil,
223                 function(result)
224                         if result ~= nil then
225                                 modstore.modlist_unsorted = {}
226                                 modstore.modlist_unsorted.data = result
227
228                                 if modstore.modlist_unsorted.data ~= nil then
229                                         modstore.modlist_unsorted.pagecount =
230                                                 math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
231                                 else
232                                         modstore.modlist_unsorted.data = {}
233                                         modstore.modlist_unsorted.pagecount = 1
234                                 end
235                                 modstore.modlist_unsorted.page = 0
236                                 modstore.fetchdetails()
237                                 engine.event_handler("Refresh")
238                         end
239                 end
240         )
241 end
242
243 --------------------------------------------------------------------------------
244 function modstore.fetchdetails()
245
246         for i=1,#modstore.modlist_unsorted.data,1 do
247                 engine.handle_async(
248                 function(param)
249                         param.details = engine.get_modstore_details(tostring(param.modid))
250                         return param
251                 end,
252                 {
253                         modid=modstore.modlist_unsorted.data[i].id,
254                         listindex=i
255                 },
256                 function(result)
257                         if result ~= nil and
258                                 modstore.modlist_unsorted ~= nil
259                                 and modstore.modlist_unsorted.data ~= nil and
260                                 modstore.modlist_unsorted.data[result.listindex] ~= nil and
261                                 modstore.modlist_unsorted.data[result.listindex].id ~= nil then
262
263                                 modstore.modlist_unsorted.data[result.listindex].details = result.details
264                                 engine.event_handler("Refresh")
265                         end
266                 end
267                 )
268         end
269 end
270 --------------------------------------------------------------------------------
271
272 --------------------------------------------------------------------------------
273 function modstore.getmodlist(list)
274         local retval = ""
275         retval = retval .. "label[10,-0.4;" .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
276
277         retval = retval .. "button[11.6,-0.1;0.5,0.5;btn_modstore_page_up;^]"
278         retval = retval .. "box[11.6,0.35;0.28,8.6;#000000]"
279         local scrollbarpos = 0.35 + (8.1/(list.pagecount-1)) * list.page
280         retval = retval .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
281         retval = retval .. "button[11.6,9.0;0.5,0.5;btn_modstore_page_down;v]"
282
283
284         if #list.data < (list.page * modstore.modsperpage) then
285                 return retval
286         end
287
288         local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
289
290         if (endmod > #list.data) then
291                 endmod = #list.data
292         end
293
294         for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
295                 --getmoddetails
296                 local details = list.data[i].details
297
298 --              if details == nil then
299 --                      details = modstore.get_details(list.data[i].id)
300 --              end
301
302                 if details == nil then
303                         details = {}
304                         details.title = list.data[i].title
305                         details.author = ""
306                         details.rating = -1
307                         details.description = ""
308                 end
309
310                 if details ~= nil then
311                         local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
312
313                         retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;#FFFFFF]"
314
315                         if details.basename then
316                                 --screenshot
317                                 if details.screenshot_url ~= nil and
318                                         details.screenshot_url ~= "" then
319                                         if list.data[i].texturename == nil then
320                                                 local fullurl = engine.setting_get("modstore_download_url") ..
321                                                                         details.screenshot_url
322                                                 local filename = os.tempfolder() .. "_MID_" .. list.data[i].id
323                                                 list.data[i].texturename = "in progress"
324                                                 engine.handle_async(
325                                                         function(param)
326                                                                 param.successfull = engine.download_file(param.fullurl,param.filename)
327                                                                 return param
328                                                         end,
329                                                         {
330                                                                 fullurl = fullurl,
331                                                                 filename = filename,
332                                                                 listindex = i,
333                                                                 modid = list.data[i].id
334                                                         },
335                                                         function(result)
336                                                                 if modstore.modlist_unsorted and
337                                                                         modstore.modlist_unsorted.data and
338                                                                         #modstore.modlist_unsorted.data >= result.listindex and
339                                                                         modstore.modlist_unsorted.data[result.listindex].id == result.modid then
340                                                                         if result.successfull then
341                                                                                 modstore.modlist_unsorted.data[result.listindex].texturename = result.filename
342                                                                         else
343                                                                                 modstore.modlist_unsorted.data[result.listindex].texturename = modstore.basetexturedir .. "no_screenshot.png"
344                                                                         end
345                                                                         engine.event_handler("Refresh")
346                                                                 end
347                                                         end
348                                                 )
349                                         end
350                                 else
351                                         if list.data[i].texturename == nil then
352                                                 list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png"
353                                         end
354                                 end
355
356                                 if list.data[i].texturename ~= nil and
357                                         list.data[i].texturename ~= "in progress" then
358                                         retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" ..
359                                                 engine.formspec_escape(list.data[i].texturename) .. "]"
360                                 end
361                         end
362
363                         --title + author
364                         retval = retval .."label[2.75," .. screenshot_ypos .. ";" ..
365                                 engine.formspec_escape(details.title) .. " (" .. details.author .. ")]"
366
367                         --description
368                         local descriptiony = screenshot_ypos + 0.5
369                         retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
370                                 engine.formspec_escape(details.description) .. ";]"
371                         --rating
372                         local ratingy = screenshot_ypos + 0.6
373                         retval = retval .."label[9.1," .. ratingy .. ";" ..
374                                                         fgettext("Rating") .. ":]"
375                         retval = retval .. "label[11.1," .. ratingy .. ";" .. details.rating .."]"
376
377                         if details.basename then
378                                 --install button
379                                 local buttony = screenshot_ypos + 1.2
380                                 local buttonnumber = (i - (list.page * modstore.modsperpage))
381                                 retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. buttonnumber .. ";"
382
383                                 if modmgr.mod_exists(details.basename) then
384                                         retval = retval .. fgettext("re-Install") .."]"
385                                 else
386                                         retval = retval .. fgettext("Install") .."]"
387                                 end
388                         end
389                 end
390         end
391
392         modstore.current_list = list
393
394         return retval
395 end
396
397 --------------------------------------------------------------------------------
398 function modstore.getsearchpage()
399         local retval = ""
400
401         --TODO implement search!
402
403         return retval;
404 end
405