update_detector.lua 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. --Minetest
  2. --Copyright (C) 2023 rubenwardy
  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. update_detector = {}
  18. if not core.get_http_api then
  19. update_detector.get_all = function() return {} end
  20. update_detector.get_count = function() return 0 end
  21. return
  22. end
  23. assert(core.create_dir(core.get_cache_path() .. DIR_DELIM .. "cdb"))
  24. local cache_file_path = core.get_cache_path() .. DIR_DELIM .. "cdb" .. DIR_DELIM .. "updates.json"
  25. local has_fetched = false
  26. local latest_releases
  27. do
  28. if check_cache_age("cdb_updates_last_checked", 3 * 3600) then
  29. local f = io.open(cache_file_path, "r")
  30. local data = ""
  31. if f then
  32. data = f:read("*a")
  33. f:close()
  34. end
  35. data = data ~= "" and core.parse_json(data) or nil
  36. if type(data) == "table" then
  37. latest_releases = data
  38. has_fetched = true
  39. end
  40. end
  41. end
  42. local function fetch_latest_releases()
  43. local version = core.get_version()
  44. local base_url = core.settings:get("contentdb_url")
  45. local url = base_url ..
  46. "/api/updates/?type=mod&type=game&type=txp&protocol_version=" ..
  47. core.get_max_supp_proto() .. "&engine_version=" .. core.urlencode(version.string)
  48. local http = core.get_http_api()
  49. local response = http.fetch_sync({ url = url })
  50. if not response.succeeded then
  51. return
  52. end
  53. return core.parse_json(response.data)
  54. end
  55. --- Get a table from package key (author/name) to latest release id
  56. ---
  57. --- @param callback function that takes a single argument, table or nil
  58. local function get_latest_releases(callback)
  59. core.handle_async(fetch_latest_releases, nil, callback)
  60. end
  61. local function has_packages_from_cdb()
  62. pkgmgr.refresh_globals()
  63. pkgmgr.update_gamelist()
  64. for _, content in pairs(pkgmgr.get_all()) do
  65. if pkgmgr.get_contentdb_id(content) then
  66. return true
  67. end
  68. end
  69. return false
  70. end
  71. --- @returns a new table with all keys lowercase
  72. local function lowercase_keys(tab)
  73. local ret = {}
  74. for key, value in pairs(tab) do
  75. ret[key:lower()] = value
  76. end
  77. return ret
  78. end
  79. local function fetch()
  80. if has_fetched or not has_packages_from_cdb() then
  81. return
  82. end
  83. has_fetched = true
  84. get_latest_releases(function(releases)
  85. if not releases then
  86. has_fetched = false
  87. return
  88. end
  89. latest_releases = lowercase_keys(releases)
  90. core.safe_file_write(cache_file_path, core.write_json(latest_releases))
  91. cache_settings:set("cdb_updates_last_checked", tostring(os.time()))
  92. if update_detector.get_count() > 0 then
  93. local maintab = ui.find_by_name("maintab")
  94. if not maintab.hidden then
  95. ui.update()
  96. end
  97. end
  98. end)
  99. end
  100. --- @returns a list of content with an update available
  101. function update_detector.get_all()
  102. if latest_releases == nil then
  103. fetch()
  104. return {}
  105. end
  106. pkgmgr.refresh_globals()
  107. pkgmgr.update_gamelist()
  108. local ret = {}
  109. local all_content = pkgmgr.get_all()
  110. for _, content in ipairs(all_content) do
  111. local cdb_id = pkgmgr.get_contentdb_id(content)
  112. if cdb_id then
  113. -- The backend will account for aliases in `latest_releases`
  114. local latest_release = latest_releases[cdb_id]
  115. if not latest_release and content.type == "game" then
  116. latest_release = latest_releases[cdb_id .. "_game"]
  117. end
  118. if latest_release and latest_release > content.release then
  119. ret[#ret + 1] = content
  120. end
  121. end
  122. end
  123. return ret
  124. end
  125. --- @return number of packages with updates available
  126. function update_detector.get_count()
  127. return #update_detector.get_all()
  128. end