mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 11:11:10 +02:00
Show mod downloads and release dates in the launcher MODS panel
The MODS tab now shows each installed mod's total GitHub downloads (summed asset download_count across all releases), its first and latest release dates, and a Sort row (Name / Popularity / Release date / Last updated) persisted in options.modSort. The launcher already fetched per-repo release lists for update checks, so the data rides the same cached fetch: parseRelease keeps download_count and published_at, writeCache persists them, and a cache entry written before the fields existed is treated as stale and refetched once instead of hiding the line behind an old cache.
This commit is contained in:
+78
-3
@@ -102,7 +102,9 @@ function ModUpdate.previewLine(text, maxChars)
|
||||
return s
|
||||
end
|
||||
|
||||
-- Decode one GitHub release object into { version, tag, zip, prerelease, body }.
|
||||
-- Decode one GitHub release object into { version, tag, zip, prerelease,
|
||||
-- name, body, downloads }. `downloads` is the sum of every asset's
|
||||
-- download_count, GitHub's own measure of a release's downloads.
|
||||
function ModUpdate.parseRelease(doc, modId)
|
||||
if type(doc) ~= "table" or not doc.tag_name then
|
||||
return nil, "no tag_name in release"
|
||||
@@ -114,6 +116,13 @@ function ModUpdate.parseRelease(doc, modId)
|
||||
local triple = version:match("^(%d+%.%d+%.%d+)")
|
||||
local zip = ModUpdate.pickZipAsset(doc.assets, modId, triple)
|
||||
local body = type(doc.body) == "string" and doc.body or ""
|
||||
local downloads = 0
|
||||
if type(doc.assets) == "table" then
|
||||
for _, a in ipairs(doc.assets) do
|
||||
local d = type(a) == "table" and tonumber(a.download_count)
|
||||
if d and d > 0 then downloads = downloads + d end
|
||||
end
|
||||
end
|
||||
return {
|
||||
version = triple,
|
||||
tag = tostring(doc.tag_name),
|
||||
@@ -121,6 +130,8 @@ function ModUpdate.parseRelease(doc, modId)
|
||||
prerelease = doc.prerelease == true,
|
||||
name = type(doc.name) == "string" and doc.name or triple,
|
||||
body = body,
|
||||
downloads = downloads,
|
||||
published = (doc.published_at or doc.created_at or ""):match("^(%d+%-%d+%-%d+)"),
|
||||
}
|
||||
end
|
||||
|
||||
@@ -154,7 +165,7 @@ function ModUpdate.parseReleases(jsonText, modId, Json)
|
||||
end
|
||||
|
||||
function ModUpdate.apiReleasesUrl(repo)
|
||||
return "https://api.github.com/repos/" .. repo .. "/releases?per_page=30"
|
||||
return "https://api.github.com/repos/" .. repo .. "/releases?per_page=100"
|
||||
end
|
||||
|
||||
function ModUpdate.apiLatestUrl(repo)
|
||||
@@ -180,6 +191,52 @@ function ModUpdate.pickBest(releases)
|
||||
return releases[1]
|
||||
end
|
||||
|
||||
-- Sum of per-release download counts. Returns nil when no release carries
|
||||
-- the field (a cache entry written before downloads existed), else
|
||||
-- { total = <sum>, releases = <release count> } so a caller can tell a
|
||||
-- real "0 downloads" apart from "no data yet".
|
||||
function ModUpdate.totalDownloads(releases)
|
||||
if type(releases) ~= "table" or #releases == 0 then return nil end
|
||||
local total, hasData = 0, false
|
||||
for _, rel in ipairs(releases) do
|
||||
local d = type(rel) == "table" and tonumber(rel.downloads)
|
||||
if d then
|
||||
hasData = true
|
||||
total = total + d
|
||||
end
|
||||
end
|
||||
if not hasData then return nil end
|
||||
return { total = total, releases = #releases }
|
||||
end
|
||||
|
||||
-- First and latest release dates ("YYYY-MM-DD", ISO strings compare in
|
||||
-- calendar order). Returns nil when no release carries a date -- same
|
||||
-- "no data yet" rule as totalDownloads.
|
||||
function ModUpdate.releaseDates(releases)
|
||||
if type(releases) ~= "table" or #releases == 0 then return nil end
|
||||
local first, latest, has = nil, nil, false
|
||||
for _, rel in ipairs(releases) do
|
||||
local p = type(rel) == "table" and rel.published
|
||||
if type(p) == "string" and p ~= "" then
|
||||
has = true
|
||||
if not first or p < first then first = p end
|
||||
if not latest or p > latest then latest = p end
|
||||
end
|
||||
end
|
||||
if not has then return nil end
|
||||
return { first = first, latest = latest }
|
||||
end
|
||||
|
||||
-- Thousands-separated count for the launcher ("12,345"), plain for small
|
||||
-- numbers. Never throws; garbage in, "0" out.
|
||||
function ModUpdate.formatCount(n)
|
||||
n = tonumber(n)
|
||||
if not n or n ~= n or n < 0 then return "0" end
|
||||
local s = tostring(math.floor(n))
|
||||
s = s:reverse():gsub("(%d%d%d)", "%1,"):reverse()
|
||||
return (s:gsub("^,", ""))
|
||||
end
|
||||
|
||||
-- ------- cache (options.modUpdateCache[repo])
|
||||
|
||||
local function cacheStore()
|
||||
@@ -210,6 +267,22 @@ function ModUpdate.cacheFresh(entry, now, ttl)
|
||||
and (now - entry.checkedAt) < ttl
|
||||
end
|
||||
|
||||
-- A cache entry written before download counts existed carries no
|
||||
-- `downloads` on any release. It is provably stale -- parseRelease always
|
||||
-- sets the field now -- so treat it as expired: the next fetch rewrites
|
||||
-- the entry in the current format, one refetch per repo, and the launcher's
|
||||
-- download line stops hiding behind an old cache.
|
||||
function ModUpdate.cacheUsable(cached)
|
||||
if type(cached) ~= "table" or type(cached.releases) ~= "table" then
|
||||
return false
|
||||
end
|
||||
if #cached.releases == 0 then return true end
|
||||
for _, rel in ipairs(cached.releases) do
|
||||
if tonumber(rel.downloads) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function ModUpdate.writeCache(repo, releases)
|
||||
if type(repo) ~= "string" or repo == "" then return false end
|
||||
local ok = pcall(function()
|
||||
@@ -226,6 +299,8 @@ function ModUpdate.writeCache(repo, releases)
|
||||
name = rel.name,
|
||||
prerelease = rel.prerelease == true,
|
||||
body = type(rel.body) == "string" and rel.body or "",
|
||||
downloads = tonumber(rel.downloads) or 0,
|
||||
published = rel.published,
|
||||
zip = rel.zip and {
|
||||
name = rel.zip.name,
|
||||
url = rel.zip.url,
|
||||
@@ -280,7 +355,7 @@ function ModUpdate.fetchReleases(repo, modId, opts)
|
||||
end
|
||||
if not opts.force then
|
||||
local cached = ModUpdate.readCache(repo)
|
||||
if ModUpdate.cacheFresh(cached) then
|
||||
if ModUpdate.cacheFresh(cached) and ModUpdate.cacheUsable(cached) then
|
||||
return cached.releases, nil, { fromCache = true }
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user