]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/mainmenu/pkgmgr.lua
Add updating to online content browser
[dragonfireclient.git] / builtin / mainmenu / pkgmgr.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 function get_mods(path,retval,modpack)
20         local mods = core.get_dir_list(path, true)
21
22         for _, name in ipairs(mods) do
23                 if name:sub(1, 1) ~= "." then
24                         local prefix = path .. DIR_DELIM .. name .. DIR_DELIM
25                         local toadd = {}
26                         retval[#retval + 1] = toadd
27
28                         local mod_conf = Settings(prefix .. "mod.conf"):to_table()
29                         if mod_conf.name then
30                                 name = mod_conf.name
31                         end
32
33                         toadd.name = name
34                         toadd.author = mod_conf.author
35                         toadd.release = tonumber(mod_conf.release or "0")
36                         toadd.path = prefix
37                         toadd.type = "mod"
38
39                         if modpack ~= nil and modpack ~= "" then
40                                 toadd.modpack = modpack
41                         else
42                                 local modpackfile = io.open(prefix .. "modpack.txt")
43                                 if modpackfile then
44                                         modpackfile:close()
45                                         toadd.type = "modpack"
46                                         toadd.is_modpack = true
47                                         get_mods(prefix, retval, name)
48                                 end
49                         end
50                 end
51         end
52 end
53
54 --modmanager implementation
55 pkgmgr = {}
56
57 function pkgmgr.get_texture_packs()
58         local txtpath = core.get_texturepath()
59         local list = core.get_dir_list(txtpath, true)
60         local retval = {}
61
62         local current_texture_path = core.settings:get("texture_path")
63
64         for _, item in ipairs(list) do
65                 if item ~= "base" then
66                         local name = item
67
68                         local path = txtpath .. DIR_DELIM .. item .. DIR_DELIM
69                         if path == current_texture_path then
70                                 name = fgettext("$1 (Enabled)", name)
71                         end
72
73                         local conf = Settings(path .. "texture_pack.conf")
74
75                         retval[#retval + 1] = {
76                                 name = item,
77                                 author = conf:get("author"),
78                                 release = tonumber(conf:get("release") or "0"),
79                                 list_name = name,
80                                 type = "txp",
81                                 path = path,
82                                 enabled = path == current_texture_path,
83                         }
84                 end
85         end
86
87         table.sort(retval, function(a, b)
88                 return a.name > b.name
89         end)
90
91         return retval
92 end
93
94 --------------------------------------------------------------------------------
95 function pkgmgr.extract(modfile)
96         if modfile.type == "zip" then
97                 local tempfolder = os.tempfolder()
98
99                 if tempfolder ~= nil and
100                         tempfolder ~= "" then
101                         core.create_dir(tempfolder)
102                         if core.extract_zip(modfile.name,tempfolder) then
103                                 return tempfolder
104                         end
105                 end
106         end
107         return nil
108 end
109
110 function pkgmgr.get_folder_type(path)
111         local testfile = io.open(path .. DIR_DELIM .. "init.lua","r")
112         if testfile ~= nil then
113                 testfile:close()
114                 return { type = "mod", path = path }
115         end
116
117         testfile = io.open(path .. DIR_DELIM .. "modpack.txt","r")
118         if testfile ~= nil then
119                 testfile:close()
120                 return { type = "modpack", path = path }
121         end
122
123         testfile = io.open(path .. DIR_DELIM .. "game.conf","r")
124         if testfile ~= nil then
125                 testfile:close()
126                 return { type = "game", path = path }
127         end
128
129         testfile = io.open(path .. DIR_DELIM .. "texture_pack.conf","r")
130         if testfile ~= nil then
131                 testfile:close()
132                 return { type = "txp", path = path }
133         end
134
135         return nil
136 end
137
138 -------------------------------------------------------------------------------
139 function pkgmgr.get_base_folder(temppath)
140         if temppath == nil then
141                 return { type = "invalid", path = "" }
142         end
143
144         local ret = pkgmgr.get_folder_type(temppath)
145         if ret then
146                 return ret
147         end
148
149         local subdirs = core.get_dir_list(temppath, true)
150         if #subdirs == 1 then
151                 ret = pkgmgr.get_folder_type(temppath .. DIR_DELIM .. subdirs[1])
152                 if ret then
153                         return ret
154                 else
155                         return { type = "invalid", path = temppath .. DIR_DELIM .. subdirs[1] }
156                 end
157         end
158
159         return nil
160 end
161
162 --------------------------------------------------------------------------------
163 function pkgmgr.isValidModname(modpath)
164         if modpath:find("-") ~= nil then
165                 return false
166         end
167
168         return true
169 end
170
171 --------------------------------------------------------------------------------
172 function pkgmgr.parse_register_line(line)
173         local pos1 = line:find("\"")
174         local pos2 = nil
175         if pos1 ~= nil then
176                 pos2 = line:find("\"",pos1+1)
177         end
178
179         if pos1 ~= nil and pos2 ~= nil then
180                 local item = line:sub(pos1+1,pos2-1)
181
182                 if item ~= nil and
183                         item ~= "" then
184                         local pos3 = item:find(":")
185
186                         if pos3 ~= nil then
187                                 local retval = item:sub(1,pos3-1)
188                                 if retval ~= nil and
189                                         retval ~= "" then
190                                         return retval
191                                 end
192                         end
193                 end
194         end
195         return nil
196 end
197
198 --------------------------------------------------------------------------------
199 function pkgmgr.parse_dofile_line(modpath,line)
200         local pos1 = line:find("\"")
201         local pos2 = nil
202         if pos1 ~= nil then
203                 pos2 = line:find("\"",pos1+1)
204         end
205
206         if pos1 ~= nil and pos2 ~= nil then
207                 local filename = line:sub(pos1+1,pos2-1)
208
209                 if filename ~= nil and
210                         filename ~= "" and
211                         filename:find(".lua") then
212                         return pkgmgr.identify_modname(modpath,filename)
213                 end
214         end
215         return nil
216 end
217
218 --------------------------------------------------------------------------------
219 function pkgmgr.identify_modname(modpath,filename)
220         local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
221         if testfile ~= nil then
222                 local line = testfile:read()
223
224                 while line~= nil do
225                         local modname = nil
226
227                         if line:find("minetest.register_tool") then
228                                 modname = pkgmgr.parse_register_line(line)
229                         end
230
231                         if line:find("minetest.register_craftitem") then
232                                 modname = pkgmgr.parse_register_line(line)
233                         end
234
235
236                         if line:find("minetest.register_node") then
237                                 modname = pkgmgr.parse_register_line(line)
238                         end
239
240                         if line:find("dofile") then
241                                 modname = pkgmgr.parse_dofile_line(modpath,line)
242                         end
243
244                         if modname ~= nil then
245                                 testfile:close()
246                                 return modname
247                         end
248
249                         line = testfile:read()
250                 end
251                 testfile:close()
252         end
253
254         return nil
255 end
256 --------------------------------------------------------------------------------
257 function pkgmgr.render_packagelist(render_list)
258         local retval = ""
259
260         if render_list == nil then
261                 if pkgmgr.global_mods == nil then
262                         pkgmgr.refresh_globals()
263                 end
264                 render_list = pkgmgr.global_mods
265         end
266
267         local list = render_list:get_list()
268         local last_modpack = nil
269         local retval = {}
270         for i, v in ipairs(list) do
271                 local color = ""
272                 if v.is_modpack then
273                         local rawlist = render_list:get_raw_list()
274                         color = mt_color_dark_green
275
276                         for j = 1, #rawlist, 1 do
277                                 if rawlist[j].modpack == list[i].name and
278                                                 rawlist[j].enabled ~= true then
279                                         -- Modpack not entirely enabled so showing as grey
280                                         color = mt_color_grey
281                                         break
282                                 end
283                         end
284                 elseif v.is_game_content or v.type == "game" then
285                         color = mt_color_blue
286                 elseif v.enabled or v.type == "txp" then
287                         color = mt_color_green
288                 end
289
290                 retval[#retval + 1] = color
291                 if v.modpack ~= nil or v.loc == "game" then
292                         retval[#retval + 1] = "1"
293                 else
294                         retval[#retval + 1] = "0"
295                 end
296                 retval[#retval + 1] = core.formspec_escape(v.list_name or v.name)
297         end
298
299         return table.concat(retval, ",")
300 end
301
302 --------------------------------------------------------------------------------
303 function pkgmgr.get_dependencies(path)
304         if path == nil then
305                 return "", ""
306         end
307
308         local info = core.get_content_info(path)
309         return table.concat(info.depends or {}, ","), table.concat(info.optional_depends or {}, ",")
310 end
311
312 --------------------------------------------------------------------------------
313 function pkgmgr.get_worldconfig(worldpath)
314         local filename = worldpath ..
315                                 DIR_DELIM .. "world.mt"
316
317         local worldfile = Settings(filename)
318
319         local worldconfig = {}
320         worldconfig.global_mods = {}
321         worldconfig.game_mods = {}
322
323         for key,value in pairs(worldfile:to_table()) do
324                 if key == "gameid" then
325                         worldconfig.id = value
326                 elseif key:sub(0, 9) == "load_mod_" then
327                         worldconfig.global_mods[key] = core.is_yes(value)
328                 else
329                         worldconfig[key] = value
330                 end
331         end
332
333         --read gamemods
334         local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
335         pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
336
337         return worldconfig
338 end
339
340 --------------------------------------------------------------------------------
341 function pkgmgr.install_dir(type, path, basename, targetpath)
342         local basefolder = pkgmgr.get_base_folder(path)
343
344         -- There's no good way to detect a texture pack, so let's just assume
345         -- it's correct for now.
346         if type == "txp" then
347                 if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
348                         return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
349                 end
350
351                 local from = basefolder and basefolder.path or path
352                 if targetpath then
353                         core.delete_dir(targetpath)
354                         core.create_dir(targetpath)
355                 else
356                         targetpath = core.get_texturepath() .. DIR_DELIM .. basename
357                 end
358                 if not core.copy_dir(from, targetpath) then
359                         return nil,
360                                 fgettext("Failed to install $1 to $2", basename, targetpath)
361                 end
362                 return targetpath, nil
363
364         elseif not basefolder then
365                 return nil, fgettext("Unable to find a valid mod or modpack")
366         end
367
368         --
369         -- Get destination
370         --
371         if basefolder.type == "modpack" then
372                 if type ~= "mod" then
373                         return nil, fgettext("Unable to install a modpack as a $1", type)
374                 end
375
376                 -- Get destination name for modpack
377                 if targetpath then
378                         core.delete_dir(targetpath)
379                         core.create_dir(targetpath)
380                 else
381                         local clean_path = nil
382                         if basename ~= nil then
383                                 clean_path = "mp_" .. basename
384                         end
385                         if not clean_path then
386                                 clean_path = get_last_folder(cleanup_path(basefolder.path))
387                         end
388                         if clean_path then
389                                 targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
390                         else
391                                 return nil,
392                                         fgettext("Install Mod: unable to find suitable foldername for modpack $1",
393                                         modfilename)
394                         end
395                 end
396         elseif basefolder.type == "mod" then
397                 if type ~= "mod" then
398                         return nil, fgettext("Unable to install a mod as a $1", type)
399                 end
400
401                 if targetpath then
402                         core.delete_dir(targetpath)
403                         core.create_dir(targetpath)
404                 else
405                         local targetfolder = basename
406                         if targetfolder == nil then
407                                 targetfolder = pkgmgr.identify_modname(basefolder.path, "init.lua")
408                         end
409
410                         -- If heuristic failed try to use current foldername
411                         if targetfolder == nil then
412                                 targetfolder = get_last_folder(basefolder.path)
413                         end
414
415                         if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
416                                 targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
417                         else
418                                 return nil, fgettext("Install Mod: unable to find real modname for: $1", modfilename)
419                         end
420                 end
421
422         elseif basefolder.type == "game" then
423                 if type ~= "game" then
424                         return nil, fgettext("Unable to install a game as a $1", type)
425                 end
426
427                 if targetpath then
428                         core.delete_dir(targetpath)
429                         core.create_dir(targetpath)
430                 else
431                         targetpath = core.get_gamepath() .. DIR_DELIM .. basename
432                 end
433         end
434
435         -- Copy it
436         if not core.copy_dir(basefolder.path, targetpath) then
437                 return nil,
438                         fgettext("Failed to install $1 to $2", basename, targetpath)
439         end
440
441         pkgmgr.refresh_globals()
442
443         return targetpath, nil
444 end
445
446 --------------------------------------------------------------------------------
447 function pkgmgr.install(type, modfilename, basename, dest)
448         local archive_info = pkgmgr.identify_filetype(modfilename)
449         local path = pkgmgr.extract(archive_info)
450
451         if path == nil then
452                 return nil,
453                         fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
454                         fgettext("Install: unsupported filetype \"$1\" or broken archive",
455                                 archive_info.type)
456         end
457
458         local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
459         core.delete_dir(path)
460         return targetpath, msg
461 end
462
463 --------------------------------------------------------------------------------
464 function pkgmgr.preparemodlist(data)
465         local retval = {}
466
467         local global_mods = {}
468         local game_mods = {}
469
470         --read global mods
471         local modpath = core.get_modpath()
472
473         if modpath ~= nil and
474                 modpath ~= "" then
475                 get_mods(modpath,global_mods)
476         end
477
478         for i=1,#global_mods,1 do
479                 global_mods[i].type = "mod"
480                 global_mods[i].loc = "global"
481                 retval[#retval + 1] = global_mods[i]
482         end
483
484         --read game mods
485         local gamespec = pkgmgr.find_by_gameid(data.gameid)
486         pkgmgr.get_game_mods(gamespec, game_mods)
487
488         if #game_mods > 0 then
489                 -- Add title
490                 retval[#retval + 1] = {
491                         type = "game",
492                         is_game_content = true,
493                         name = fgettext("Subgame Mods")
494                 }
495         end
496
497         for i=1,#game_mods,1 do
498                 game_mods[i].type = "mod"
499                 game_mods[i].loc = "game"
500                 game_mods[i].is_game_content = true
501                 retval[#retval + 1] = game_mods[i]
502         end
503
504         if data.worldpath == nil then
505                 return retval
506         end
507
508         --read world mod configuration
509         local filename = data.worldpath ..
510                                 DIR_DELIM .. "world.mt"
511
512         local worldfile = Settings(filename)
513
514         for key,value in pairs(worldfile:to_table()) do
515                 if key:sub(1, 9) == "load_mod_" then
516                         key = key:sub(10)
517                         local element = nil
518                         for i=1,#retval,1 do
519                                 if retval[i].name == key and
520                                         not retval[i].is_modpack then
521                                         element = retval[i]
522                                         break
523                                 end
524                         end
525                         if element ~= nil then
526                                 element.enabled = core.is_yes(value)
527                         else
528                                 core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
529                         end
530                 end
531         end
532
533         return retval
534 end
535
536 function pkgmgr.compare_package(a, b)
537         return a and b and a.name == b.name and a.path == b.path
538 end
539
540 --------------------------------------------------------------------------------
541 function pkgmgr.comparemod(elem1,elem2)
542         if elem1 == nil or elem2 == nil then
543                 return false
544         end
545         if elem1.name ~= elem2.name then
546                 return false
547         end
548         if elem1.is_modpack ~= elem2.is_modpack then
549                 return false
550         end
551         if elem1.type ~= elem2.type then
552                 return false
553         end
554         if elem1.modpack ~= elem2.modpack then
555                 return false
556         end
557
558         if elem1.path ~= elem2.path then
559                 return false
560         end
561
562         return true
563 end
564
565 --------------------------------------------------------------------------------
566 function pkgmgr.mod_exists(basename)
567
568         if pkgmgr.global_mods == nil then
569                 pkgmgr.refresh_globals()
570         end
571
572         if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
573                 return true
574         end
575
576         return false
577 end
578
579 --------------------------------------------------------------------------------
580 function pkgmgr.get_global_mod(idx)
581
582         if pkgmgr.global_mods == nil then
583                 return nil
584         end
585
586         if idx == nil or idx < 1 or
587                 idx > pkgmgr.global_mods:size() then
588                 return nil
589         end
590
591         return pkgmgr.global_mods:get_list()[idx]
592 end
593
594 --------------------------------------------------------------------------------
595 function pkgmgr.refresh_globals()
596         local function is_equal(element,uid) --uid match
597                 if element.name == uid then
598                         return true
599                 end
600         end
601         pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
602                         pkgmgr.comparemod, is_equal, nil, {})
603         pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
604         pkgmgr.global_mods:set_sortmode("alphabetic")
605 end
606
607 --------------------------------------------------------------------------------
608 function pkgmgr.identify_filetype(name)
609
610         if name:sub(-3):lower() == "zip" then
611                 return {
612                                 name = name,
613                                 type = "zip"
614                                 }
615         end
616
617         if name:sub(-6):lower() == "tar.gz" or
618                 name:sub(-3):lower() == "tgz"then
619                 return {
620                                 name = name,
621                                 type = "tgz"
622                                 }
623         end
624
625         if name:sub(-6):lower() == "tar.bz2" then
626                 return {
627                                 name = name,
628                                 type = "tbz"
629                                 }
630         end
631
632         if name:sub(-2):lower() == "7z" then
633                 return {
634                                 name = name,
635                                 type = "7z"
636                                 }
637         end
638
639         return {
640                 name = name,
641                 type = "ukn"
642         }
643 end
644
645
646 --------------------------------------------------------------------------------
647 function pkgmgr.find_by_gameid(gameid)
648         for i=1,#pkgmgr.games,1 do
649                 if pkgmgr.games[i].id == gameid then
650                         return pkgmgr.games[i], i
651                 end
652         end
653         return nil, nil
654 end
655
656 --------------------------------------------------------------------------------
657 function pkgmgr.get_game_mods(gamespec, retval)
658         if gamespec ~= nil and
659                 gamespec.gamemods_path ~= nil and
660                 gamespec.gamemods_path ~= "" then
661                 get_mods(gamespec.gamemods_path, retval)
662         end
663 end
664
665 --------------------------------------------------------------------------------
666 function pkgmgr.get_game_modlist(gamespec)
667         local retval = ""
668         local game_mods = {}
669         pkgmgr.get_game_mods(gamespec, game_mods)
670         for i=1,#game_mods,1 do
671                 if retval ~= "" then
672                         retval = retval..","
673                 end
674                 retval = retval .. game_mods[i].name
675         end
676         return retval
677 end
678
679 --------------------------------------------------------------------------------
680 function pkgmgr.get_game(index)
681         if index > 0 and index <= #pkgmgr.games then
682                 return pkgmgr.games[index]
683         end
684
685         return nil
686 end
687
688 --------------------------------------------------------------------------------
689 function pkgmgr.update_gamelist()
690         pkgmgr.games = core.get_games()
691 end
692
693 --------------------------------------------------------------------------------
694 function pkgmgr.gamelist()
695         local retval = ""
696         if #pkgmgr.games > 0 then
697                 retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
698
699                 for i=2,#pkgmgr.games,1 do
700                         retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
701                 end
702         end
703         return retval
704 end
705
706 --------------------------------------------------------------------------------
707 -- read initial data
708 --------------------------------------------------------------------------------
709 pkgmgr.update_gamelist()