update_detector.lua 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. --Luanti
  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", 24 * 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. for _, content in pairs(pkgmgr.get_all()) do
  63. if pkgmgr.get_contentdb_id(content) then
  64. return true
  65. end
  66. end
  67. return false
  68. end
  69. --- @returns a new table with all keys lowercase
  70. local function lowercase_keys(tab)
  71. local ret = {}
  72. for key, value in pairs(tab) do
  73. ret[key:lower()] = value
  74. end
  75. return ret
  76. end
  77. local function fetch()
  78. if has_fetched or not has_packages_from_cdb() then
  79. return
  80. end
  81. has_fetched = true
  82. get_latest_releases(function(releases)
  83. if not releases then
  84. has_fetched = false
  85. return
  86. end
  87. latest_releases = lowercase_keys(releases)
  88. core.safe_file_write(cache_file_path, core.write_json(latest_releases))
  89. cache_settings:set("cdb_updates_last_checked", tostring(os.time()))
  90. if update_detector.get_count() > 0 then
  91. local maintab = ui.find_by_name("maintab")
  92. if not maintab.hidden then
  93. ui.update()
  94. end
  95. end
  96. end)
  97. end
  98. --- @returns a list of content with an update available
  99. function update_detector.get_all()
  100. if latest_releases == nil then
  101. fetch()
  102. return {}
  103. end
  104. local ret = {}
  105. local all_content = pkgmgr.get_all()
  106. for _, content in ipairs(all_content) do
  107. local cdb_id = pkgmgr.get_contentdb_id(content)
  108. if cdb_id then
  109. -- The backend will account for aliases in `latest_releases`
  110. local latest_release = latest_releases[cdb_id]
  111. if not latest_release and content.type == "game" then
  112. latest_release = latest_releases[cdb_id .. "_game"]
  113. end
  114. if latest_release and latest_release > content.release then
  115. ret[#ret + 1] = content
  116. end
  117. end
  118. end
  119. return ret
  120. end
  121. --- @return number of packages with updates available
  122. function update_detector.get_count()
  123. return #update_detector.get_all()
  124. end