modmgr.lua 12 KB

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