pkgmgr.lua 17 KB

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