]> git.lizzy.rs Git - minetest.git/blob - builtin/mainmenu/dlg_contentstore.lua
Add updating to online content browser
[minetest.git] / builtin / mainmenu / dlg_contentstore.lua
1 --Minetest
2 --Copyright (C) 2018 rubenwardy
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 local store = {}
19 local package_dialog = {}
20
21 local search_string = ""
22 local cur_page = 1
23 local num_per_page = 5
24 local filter_type = 1
25 local filter_types_titles = {
26         fgettext("All packages"),
27         fgettext("Games"),
28         fgettext("Mods"),
29         fgettext("Texture packs"),
30 }
31
32 local filter_types_type = {
33         nil,
34         "game",
35         "mod",
36         "txp",
37 }
38
39
40
41
42 local function download_package(param)
43         if core.download_file(param.package.url, param.filename) then
44                 return {
45                         package = param.package,
46                         filename = param.filename,
47                         successful = true,
48                 }
49         else
50                 core.log("error", "downloading " .. dump(param.package.url) .. " failed")
51                 return {
52                         package = param.package,
53                         successful = false,
54                 }
55         end
56 end
57
58 local function start_install(calling_dialog, package)
59         local params = {
60                 package = package,
61                 filename = os.tempfolder() .. "_MODNAME_" .. package.name .. ".zip",
62         }
63
64         local function callback(result)
65                 if result.successful then
66                         local path, msg = pkgmgr.install(result.package.type,
67                                         result.filename, result.package.name,
68                                         result.package.path)
69                         if not path then
70                                 gamedata.errormessage = msg
71                         else
72                                 local conf_path
73                                 local name_is_title = false
74                                 if result.package.type == "mod" then
75                                         conf_path = path .. DIR_DELIM .. "mod.conf"
76                                 elseif result.package.type == "game" then
77                                         conf_path = path .. DIR_DELIM .. "game.conf"
78                                         name_is_title = true
79                                 elseif result.package.type == "txp" then
80                                         conf_path = path .. DIR_DELIM .. "texture_pack.conf"
81                                 end
82
83                                 if conf_path then
84                                         local conf = Settings(conf_path)
85                                         local function set_def(key, value)
86                                                 if conf:get(key) == nil then
87                                                         conf:set(key, value)
88                                                 end
89                                         end
90                                         if name_is_title then
91                                                 set_def("name",    result.package.title)
92                                         else
93                                                 set_def("title",   result.package.title)
94                                                 set_def("name",    result.package.name)
95                                         end
96                                         set_def("description", result.package.short_description)
97                                         set_def("author",      result.package.author)
98                                         conf:set("release",    result.package.release)
99                                         conf:write()
100                                 end
101                         end
102                         os.remove(result.filename)
103                 else
104                         gamedata.errormessage = fgettext("Failed to download $1", package.name)
105                 end
106
107                 if gamedata.errormessage == nil then
108                         core.button_handler({btn_hidden_close_download=result})
109                 else
110                         core.button_handler({btn_hidden_close_download={successful=false}})
111                 end
112         end
113
114         if not core.handle_async(download_package, params, callback) then
115                 core.log("error", "ERROR: async event failed")
116                 gamedata.errormessage = fgettext("Failed to download $1", package.name)
117         end
118
119         local new_dlg = dialog_create("store_downloading",
120                 function(data)
121                         return "size[7,2]label[0.25,0.75;" ..
122                                 fgettext("Downloading and installing $1, please wait...", data.title) .. "]"
123                 end,
124                 function(this,fields)
125                         if fields["btn_hidden_close_download"] ~= nil then
126                                 this:delete()
127                                 return true
128                         end
129
130                         return false
131                 end,
132                 nil)
133
134         new_dlg:set_parent(calling_dialog)
135         new_dlg.data.title = package.title
136         calling_dialog:hide()
137         new_dlg:show()
138 end
139
140
141 function package_dialog.get_formspec()
142         local package = package_dialog.package
143
144         store.update_paths()
145
146         local formspec = {
147                 "size[9,4;true]",
148                 "label[2.5,0.2;", core.formspec_escape(package.title), "]",
149                 "textarea[0.2,1;9,3;;;", core.formspec_escape(package.short_description), "]",
150                 "button[0,0;2,1;back;", fgettext("Back"), "]",
151         }
152
153         if not package.path then
154                 formspec[#formspec + 1] = "button[7,0;2,1;install;"
155                 formspec[#formspec + 1] = fgettext("Install")
156                 formspec[#formspec + 1] = "]"
157         elseif package.installed_release < package.release then
158                 formspec[#formspec + 1] = "button[7,0;2,1;install;"
159                 formspec[#formspec + 1] = fgettext("Update")
160                 formspec[#formspec + 1] = "]"
161                 formspec[#formspec + 1] = "button[7,1;2,1;uninstall;"
162                 formspec[#formspec + 1] = fgettext("Uninstall")
163                 formspec[#formspec + 1] = "]"
164         else
165                 formspec[#formspec + 1] = "button[7,0;2,1;uninstall;"
166                 formspec[#formspec + 1] = fgettext("Uninstall")
167                 formspec[#formspec + 1] = "]"
168         end
169
170         -- TODO: screenshots
171
172         return table.concat(formspec, "")
173 end
174
175 function package_dialog.handle_submit(this, fields, tabname, tabdata)
176         if fields.back then
177                 this:delete()
178                 return true
179         end
180
181         if fields.install then
182                 start_install(this, package_dialog.package)
183                 return true
184         end
185
186         if fields.uninstall then
187                 local dlg_delmod = create_delete_content_dlg(package_dialog.package)
188                 dlg_delmod:set_parent(this)
189                 this:hide()
190                 dlg_delmod:show()
191                 return true
192         end
193
194         return false
195 end
196
197 function package_dialog.create(package)
198         package_dialog.package = package
199         return dialog_create("package_view",
200                 package_dialog.get_formspec,
201                 package_dialog.handle_submit,
202                 nil)
203 end
204
205 function store.load()
206         store.packages_full = core.get_package_list()
207         store.packages = store.packages_full
208         store.loaded = true
209 end
210
211 function store.update_paths()
212         local mod_hash = {}
213         pkgmgr.refresh_globals()
214         for _, mod in pairs(pkgmgr.global_mods:get_list()) do
215                 mod_hash[mod.name] = mod
216         end
217
218         local game_hash = {}
219         pkgmgr.update_gamelist()
220         for _, game in pairs(pkgmgr.games) do
221                 game_hash[game.id] = game
222         end
223
224         local txp_hash = {}
225         for _, txp in pairs(pkgmgr.get_texture_packs()) do
226                 txp_hash[txp.name] = txp
227         end
228
229         for _, package in pairs(store.packages_full) do
230                 local content
231                 if package.type == "mod" then
232                         content = mod_hash[package.name]
233                 elseif package.type == "game" then
234                         content = game_hash[package.name]
235                 elseif package.type == "txp" then
236                         content = txp_hash[package.name]
237                 end
238
239                 if content and content.author == package.author then
240                         package.path = content.path
241                         package.installed_release = content.release
242                 else
243                         package.path = nil
244                 end
245         end
246 end
247
248 function store.filter_packages(query)
249         if query == "" and filter_type == 1 then
250                 store.packages = store.packages_full
251                 return
252         end
253
254         local keywords = {}
255         for word in query:lower():gmatch("%S+") do
256                 table.insert(keywords, word)
257         end
258
259         local function matches_keywords(package, keywords)
260                 for k = 1, #keywords do
261                         local keyword = keywords[k]
262
263                         if string.find(package.name:lower(), keyword, 1, true) or
264                                         string.find(package.title:lower(), keyword, 1, true) or
265                                         string.find(package.author:lower(), keyword, 1, true) or
266                                         string.find(package.short_description:lower(), keyword, 1, true) then
267                                 return true
268                         end
269                 end
270
271                 return false
272         end
273
274         store.packages = {}
275         for _, package in pairs(store.packages_full) do
276                 if (query == "" or matches_keywords(package, keywords)) and
277                                 (filter_type == 1 or package.type == filter_types_type[filter_type]) then
278                         store.packages[#store.packages + 1] = package
279                 end
280         end
281
282 end
283
284 function store.get_formspec()
285         assert(store.loaded)
286
287         store.update_paths()
288
289         local pages = math.ceil(#store.packages / num_per_page)
290         if cur_page > pages then
291                 cur_page = 1
292         end
293
294         local formspec = {
295                 "size[12,6.5;true]",
296                 "field[0.3,0.1;10.2,1;search_string;;", core.formspec_escape(search_string), "]",
297                 "field_close_on_enter[search_string;false]",
298                 "button[10.2,-0.2;2,1;search;", fgettext("Search"), "]",
299                 "dropdown[0,1;2.4;type;",
300                 table.concat(filter_types_titles, ","),
301                 ";",
302                 filter_type,
303                 "]",
304                 -- "textlist[0,1;2.4,5.6;a;",
305                 -- table.concat(taglist, ","),
306                 -- "]"
307         }
308
309         local start_idx = (cur_page - 1) * num_per_page + 1
310         for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
311                 local package = store.packages[i]
312                 formspec[#formspec + 1] = "container[3,"
313                 formspec[#formspec + 1] = i - start_idx + 1
314                 formspec[#formspec + 1] = "]"
315
316                 -- image
317                 formspec[#formspec + 1] = "image[-0.4,0;1.5,1;"
318                 formspec[#formspec + 1] = defaulttexturedir
319                 formspec[#formspec + 1] = "no_screenshot.png"
320                 formspec[#formspec + 1] = "]"
321
322                 -- title
323                 formspec[#formspec + 1] = "label[1,-0.1;"
324                 formspec[#formspec + 1] = core.formspec_escape(package.title ..
325                                 " by " .. package.author)
326                 formspec[#formspec + 1] = "]"
327
328                 -- description
329                 formspec[#formspec + 1] = "textarea[1.25,0.3;5,1;;;"
330                 formspec[#formspec + 1] = core.formspec_escape(package.short_description)
331                 formspec[#formspec + 1] = "]"
332
333                 -- buttons
334                 if not package.path then
335                         formspec[#formspec + 1] = "button[6,0;1.5,1;install_"
336                         formspec[#formspec + 1] = tostring(i)
337                         formspec[#formspec + 1] = ";"
338                         formspec[#formspec + 1] = fgettext("Install")
339                         formspec[#formspec + 1] = "]"
340                 elseif package.installed_release < package.release then
341                         formspec[#formspec + 1] = "button[6,0;1.5,1;install_"
342                         formspec[#formspec + 1] = tostring(i)
343                         formspec[#formspec + 1] = ";"
344                         formspec[#formspec + 1] = fgettext("Update")
345                         formspec[#formspec + 1] = "]"
346                 else
347                         formspec[#formspec + 1] = "button[6,0;1.5,1;uninstall_"
348                         formspec[#formspec + 1] = tostring(i)
349                         formspec[#formspec + 1] = ";"
350                         formspec[#formspec + 1] = fgettext("Uninstall")
351                         formspec[#formspec + 1] = "]"
352                 end
353                 formspec[#formspec + 1] = "button[7.5,0;1.5,1;view_"
354                 formspec[#formspec + 1] = tostring(i)
355                 formspec[#formspec + 1] = ";"
356                 formspec[#formspec + 1] = fgettext("View")
357                 formspec[#formspec + 1] = "]"
358
359                 formspec[#formspec + 1] = "container_end[]"
360         end
361
362         formspec[#formspec + 1] = "container[0,"
363         formspec[#formspec + 1] = num_per_page + 1
364         formspec[#formspec + 1] = "]"
365         formspec[#formspec + 1] = "button[2.6,0;3,1;back;"
366         formspec[#formspec + 1] = fgettext("Back to Main Menu")
367         formspec[#formspec + 1] = "]"
368         formspec[#formspec + 1] = "button[7,0;1,1;pstart;<<]"
369         formspec[#formspec + 1] = "button[8,0;1,1;pback;<]"
370         formspec[#formspec + 1] = "label[9.2,0.2;"
371         formspec[#formspec + 1] = tonumber(cur_page)
372         formspec[#formspec + 1] = " / "
373         formspec[#formspec + 1] = tonumber(pages)
374         formspec[#formspec + 1] = "]"
375         formspec[#formspec + 1] = "button[10,0;1,1;pnext;>]"
376         formspec[#formspec + 1] = "button[11,0;1,1;pend;>>]"
377         formspec[#formspec + 1] = "container_end[]"
378
379         formspec[#formspec + 1] = "]"
380         return table.concat(formspec, "")
381 end
382
383 function store.handle_submit(this, fields, tabname, tabdata)
384         if fields.search or fields.key_enter_field == "search_string" then
385                 search_string = fields.search_string:trim()
386                 cur_page = 1
387                 store.filter_packages(search_string)
388                 core.update_formspec(store.get_formspec())
389                 return true
390         end
391
392         if fields.back then
393                 this:delete()
394                 return true
395         end
396
397         if fields.pstart then
398                 cur_page = 1
399                 core.update_formspec(store.get_formspec())
400                 return true
401         end
402
403         if fields.pend then
404                 cur_page = math.ceil(#store.packages / num_per_page)
405                 core.update_formspec(store.get_formspec())
406                 return true
407         end
408
409         if fields.pnext then
410                 cur_page = cur_page + 1
411                 local pages = math.ceil(#store.packages / num_per_page)
412                 if cur_page > pages then
413                         cur_page = 1
414                 end
415                 core.update_formspec(store.get_formspec())
416                 return true
417         end
418
419         if fields.pback then
420                 if cur_page == 1 then
421                         local pages = math.ceil(#store.packages / num_per_page)
422                         cur_page = pages
423                 else
424                         cur_page = cur_page - 1
425                 end
426                 core.update_formspec(store.get_formspec())
427                 return true
428         end
429
430         if fields.type then
431                 local new_type = table.indexof(filter_types_titles, fields.type)
432                 if new_type ~= filter_type then
433                         filter_type = new_type
434                         store.filter_packages(search_string)
435                         return true
436                 end
437         end
438
439         local start_idx = (cur_page - 1) * num_per_page + 1
440         assert(start_idx ~= nil)
441         for i=start_idx, math.min(#store.packages, start_idx+num_per_page-1) do
442                 local package = store.packages[i]
443                 assert(package)
444
445                 if fields["install_" .. i] then
446                         start_install(this, package)
447                         return true
448                 end
449
450                 if fields["uninstall_" .. i] then
451                         local dlg_delmod = create_delete_content_dlg(package)
452                         dlg_delmod:set_parent(this)
453                         this:hide()
454                         dlg_delmod:show()
455                         return true
456                 end
457
458                 if fields["view_" .. i] then
459                         local dlg = package_dialog.create(package)
460                         dlg:set_parent(this)
461                         this:hide()
462                         dlg:show()
463                         return true
464                 end
465         end
466
467         return false
468 end
469
470 function create_store_dlg(type)
471         if not store.loaded then
472                 store.load()
473         end
474
475         search_string = ""
476         cur_page = 1
477         store.filter_packages(search_string)
478
479         return dialog_create("store",
480                         store.get_formspec,
481                         store.handle_submit,
482                         nil)
483 end