store.lua 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  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. --modstore implementation
  19. modstore = {}
  20. --------------------------------------------------------------------------------
  21. -- @function [parent=#modstore] init
  22. function modstore.init(size, unsortedmods, searchmods)
  23. modstore.mods_on_unsorted_page = unsortedmods
  24. modstore.mods_on_search_page = searchmods
  25. modstore.modsperpage = modstore.mods_on_unsorted_page
  26. modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
  27. DIR_DELIM .. "pack" .. DIR_DELIM
  28. modstore.lastmodtitle = ""
  29. modstore.last_search = ""
  30. modstore.searchlist = filterlist.create(
  31. function()
  32. if modstore.modlist_unsorted ~= nil and
  33. modstore.modlist_unsorted.data ~= nil then
  34. return modstore.modlist_unsorted.data
  35. end
  36. return {}
  37. end,
  38. function(element,modid)
  39. if element.id == modid then
  40. return true
  41. end
  42. return false
  43. end, --compare fct
  44. nil, --uid match fct
  45. function(element,substring)
  46. if substring == nil or
  47. substring == "" then
  48. return false
  49. end
  50. substring = substring:upper()
  51. if element.title ~= nil and
  52. element.title:upper():find(substring) ~= nil then
  53. return true
  54. end
  55. if element.details ~= nil and
  56. element.details.author ~= nil and
  57. element.details.author:upper():find(substring) ~= nil then
  58. return true
  59. end
  60. if element.details ~= nil and
  61. element.details.description ~= nil and
  62. element.details.description:upper():find(substring) ~= nil then
  63. return true
  64. end
  65. return false
  66. end --filter fct
  67. )
  68. modstore.current_list = nil
  69. modstore.tv_store = tabview_create("modstore",size,{x=0,y=0})
  70. modstore.tv_store:set_global_event_handler(modstore.handle_events)
  71. modstore.tv_store:add(
  72. {
  73. name = "unsorted",
  74. caption = fgettext("Unsorted"),
  75. cbf_formspec = modstore.unsorted_tab,
  76. cbf_button_handler = modstore.handle_buttons,
  77. on_change =
  78. function() modstore.modsperpage = modstore.mods_on_unsorted_page end
  79. }
  80. )
  81. modstore.tv_store:add(
  82. {
  83. name = "search",
  84. caption = fgettext("Search"),
  85. cbf_formspec = modstore.getsearchpage,
  86. cbf_button_handler = modstore.handle_buttons,
  87. on_change = modstore.activate_search_tab
  88. }
  89. )
  90. end
  91. --------------------------------------------------------------------------------
  92. -- @function [parent=#modstore] nametoindex
  93. function modstore.nametoindex(name)
  94. for i=1,#modstore.tabnames,1 do
  95. if modstore.tabnames[i] == name then
  96. return i
  97. end
  98. end
  99. return 1
  100. end
  101. --------------------------------------------------------------------------------
  102. -- @function [parent=#modstore] showdownloading
  103. function modstore.showdownloading(title)
  104. local new_dlg = dialog_create("store_downloading",
  105. function(data)
  106. return "size[6,2]label[0.25,0.75;" ..
  107. fgettext("Downloading $1, please wait...", data.title) .. "]"
  108. end,
  109. function(this,fields)
  110. if fields["btn_hidden_close_download"] ~= nil then
  111. if fields["btn_hidden_close_download"].successfull then
  112. modstore.lastmodentry = fields["btn_hidden_close_download"]
  113. modstore.successfulldialog(this)
  114. else
  115. this.parent:show()
  116. this:delete()
  117. modstore.lastmodtitle = ""
  118. end
  119. return true
  120. end
  121. return false
  122. end,
  123. nil)
  124. new_dlg:set_parent(modstore.tv_store)
  125. modstore.tv_store:hide()
  126. new_dlg.data.title = title
  127. new_dlg:show()
  128. end
  129. --------------------------------------------------------------------------------
  130. -- @function [parent=#modstore] successfulldialog
  131. function modstore.successfulldialog(downloading_dlg)
  132. local new_dlg = dialog_create("store_downloading",
  133. function(data)
  134. local retval = ""
  135. retval = retval .. "size[6,2,true]"
  136. if modstore.lastmodentry ~= nil then
  137. retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
  138. retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
  139. retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
  140. retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
  141. end
  142. retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]"
  143. return retval
  144. end,
  145. function(this,fields)
  146. if fields["btn_confirm_mod_successfull"] ~= nil then
  147. this.parent:show()
  148. downloading_dlg:delete()
  149. this:delete()
  150. return true
  151. end
  152. return false
  153. end,
  154. nil)
  155. new_dlg:set_parent(modstore.tv_store)
  156. modstore.tv_store:hide()
  157. new_dlg:show()
  158. end
  159. --------------------------------------------------------------------------------
  160. -- @function [parent=#modstore] handle_buttons
  161. function modstore.handle_buttons(parent, fields, name, data)
  162. if fields["btn_modstore_page_up"] then
  163. if modstore.current_list ~= nil and modstore.current_list.page > 0 then
  164. modstore.current_list.page = modstore.current_list.page - 1
  165. end
  166. return true
  167. end
  168. if fields["btn_modstore_page_down"] then
  169. if modstore.current_list ~= nil and
  170. modstore.current_list.page <modstore.current_list.pagecount-1 then
  171. modstore.current_list.page = modstore.current_list.page +1
  172. end
  173. return true
  174. end
  175. if fields["btn_modstore_search"] or
  176. (fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
  177. modstore.last_search = fields["te_modstore_search"]
  178. filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
  179. filterlist.refresh(modstore.searchlist)
  180. modstore.currentlist = {
  181. page = 0,
  182. pagecount = math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
  183. data = filterlist.get_list(modstore.searchlist),
  184. }
  185. return true
  186. end
  187. if fields["btn_modstore_close"] then
  188. local maintab = ui.find_by_name("maintab")
  189. parent:hide()
  190. maintab:show()
  191. return true
  192. end
  193. for key,value in pairs(fields) do
  194. local foundat = key:find("btn_install_mod_")
  195. if ( foundat == 1) then
  196. local modid = tonumber(key:sub(17))
  197. for i=1,#modstore.modlist_unsorted.data,1 do
  198. if modstore.modlist_unsorted.data[i].id == modid then
  199. local moddetails = modstore.modlist_unsorted.data[i].details
  200. modstore.lastmodtitle = moddetails.title
  201. if not core.handle_async(
  202. function(param)
  203. local fullurl = core.settings:get("modstore_download_url") ..
  204. param.moddetails.download_url
  205. if param.version ~= nil then
  206. local found = false
  207. for i=1,#param.moddetails.versions, 1 do
  208. if param.moddetails.versions[i].date:sub(1,10) == param.version then
  209. fullurl = core.settings:get("modstore_download_url") ..
  210. param.moddetails.versions[i].download_url
  211. found = true
  212. end
  213. end
  214. if not found then
  215. core.log("error","no download url found for version " .. dump(param.version))
  216. return {
  217. moddetails = param.moddetails,
  218. successfull = false
  219. }
  220. end
  221. end
  222. if core.download_file(fullurl,param.filename) then
  223. return {
  224. texturename = param.texturename,
  225. moddetails = param.moddetails,
  226. filename = param.filename,
  227. successfull = true
  228. }
  229. else
  230. core.log("error","downloading " .. dump(fullurl) .. " failed")
  231. return {
  232. moddetails = param.moddetails,
  233. successfull = false
  234. }
  235. end
  236. end,
  237. {
  238. moddetails = moddetails,
  239. version = fields["dd_version" .. modid],
  240. filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
  241. texturename = modstore.modlist_unsorted.data[i].texturename
  242. },
  243. function(result)
  244. --print("Result from async: " .. dump(result.successfull))
  245. if result.successfull then
  246. modmgr.installmod(result.filename,result.moddetails.basename)
  247. os.remove(result.filename)
  248. else
  249. gamedata.errormessage = "Failed to download " .. result.moddetails.title
  250. end
  251. if gamedata.errormessage == nil then
  252. core.button_handler({btn_hidden_close_download=result})
  253. else
  254. core.button_handler({btn_hidden_close_download={successfull=false}})
  255. end
  256. end
  257. ) then
  258. print("ERROR: async event failed")
  259. gamedata.errormessage = "Failed to download " .. modstore.lastmodtitle
  260. end
  261. modstore.showdownloading(modstore.lastmodtitle)
  262. return true
  263. end
  264. end
  265. return true
  266. end
  267. end
  268. return false
  269. end
  270. --------------------------------------------------------------------------------
  271. -- @function [parent=#modstore] handle_events
  272. function modstore.handle_events(this,event)
  273. if (event == "MenuQuit") then
  274. this:hide()
  275. return true
  276. end
  277. end
  278. --------------------------------------------------------------------------------
  279. -- @function [parent=#modstore] update_modlist
  280. function modstore.update_modlist()
  281. modstore.modlist_unsorted = {}
  282. modstore.modlist_unsorted.data = {}
  283. modstore.modlist_unsorted.pagecount = 1
  284. modstore.modlist_unsorted.page = 0
  285. core.handle_async(
  286. function(param)
  287. return core.get_modstore_list()
  288. end,
  289. nil,
  290. function(result)
  291. if result ~= nil then
  292. modstore.modlist_unsorted = {}
  293. modstore.modlist_unsorted.data = result
  294. if modstore.modlist_unsorted.data ~= nil then
  295. modstore.modlist_unsorted.pagecount =
  296. math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
  297. else
  298. modstore.modlist_unsorted.data = {}
  299. modstore.modlist_unsorted.pagecount = 1
  300. end
  301. modstore.modlist_unsorted.page = 0
  302. modstore.fetchdetails()
  303. core.event_handler("Refresh")
  304. end
  305. end
  306. )
  307. end
  308. --------------------------------------------------------------------------------
  309. -- @function [parent=#modstore] fetchdetails
  310. function modstore.fetchdetails()
  311. for i=1,#modstore.modlist_unsorted.data,1 do
  312. core.handle_async(
  313. function(param)
  314. param.details = core.get_modstore_details(tostring(param.modid))
  315. return param
  316. end,
  317. {
  318. modid=modstore.modlist_unsorted.data[i].id,
  319. listindex=i
  320. },
  321. function(result)
  322. if result ~= nil and
  323. modstore.modlist_unsorted ~= nil
  324. and modstore.modlist_unsorted.data ~= nil and
  325. modstore.modlist_unsorted.data[result.listindex] ~= nil and
  326. modstore.modlist_unsorted.data[result.listindex].id ~= nil then
  327. modstore.modlist_unsorted.data[result.listindex].details = result.details
  328. core.event_handler("Refresh")
  329. end
  330. end
  331. )
  332. end
  333. end
  334. --------------------------------------------------------------------------------
  335. -- @function [parent=#modstore] getscreenshot
  336. function modstore.getscreenshot(ypos,listentry)
  337. if listentry.details ~= nil and
  338. (listentry.details.screenshot_url == nil or
  339. listentry.details.screenshot_url == "") then
  340. if listentry.texturename == nil then
  341. listentry.texturename = defaulttexturedir .. "no_screenshot.png"
  342. end
  343. return "image[0,".. ypos .. ";3,2;" ..
  344. core.formspec_escape(listentry.texturename) .. "]"
  345. end
  346. if listentry.details ~= nil and
  347. listentry.texturename == nil then
  348. --make sure we don't download multiple times
  349. listentry.texturename = "in progress"
  350. --prepare url and filename
  351. local fullurl = core.settings:get("modstore_download_url") ..
  352. listentry.details.screenshot_url
  353. local filename = os.tempfolder() .. "_MID_" .. listentry.id
  354. --trigger download
  355. core.handle_async(
  356. --first param is downloadfct
  357. function(param)
  358. param.successfull = core.download_file(param.fullurl,param.filename)
  359. return param
  360. end,
  361. --second parameter is data passed to async job
  362. {
  363. fullurl = fullurl,
  364. filename = filename,
  365. modid = listentry.id
  366. },
  367. --integrate result to raw list
  368. function(result)
  369. if result.successfull then
  370. local found = false
  371. for i=1,#modstore.modlist_unsorted.data,1 do
  372. if modstore.modlist_unsorted.data[i].id == result.modid then
  373. found = true
  374. modstore.modlist_unsorted.data[i].texturename = result.filename
  375. break
  376. end
  377. end
  378. if found then
  379. core.event_handler("Refresh")
  380. else
  381. core.log("error","got screenshot but didn't find matching mod: " .. result.modid)
  382. end
  383. end
  384. end
  385. )
  386. end
  387. if listentry.texturename ~= nil and
  388. listentry.texturename ~= "in progress" then
  389. return "image[0,".. ypos .. ";3,2;" ..
  390. core.formspec_escape(listentry.texturename) .. "]"
  391. end
  392. return ""
  393. end
  394. --------------------------------------------------------------------------------
  395. --@function [parent=#modstore] getshortmodinfo
  396. function modstore.getshortmodinfo(ypos,listentry,details)
  397. local retval = ""
  398. retval = retval .. "box[0," .. ypos .. ";11.4,1.75;#FFFFFF]"
  399. --screenshot
  400. retval = retval .. modstore.getscreenshot(ypos,listentry)
  401. --title + author
  402. retval = retval .."label[2.75," .. ypos .. ";" ..
  403. core.formspec_escape(details.title) .. " (" .. details.author .. ")]"
  404. --description
  405. local descriptiony = ypos + 0.5
  406. retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
  407. core.formspec_escape(details.description) .. ";]"
  408. --rating
  409. local ratingy = ypos
  410. retval = retval .."label[7," .. ratingy .. ";" ..
  411. fgettext("Rating") .. ":]"
  412. retval = retval .. "label[8.7," .. ratingy .. ";" .. details.rating .."]"
  413. --versions (IMPORTANT has to be defined AFTER rating)
  414. if details.versions ~= nil and
  415. #details.versions > 1 then
  416. local versiony = ypos + 0.05
  417. retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";"
  418. local versions = ""
  419. for i=1,#details.versions , 1 do
  420. if versions ~= "" then
  421. versions = versions .. ","
  422. end
  423. versions = versions .. details.versions[i].date:sub(1,10)
  424. end
  425. retval = retval .. versions .. ";1]"
  426. end
  427. if details.basename then
  428. --install button
  429. local buttony = ypos + 1.2
  430. retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";"
  431. if modmgr.mod_exists(details.basename) then
  432. retval = retval .. fgettext("re-Install") .."]"
  433. else
  434. retval = retval .. fgettext("Install") .."]"
  435. end
  436. end
  437. return retval
  438. end
  439. --------------------------------------------------------------------------------
  440. --@function [parent=#modstore] getmodlist
  441. function modstore.getmodlist(list,yoffset)
  442. modstore.current_list = list
  443. if yoffset == nil then
  444. yoffset = 0
  445. end
  446. local sb_y_start = 0.2 + yoffset
  447. local sb_y_end = (modstore.modsperpage * 1.75) + ((modstore.modsperpage-1) * 0.15)
  448. local close_button = "button[4," .. (sb_y_end + 0.3 + yoffset) ..
  449. ";4,0.5;btn_modstore_close;" .. fgettext("Close store") .. "]"
  450. if #list.data == 0 then
  451. return close_button
  452. end
  453. local scrollbar = ""
  454. scrollbar = scrollbar .. "label[0.1,".. (sb_y_end + 0.25 + yoffset) ..";"
  455. .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
  456. scrollbar = scrollbar .. "box[11.6," .. sb_y_start .. ";0.28," .. sb_y_end .. ";#000000]"
  457. local scrollbarpos = (sb_y_start + 0.5) +
  458. ((sb_y_end -1.6)/(list.pagecount-1)) * list.page
  459. scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
  460. scrollbar = scrollbar .. "button[11.6," .. (sb_y_start)
  461. .. ";0.5,0.5;btn_modstore_page_up;^]"
  462. scrollbar = scrollbar .. "button[11.6," .. (sb_y_start + sb_y_end - 0.5)
  463. .. ";0.5,0.5;btn_modstore_page_down;v]"
  464. local retval = ""
  465. local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
  466. if (endmod > #list.data) then
  467. endmod = #list.data
  468. end
  469. for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
  470. --getmoddetails
  471. local details = list.data[i].details
  472. if details == nil then
  473. details = {}
  474. details.title = list.data[i].title
  475. details.author = ""
  476. details.rating = -1
  477. details.description = ""
  478. end
  479. if details ~= nil then
  480. local screenshot_ypos =
  481. yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
  482. retval = retval .. modstore.getshortmodinfo(screenshot_ypos,
  483. list.data[i],
  484. details)
  485. end
  486. end
  487. return retval .. scrollbar .. close_button
  488. end
  489. --------------------------------------------------------------------------------
  490. --@function [parent=#modstore] getsearchpage
  491. function modstore.getsearchpage(tabview, name, tabdata)
  492. local retval = ""
  493. local search = ""
  494. if modstore.last_search ~= nil then
  495. search = modstore.last_search
  496. end
  497. retval = retval ..
  498. "button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" ..
  499. "field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]"
  500. retval = retval ..
  501. modstore.getmodlist(
  502. modstore.currentlist,
  503. 1.75)
  504. return retval;
  505. end
  506. --------------------------------------------------------------------------------
  507. --@function [parent=#modstore] unsorted_tab
  508. function modstore.unsorted_tab()
  509. return modstore.getmodlist(modstore.modlist_unsorted)
  510. end
  511. --------------------------------------------------------------------------------
  512. --@function [parent=#modstore] activate_search_tab
  513. function modstore.activate_search_tab(type, old_tab, new_tab)
  514. if old_tab == new_tab then
  515. return
  516. end
  517. filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search)
  518. filterlist.refresh(modstore.searchlist)
  519. modstore.modsperpage = modstore.mods_on_search_page
  520. modstore.currentlist = {
  521. page = 0,
  522. pagecount =
  523. math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
  524. data = filterlist.get_list(modstore.searchlist),
  525. }
  526. end