Small tweaking (alignement - client tab)
[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_dirlist(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_dirlist(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                 else
325                         worldconfig.global_mods[key] = core.is_yes(value)
326                 end
327         end
328
329         --read gamemods
330         local gamespec = gamemgr.find_by_gameid(worldconfig.id)
331         gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
332
333         return worldconfig
334 end
335
336 --------------------------------------------------------------------------------
337 function modmgr.installmod(modfilename,basename)
338         local modfile = modmgr.identify_filetype(modfilename)
339         local modpath = modmgr.extract(modfile)
340
341         if modpath == nil then
342                 gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
343                         fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
344                 return
345         end
346
347         local basefolder = modmgr.getbasefolder(modpath)
348
349         if basefolder.type == "modpack" then
350                 local clean_path = nil
351
352                 if basename ~= nil then
353                         clean_path = "mp_" .. basename
354                 end
355
356                 if clean_path == nil then
357                         clean_path = get_last_folder(cleanup_path(basefolder.path))
358                 end
359
360                 if clean_path ~= nil then
361                         local targetpath = core.get_modpath() .. DIR_DELIM .. clean_path
362                         if not core.copy_dir(basefolder.path,targetpath) then
363                                 gamedata.errormessage = fgettext("Failed to install $1 to $2", basename, targetpath)
364                         end
365                 else
366                         gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
367                 end
368         end
369
370         if basefolder.type == "mod" then
371                 local targetfolder = basename
372
373                 if targetfolder == nil then
374                         targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
375                 end
376
377                 --if heuristic failed try to use current foldername
378                 if targetfolder == nil then
379                         targetfolder = get_last_folder(basefolder.path)
380                 end
381
382                 if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
383                         local targetpath = core.get_modpath() .. DIR_DELIM .. targetfolder
384                         core.copy_dir(basefolder.path,targetpath)
385                 else
386                         gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
387                 end
388         end
389
390         core.delete_dir(modpath)
391
392         modmgr.refresh_globals()
393
394 end
395
396 --------------------------------------------------------------------------------
397 function modmgr.preparemodlist(data)
398         local retval = {}
399
400         local global_mods = {}
401         local game_mods = {}
402
403         --read global mods
404         local modpath = core.get_modpath()
405
406         if modpath ~= nil and
407                 modpath ~= "" then
408                 get_mods(modpath,global_mods)
409         end
410
411         for i=1,#global_mods,1 do
412                 global_mods[i].typ = "global_mod"
413                 table.insert(retval,global_mods[i])
414         end
415
416         --read game mods
417         local gamespec = gamemgr.find_by_gameid(data.gameid)
418         gamemgr.get_game_mods(gamespec, game_mods)
419
420         for i=1,#game_mods,1 do
421                 game_mods[i].typ = "game_mod"
422                 table.insert(retval,game_mods[i])
423         end
424
425         if data.worldpath == nil then
426                 return retval
427         end
428
429         --read world mod configuration
430         local filename = data.worldpath ..
431                                 DIR_DELIM .. "world.mt"
432
433         local worldfile = Settings(filename)
434
435         for key,value in pairs(worldfile:to_table()) do
436                 if key:sub(1, 9) == "load_mod_" then
437                         key = key:sub(10)
438                         local element = nil
439                         for i=1,#retval,1 do
440                                 if retval[i].name == key and
441                                         not retval[i].is_modpack then
442                                         element = retval[i]
443                                         break
444                                 end
445                         end
446                         if element ~= nil then
447                                 element.enabled = core.is_yes(value)
448                         else
449                                 core.log("info", "Mod: " .. key .. " " .. dump(value) .. " but not found")
450                         end
451                 end
452         end
453
454         return retval
455 end
456
457 --------------------------------------------------------------------------------
458 function modmgr.comparemod(elem1,elem2)
459         if elem1 == nil or elem2 == nil then
460                 return false
461         end
462         if elem1.name ~= elem2.name then
463                 return false
464         end
465         if elem1.is_modpack ~= elem2.is_modpack then
466                 return false
467         end
468         if elem1.typ ~= elem2.typ then
469                 return false
470         end
471         if elem1.modpack ~= elem2.modpack then
472                 return false
473         end
474
475         if elem1.path ~= elem2.path then
476                 return false
477         end
478
479         return true
480 end
481
482 --------------------------------------------------------------------------------
483 function modmgr.mod_exists(basename)
484
485         if modmgr.global_mods == nil then
486                 modmgr.refresh_globals()
487         end
488
489         if modmgr.global_mods:raw_index_by_uid(basename) > 0 then
490                 return true
491         end
492
493         return false
494 end
495
496 --------------------------------------------------------------------------------
497 function modmgr.get_global_mod(idx)
498
499         if modmgr.global_mods == nil then
500                 return nil
501         end
502
503         if idx == nil or idx < 1 or
504                 idx > modmgr.global_mods:size() then
505                 return nil
506         end
507
508         return modmgr.global_mods:get_list()[idx]
509 end
510
511 --------------------------------------------------------------------------------
512 function modmgr.refresh_globals()
513         modmgr.global_mods = filterlist.create(
514                                         modmgr.preparemodlist, --refresh
515                                         modmgr.comparemod, --compare
516                                         function(element,uid) --uid match
517                                                 if element.name == uid then
518                                                         return true
519                                                 end
520                                         end,
521                                         nil, --filter
522                                         {}
523                                         )
524         modmgr.global_mods:add_sort_mechanism("alphabetic", sort_mod_list)
525         modmgr.global_mods:set_sortmode("alphabetic")
526 end
527
528 --------------------------------------------------------------------------------
529 function modmgr.identify_filetype(name)
530
531         if name:sub(-3):lower() == "zip" then
532                 return {
533                                 name = name,
534                                 type = "zip"
535                                 }
536         end
537
538         if name:sub(-6):lower() == "tar.gz" or
539                 name:sub(-3):lower() == "tgz"then
540                 return {
541                                 name = name,
542                                 type = "tgz"
543                                 }
544         end
545
546         if name:sub(-6):lower() == "tar.bz2" then
547                 return {
548                                 name = name,
549                                 type = "tbz"
550                                 }
551         end
552
553         if name:sub(-2):lower() == "7z" then
554                 return {
555                                 name = name,
556                                 type = "7z"
557                                 }
558         end
559
560         return {
561                 name = name,
562                 type = "ukn"
563         }
564 end