]> git.lizzy.rs Git - dragonfireclient.git/blob - builtin/mainmenu/pkgmgr.lua
Settingtypes.txt: Various language improvements, document stable mapgens (#7801)
[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 ----------- tests whether all of the mods in the modpack are enabled -----------
313 function pkgmgr.is_modpack_entirely_enabled(data, name)
314         local rawlist = data.list:get_raw_list()
315         for j = 1, #rawlist do
316                 if rawlist[j].modpack == name and not rawlist[j].enabled then
317                         return false
318                 end
319         end
320         return true
321 end
322
323 ---------- toggles or en/disables a mod or modpack -----------------------------
324 function pkgmgr.enable_mod(this, toset)
325         local mod = this.data.list:get_list()[this.data.selected_mod]
326
327         -- game mods can't be enabled or disabled
328         if mod.is_game_content then
329                 return
330         end
331
332         -- toggle or en/disable the mod
333         if not mod.is_modpack then
334                 if toset == nil then
335                         mod.enabled = not mod.enabled
336                 else
337                         mod.enabled = toset
338                 end
339                 return
340         end
341
342         -- toggle or en/disable every mod in the modpack, interleaved unsupported
343         local list = this.data.list:get_raw_list()
344         for i = 1, #list do
345                 if list[i].modpack == mod.name then
346                         if toset == nil then
347                                 toset = not list[i].enabled
348                         end
349                         list[i].enabled = toset
350                 end
351         end
352 end
353
354 --------------------------------------------------------------------------------
355 function pkgmgr.get_worldconfig(worldpath)
356         local filename = worldpath ..
357                                 DIR_DELIM .. "world.mt"
358
359         local worldfile = Settings(filename)
360
361         local worldconfig = {}
362         worldconfig.global_mods = {}
363         worldconfig.game_mods = {}
364
365         for key,value in pairs(worldfile:to_table()) do
366                 if key == "gameid" then
367                         worldconfig.id = value
368                 elseif key:sub(0, 9) == "load_mod_" then
369                         worldconfig.global_mods[key] = core.is_yes(value)
370                 else
371                         worldconfig[key] = value
372                 end
373         end
374
375         --read gamemods
376         local gamespec = pkgmgr.find_by_gameid(worldconfig.id)
377         pkgmgr.get_game_mods(gamespec, worldconfig.game_mods)
378
379         return worldconfig
380 end
381
382 --------------------------------------------------------------------------------
383 function pkgmgr.install_dir(type, path, basename, targetpath)
384         local basefolder = pkgmgr.get_base_folder(path)
385
386         -- There's no good way to detect a texture pack, so let's just assume
387         -- it's correct for now.
388         if type == "txp" then
389                 if basefolder and basefolder.type ~= "invalid" and basefolder.type ~= "txp" then
390                         return nil, fgettext("Unable to install a $1 as a texture pack", basefolder.type)
391                 end
392
393                 local from = basefolder and basefolder.path or path
394                 if targetpath then
395                         core.delete_dir(targetpath)
396                         core.create_dir(targetpath)
397                 else
398                         targetpath = core.get_texturepath() .. DIR_DELIM .. basename
399                 end
400                 if not core.copy_dir(from, targetpath) then
401                         return nil,
402                                 fgettext("Failed to install $1 to $2", basename, targetpath)
403                 end
404                 return targetpath, nil
405
406         elseif not basefolder then
407                 return nil, fgettext("Unable to find a valid mod or modpack")
408         end
409
410         --
411         -- Get destination
412         --
413         if basefolder.type == "modpack" then
414                 if type ~= "mod" then
415                         return nil, fgettext("Unable to install a modpack as a $1", type)
416                 end
417
418                 -- Get destination name for modpack
419                 if targetpath then
420                         core.delete_dir(targetpath)
421                         core.create_dir(targetpath)
422                 else
423                         local clean_path = nil
424                         if basename ~= nil then
425                                 clean_path = "mp_" .. basename
426                         end
427                         if not clean_path then
428                                 clean_path = get_last_folder(cleanup_path(basefolder.path))
429                         end
430                         if clean_path then
431                                 targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
432                         else
433                                 return nil,
434                                         fgettext("Install Mod: Unable to find suitable folder name for modpack $1",
435                                         modfilename)
436                         end
437                 end
438         elseif basefolder.type == "mod" then
439                 if type ~= "mod" then
440                         return nil, fgettext("Unable to install a mod as a $1", type)
441                 end
442
443                 if targetpath then
444                         core.delete_dir(targetpath)
445                         core.create_dir(targetpath)
446                 else
447                         local targetfolder = basename
448                         if targetfolder == nil then
449                                 targetfolder = pkgmgr.identify_modname(basefolder.path, "init.lua")
450                         end
451
452                         -- If heuristic failed try to use current foldername
453                         if targetfolder == nil then
454                                 targetfolder = get_last_folder(basefolder.path)
455                         end
456
457                         if targetfolder ~= nil and pkgmgr.isValidModname(targetfolder) then
458                                 targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
459                         else
460                                 return nil, fgettext("Install Mod: Unable to find real mod name for: $1", modfilename)
461                         end
462                 end
463
464         elseif basefolder.type == "game" then
465                 if type ~= "game" then
466                         return nil, fgettext("Unable to install a game as a $1", type)
467                 end
468
469                 if targetpath then
470                         core.delete_dir(targetpath)
471                         core.create_dir(targetpath)
472                 else
473                         targetpath = core.get_gamepath() .. DIR_DELIM .. basename
474                 end
475         end
476
477         -- Copy it
478         if not core.copy_dir(basefolder.path, targetpath) then
479                 return nil,
480                         fgettext("Failed to install $1 to $2", basename, targetpath)
481         end
482
483         pkgmgr.refresh_globals()
484
485         return targetpath, nil
486 end
487
488 --------------------------------------------------------------------------------
489 function pkgmgr.install(type, modfilename, basename, dest)
490         local archive_info = pkgmgr.identify_filetype(modfilename)
491         local path = pkgmgr.extract(archive_info)
492
493         if path == nil then
494                 return nil,
495                         fgettext("Install: file: \"$1\"", archive_info.name) .. "\n" ..
496                         fgettext("Install: Unsupported file type \"$1\" or broken archive",
497                                 archive_info.type)
498         end
499
500         local targetpath, msg = pkgmgr.install_dir(type, path, basename, dest)
501         core.delete_dir(path)
502         return targetpath, msg
503 end
504
505 --------------------------------------------------------------------------------
506 function pkgmgr.preparemodlist(data)
507         local retval = {}
508
509         local global_mods = {}
510         local game_mods = {}
511
512         --read global mods
513         local modpath = core.get_modpath()
514
515         if modpath ~= nil and
516                 modpath ~= "" then
517                 get_mods(modpath,global_mods)
518         end
519
520         for i=1,#global_mods,1 do
521                 global_mods[i].type = "mod"
522                 global_mods[i].loc = "global"
523                 retval[#retval + 1] = global_mods[i]
524         end
525
526         --read game mods
527         local gamespec = pkgmgr.find_by_gameid(data.gameid)
528         pkgmgr.get_game_mods(gamespec, game_mods)
529
530         if #game_mods > 0 then
531                 -- Add title
532                 retval[#retval + 1] = {
533                         type = "game",
534                         is_game_content = true,
535                         name = fgettext("Subgame Mods")
536                 }
537         end
538
539         for i=1,#game_mods,1 do
540                 game_mods[i].type = "mod"
541                 game_mods[i].loc = "game"
542                 game_mods[i].is_game_content = true
543                 retval[#retval + 1] = game_mods[i]
544         end
545
546         if data.worldpath == nil then
547                 return retval
548         end
549
550         --read world mod configuration
551         local filename = data.worldpath ..
552                                 DIR_DELIM .. "world.mt"
553
554         local worldfile = Settings(filename)
555
556         for key,value in pairs(worldfile:to_table()) do
557                 if key:sub(1, 9) == "load_mod_" then
558                         key = key:sub(10)
559                         local element = nil
560                         for i=1,#retval,1 do
561                                 if retval[i].name == key and
562                                         not retval[i].is_modpack then
563                                         element = retval[i]
564                                         break
565                                 end
566                         end
567                         if element ~= nil then
568                                 element.enabled = core.is_yes(value)
569                         else
570                                 core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
571                         end
572                 end
573         end
574
575         return retval
576 end
577
578 function pkgmgr.compare_package(a, b)
579         return a and b and a.name == b.name and a.path == b.path
580 end
581
582 --------------------------------------------------------------------------------
583 function pkgmgr.comparemod(elem1,elem2)
584         if elem1 == nil or elem2 == nil then
585                 return false
586         end
587         if elem1.name ~= elem2.name then
588                 return false
589         end
590         if elem1.is_modpack ~= elem2.is_modpack then
591                 return false
592         end
593         if elem1.type ~= elem2.type then
594                 return false
595         end
596         if elem1.modpack ~= elem2.modpack then
597                 return false
598         end
599
600         if elem1.path ~= elem2.path then
601                 return false
602         end
603
604         return true
605 end
606
607 --------------------------------------------------------------------------------
608 function pkgmgr.mod_exists(basename)
609
610         if pkgmgr.global_mods == nil then
611                 pkgmgr.refresh_globals()
612         end
613
614         if pkgmgr.global_mods:raw_index_by_uid(basename) > 0 then
615                 return true
616         end
617
618         return false
619 end
620
621 --------------------------------------------------------------------------------
622 function pkgmgr.get_global_mod(idx)
623
624         if pkgmgr.global_mods == nil then
625                 return nil
626         end
627
628         if idx == nil or idx < 1 or
629                 idx > pkgmgr.global_mods:size() then
630                 return nil
631         end
632
633         return pkgmgr.global_mods:get_list()[idx]
634 end
635
636 --------------------------------------------------------------------------------
637 function pkgmgr.refresh_globals()
638         local function is_equal(element,uid) --uid match
639                 if element.name == uid then
640                         return true
641                 end
642         end
643         pkgmgr.global_mods = filterlist.create(pkgmgr.preparemodlist,
644                         pkgmgr.comparemod, is_equal, nil, {})
645         pkgmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
646         pkgmgr.global_mods:set_sortmode("alphabetic")
647 end
648
649 --------------------------------------------------------------------------------
650 function pkgmgr.identify_filetype(name)
651
652         if name:sub(-3):lower() == "zip" then
653                 return {
654                                 name = name,
655                                 type = "zip"
656                                 }
657         end
658
659         if name:sub(-6):lower() == "tar.gz" or
660                 name:sub(-3):lower() == "tgz"then
661                 return {
662                                 name = name,
663                                 type = "tgz"
664                                 }
665         end
666
667         if name:sub(-6):lower() == "tar.bz2" then
668                 return {
669                                 name = name,
670                                 type = "tbz"
671                                 }
672         end
673
674         if name:sub(-2):lower() == "7z" then
675                 return {
676                                 name = name,
677                                 type = "7z"
678                                 }
679         end
680
681         return {
682                 name = name,
683                 type = "ukn"
684         }
685 end
686
687
688 --------------------------------------------------------------------------------
689 function pkgmgr.find_by_gameid(gameid)
690         for i=1,#pkgmgr.games,1 do
691                 if pkgmgr.games[i].id == gameid then
692                         return pkgmgr.games[i], i
693                 end
694         end
695         return nil, nil
696 end
697
698 --------------------------------------------------------------------------------
699 function pkgmgr.get_game_mods(gamespec, retval)
700         if gamespec ~= nil and
701                 gamespec.gamemods_path ~= nil and
702                 gamespec.gamemods_path ~= "" then
703                 get_mods(gamespec.gamemods_path, retval)
704         end
705 end
706
707 --------------------------------------------------------------------------------
708 function pkgmgr.get_game_modlist(gamespec)
709         local retval = ""
710         local game_mods = {}
711         pkgmgr.get_game_mods(gamespec, game_mods)
712         for i=1,#game_mods,1 do
713                 if retval ~= "" then
714                         retval = retval..","
715                 end
716                 retval = retval .. game_mods[i].name
717         end
718         return retval
719 end
720
721 --------------------------------------------------------------------------------
722 function pkgmgr.get_game(index)
723         if index > 0 and index <= #pkgmgr.games then
724                 return pkgmgr.games[index]
725         end
726
727         return nil
728 end
729
730 --------------------------------------------------------------------------------
731 function pkgmgr.update_gamelist()
732         pkgmgr.games = core.get_games()
733 end
734
735 --------------------------------------------------------------------------------
736 function pkgmgr.gamelist()
737         local retval = ""
738         if #pkgmgr.games > 0 then
739                 retval = retval .. core.formspec_escape(pkgmgr.games[1].name)
740
741                 for i=2,#pkgmgr.games,1 do
742                         retval = retval .. "," .. core.formspec_escape(pkgmgr.games[i].name)
743                 end
744         end
745         return retval
746 end
747
748 --------------------------------------------------------------------------------
749 -- read initial data
750 --------------------------------------------------------------------------------
751 pkgmgr.update_gamelist()