SAPI: Throw runtime error instead of if l_get_mapgen_object called in incorrect thread
[oweals/minetest.git] / builtin / mainmenu / modmgr.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 i=1, #mods, 1 do
23                 if mods[i]:sub(1,1) ~= "." then
24                         local toadd = {}
25                         local modpackfile = nil
26
27                         toadd.name = mods[i]
28                         toadd.path = path .. DIR_DELIM .. mods[i] .. DIR_DELIM
29                         if modpack ~= nil and
30                                 modpack ~= "" then
31                                 toadd.modpack = modpack
32                         else
33                                 local filename = path .. DIR_DELIM .. mods[i] .. DIR_DELIM .. "modpack.txt"
34                                 local error = nil
35                                 modpackfile,error = io.open(filename,"r")
36                         end
37
38                         if modpackfile ~= nil then
39                                 modpackfile:close()
40                                 toadd.is_modpack = true
41                                 table.insert(retval,toadd)
42                                 get_mods(path .. DIR_DELIM .. mods[i],retval,mods[i])
43                         else
44                                 table.insert(retval,toadd)
45                         end
46                 end
47         end
48 end
49
50 --modmanager implementation
51 modmgr = {}
52
53 --------------------------------------------------------------------------------
54 function modmgr.extract(modfile)
55         if modfile.type == "zip" then
56                 local tempfolder = os.tempfolder()
57
58                 if tempfolder ~= nil and
59                         tempfolder ~= "" then
60                         core.create_dir(tempfolder)
61                         if core.extract_zip(modfile.name,tempfolder) then
62                                 return tempfolder
63                         end
64                 end
65         end
66         return nil
67 end
68
69 -------------------------------------------------------------------------------
70 function modmgr.getbasefolder(temppath)
71
72         if temppath == nil then
73                 return {
74                 type = "invalid",
75                 path = ""
76                 }
77         end
78
79         local testfile = io.open(temppath .. DIR_DELIM .. "init.lua","r")
80         if testfile ~= nil then
81                 testfile:close()
82                 return {
83                                 type="mod",
84                                 path=temppath
85                                 }
86         end
87
88         testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
89         if testfile ~= nil then
90                 testfile:close()
91                 return {
92                                 type="modpack",
93                                 path=temppath
94                                 }
95         end
96
97         local subdirs = core.get_dir_list(temppath, true)
98
99         --only single mod or modpack allowed
100         if #subdirs ~= 1 then
101                 return {
102                         type = "invalid",
103                         path = ""
104                         }
105         end
106
107         testfile =
108         io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
109         if testfile ~= nil then
110                 testfile:close()
111                 return {
112                         type="mod",
113                         path= temppath .. DIR_DELIM .. subdirs[1]
114                         }
115         end
116
117         testfile =
118         io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
119         if testfile ~= nil then
120                 testfile:close()
121                 return {
122                         type="modpack",
123                         path=temppath ..  DIR_DELIM .. subdirs[1]
124                         }
125         end
126
127         return {
128                 type = "invalid",
129                 path = ""
130                 }
131 end
132
133 --------------------------------------------------------------------------------
134 function modmgr.isValidModname(modpath)
135         if modpath:find("-") ~= nil then
136                 return false
137         end
138
139         return true
140 end
141
142 --------------------------------------------------------------------------------
143 function modmgr.parse_register_line(line)
144         local pos1 = line:find("\"")
145         local pos2 = nil
146         if pos1 ~= nil then
147                 pos2 = line:find("\"",pos1+1)
148         end
149
150         if pos1 ~= nil and pos2 ~= nil then
151                 local item = line:sub(pos1+1,pos2-1)
152
153                 if item ~= nil and
154                         item ~= "" then
155                         local pos3 = item:find(":")
156
157                         if pos3 ~= nil then
158                                 local retval = item:sub(1,pos3-1)
159                                 if retval ~= nil and
160                                         retval ~= "" then
161                                         return retval
162                                 end
163                         end
164                 end
165         end
166         return nil
167 end
168
169 --------------------------------------------------------------------------------
170 function modmgr.parse_dofile_line(modpath,line)
171         local pos1 = line:find("\"")
172         local pos2 = nil
173         if pos1 ~= nil then
174                 pos2 = line:find("\"",pos1+1)
175         end
176
177         if pos1 ~= nil and pos2 ~= nil then
178                 local filename = line:sub(pos1+1,pos2-1)
179
180                 if filename ~= nil and
181                         filename ~= "" and
182                         filename:find(".lua") then
183                         return modmgr.identify_modname(modpath,filename)
184                 end
185         end
186         return nil
187 end
188
189 --------------------------------------------------------------------------------
190 function modmgr.identify_modname(modpath,filename)
191         local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
192         if testfile ~= nil then
193                 local line = testfile:read()
194
195                 while line~= nil do
196                         local modname = nil
197
198                         if line:find("minetest.register_tool") then
199                                 modname = modmgr.parse_register_line(line)
200                         end
201
202                         if line:find("minetest.register_craftitem") then
203                                 modname = modmgr.parse_register_line(line)
204                         end
205
206
207                         if line:find("minetest.register_node") then
208                                 modname = modmgr.parse_register_line(line)
209                         end
210
211                         if line:find("dofile") then
212                                 modname = modmgr.parse_dofile_line(modpath,line)
213                         end
214
215                         if modname ~= nil then
216                                 testfile:close()
217                                 return modname
218                         end
219
220                         line = testfile:read()
221                 end
222                 testfile:close()
223         end
224
225         return nil
226 end
227 --------------------------------------------------------------------------------
228 function modmgr.render_modlist(render_list)
229         local retval = ""
230
231         if render_list == nil then
232                 if modmgr.global_mods == nil then
233                         modmgr.refresh_globals()
234                 end
235                 render_list = modmgr.global_mods
236         end
237
238         local list = render_list:get_list()
239         local last_modpack = nil
240
241         for i,v in ipairs(list) do
242                 if retval ~= "" then
243                         retval = retval ..","
244                 end
245
246                 local color = ""
247
248                 if v.is_modpack then
249                         local rawlist = render_list:get_raw_list()
250
251                         local all_enabled = true
252                         for j=1,#rawlist,1 do
253                                 if rawlist[j].modpack == list[i].name and
254                                         rawlist[j].enabled ~= true then
255                                                 all_enabled = false
256                                                 break
257                                 end
258                         end
259
260                         if all_enabled == false then
261                                 color = mt_color_grey
262                         else
263                                 color = mt_color_dark_green
264                         end
265                 end
266
267                 if v.typ == "game_mod" then
268                         color = mt_color_blue
269                 else
270                         if v.enabled then
271                                 color = mt_color_green
272                         end
273                 end
274
275                 retval = retval .. color
276                 if v.modpack  ~= nil then
277                         retval = retval .. "    "
278                 end
279                 retval = retval .. v.name
280         end
281
282         return retval
283 end
284
285 --------------------------------------------------------------------------------
286 function modmgr.get_dependencies(modfolder)
287         local toadd = ""
288         if modfolder ~= nil then
289                 local filename = modfolder ..
290                                         DIR_DELIM .. "depends.txt"
291
292                 local dependencyfile = io.open(filename,"r")
293
294                 if dependencyfile then
295                         local dependency = dependencyfile:read("*l")
296                         while dependency do
297                                 if toadd ~= "" then
298                                         toadd = toadd .. ","
299                                 end
300                                 toadd = toadd .. dependency
301                                 dependency = dependencyfile:read()
302                         end
303                         dependencyfile:close()
304                 end
305         end
306
307         return toadd
308 end
309
310 --------------------------------------------------------------------------------
311 function modmgr.get_worldconfig(worldpath)
312         local filename = worldpath ..
313                                 DIR_DELIM .. "world.mt"
314
315         local worldfile = Settings(filename)
316
317         local worldconfig = {}
318         worldconfig.global_mods = {}
319         worldconfig.game_mods = {}
320
321         for key,value in pairs(worldfile:to_table()) do
322                 if key == "gameid" then
323                         worldconfig.id = value
324                 elseif key:sub(0, 9) == "load_mod_" then
325                         worldconfig.global_mods[key] = core.is_yes(value)
326                 else
327                         worldconfig[key] = value
328                 end
329         end
330
331         --read gamemods
332         local gamespec = gamemgr.find_by_gameid(worldconfig.id)
333         gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
334
335         return worldconfig
336 end
337
338 --------------------------------------------------------------------------------
339 function modmgr.installmod(modfilename,basename)
340         local modfile = modmgr.identify_filetype(modfilename)
341         local modpath = modmgr.extract(modfile)
342
343         if modpath == nil then
344                 gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
345                         fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
346                 return
347         end
348
349         local basefolder = modmgr.getbasefolder(modpath)
350
351         if basefolder.type == "modpack" then
352                 local clean_path = nil
353
354                 if basename ~= nil then
355                         clean_path = "mp_" .. basename
356                 end
357
358                 if clean_path == nil then
359                         clean_path = get_last_folder(cleanup_path(basefolder.path))
360                 end
361
362                 if clean_path ~= nil then
363                         local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
364                         if not core.copy_dir(basefolder.path,targetpath) then
365                                 gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
366                         end
367                 else
368                         gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
369                 end
370         end
371
372         if basefolder.type == "mod" then
373                 local targetfolder = basename
374
375                 if targetfolder == nil then
376                         targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
377                 end
378
379                 --if heuristic failed try to use current foldername
380                 if targetfolder == nil then
381                         targetfolder = get_last_folder(basefolder.path)
382                 end
383
384                 if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
385                         local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
386                         core.copy_dir(basefolder.path,targetpath)
387                 else
388                         gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
389                 end
390         end
391
392         core.delete_dir(modpath)
393
394         modmgr.refresh_globals()
395
396 end
397
398 --------------------------------------------------------------------------------
399 function modmgr.preparemodlist(data)
400         local retval = {}
401
402         local global_mods = {}
403         local game_mods = {}
404
405         --read global mods
406         local modpath = core.get_modpath()
407
408         if modpath ~= nil and
409                 modpath ~= "" then
410                 get_mods(modpath,global_mods)
411         end
412
413         for i=1,#global_mods,1 do
414                 global_mods[i].typ = "global_mod"
415                 table.insert(retval,global_mods[i])
416         end
417
418         --read game mods
419         local gamespec = gamemgr.find_by_gameid(data.gameid)
420         gamemgr.get_game_mods(gamespec, game_mods)
421
422         for i=1,#game_mods,1 do
423                 game_mods[i].typ = "game_mod"
424                 table.insert(retval,game_mods[i])
425         end
426
427         if data.worldpath == nil then
428                 return retval
429         end
430
431         --read world mod configuration
432         local filename = data.worldpath ..
433                                 DIR_DELIM .. "world.mt"
434
435         local worldfile = Settings(filename)
436
437         for key,value in pairs(worldfile:to_table()) do
438                 if key:sub(1, 9) == "load_mod_" then
439                         key = key:sub(10)
440                         local element = nil
441                         for i=1,#retval,1 do
442                                 if retval[i].name == key and
443                                         not retval[i].is_modpack then
444                                         element = retval[i]
445                                         break
446                                 end
447                         end
448                         if element ~= nil then
449                                 element.enabled = core.is_yes(value)
450                         else
451                                 core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
452                         end
453                 end
454         end
455
456         return retval
457 end
458
459 --------------------------------------------------------------------------------
460 function modmgr.comparemod(elem1,elem2)
461         if elem1 == nil or elem2 == nil then
462                 return false
463         end
464         if elem1.name ~= elem2.name then
465                 return false
466         end
467         if elem1.is_modpack ~= elem2.is_modpack then
468                 return false
469         end
470         if elem1.typ ~= elem2.typ then
471                 return false
472         end
473         if elem1.modpack ~= elem2.modpack then
474                 return false
475         end
476
477         if elem1.path ~= elem2.path then
478                 return false
479         end
480
481         return true
482 end
483
484 --------------------------------------------------------------------------------
485 function modmgr.mod_exists(basename)
486
487         if modmgr.global_mods == nil then
488                 modmgr.refresh_globals()
489         end
490
491         if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
492                 return true
493         end
494
495         return false
496 end
497
498 --------------------------------------------------------------------------------
499 function modmgr.get_global_mod(idx)
500
501         if modmgr.global_mods == nil then
502                 return nil
503         end
504
505         if idx == nil or idx < 1 or
506                 idx > modmgr.global_mods:size() then
507                 return nil
508         end
509
510         return modmgr.global_mods:get_list()[idx]
511 end
512
513 --------------------------------------------------------------------------------
514 function modmgr.refresh_globals()
515         modmgr.global_mods = filterlist.create(
516                                         modmgr.preparemodlist, --refresh
517                                         modmgr.comparemod, --compare
518                                         function(element,uid) --uid match
519                                                 if element.name == uid then
520                                                         return true
521                                                 end
522                                         end,
523                                         nil, --filter
524                                         {}
525                                         )
526         modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
527         modmgr.global_mods:set_sortmode("alphabetic")
528 end
529
530 --------------------------------------------------------------------------------
531 function modmgr.identify_filetype(name)
532
533         if name:sub(-3):lower() == "zip" then
534                 return {
535                                 name = name,
536                                 type = "zip"
537                                 }
538         end
539
540         if name:sub(-6):lower() == "tar.gz" or
541                 name:sub(-3):lower() == "tgz"then
542                 return {
543                                 name = name,
544                                 type = "tgz"
545                                 }
546         end
547
548         if name:sub(-6):lower() == "tar.bz2" then
549                 return {
550                                 name = name,
551                                 type = "tbz"
552                                 }
553         end
554
555         if name:sub(-2):lower() == "7z" then
556                 return {
557                                 name = name,
558                                 type = "7z"
559                                 }
560         end
561
562         return {
563                 name = name,
564                 type = "ukn"
565         }
566 end