diff --git a/core/src/com/unciv/ui/pickerscreens/GitHub.kt b/core/src/com/unciv/ui/pickerscreens/GitHub.kt index f87135ae4b..a3abd172e5 100644 --- a/core/src/com/unciv/ui/pickerscreens/GitHub.kt +++ b/core/src/com/unciv/ui/pickerscreens/GitHub.kt @@ -250,6 +250,43 @@ object Github { return null } + class Tree { + + class TreeFile { + var size: Long = 0L + } + + var url: String = "" + var tree = ArrayList() + } + + fun getRepoSize(repo: Repo): Float { + val link = "https://api.github.com/repos/${repo.full_name}/git/trees/${repo.default_branch}?recursive=true" + + var retries = 2 + while (retries > 0) { + retries-- + // obey rate limit + if (RateLimit.waitForLimit()) return 0f + // try download + val inputStream = download(link) { + if (it.responseCode == 403 || it.responseCode == 200 && retries == 1) { + // Pass the response headers to the rate limit handler so it can process the rate limit headers + RateLimit.notifyHttpResponse(it) + retries++ // An extra retry so the 403 is ignored in the retry count + } + } ?: continue + val tree = json().fromJson(Tree::class.java, inputStream.bufferedReader().readText()) + + var totalSizeBytes = 0L + for (file in tree.tree) + totalSizeBytes += file.size + + return totalSizeBytes / 1024f + } + return 0f + } + /** * Parsed GitHub repo search response * @property total_count Total number of hits for the search (ignoring paging window) @@ -268,6 +305,9 @@ object Github { /** Part of [RepoSearch] in Github API response - one repository entry in [items][RepoSearch.items] */ @Suppress("PropertyName") class Repo { + + var hasUpdatedSize = false + var name = "" var full_name = "" var description: String? = null diff --git a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt index 7d6b18858b..4d1d0acb3d 100644 --- a/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/ModManagementScreen.kt @@ -10,6 +10,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.ScrollPane import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.badlogic.gdx.utils.Align +import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.SerializationException import com.unciv.MainMenuScreen import com.unciv.UncivGame @@ -47,8 +48,13 @@ import com.unciv.ui.utils.extensions.toTextButton import com.unciv.utils.Log import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.launchOnGLThread +import com.unciv.utils.concurrency.withGLContext import kotlinx.coroutines.Job import kotlinx.coroutines.isActive +import java.io.IOException +import java.net.HttpURLConnection +import java.net.URL +import java.net.URLConnection import kotlin.math.max /** @@ -73,8 +79,7 @@ class ModManagementScreen( private var lastSelectedButton: Button? = null private var lastSyncMarkedButton: Button? = null - private var selectedModName = "" - private var selectedAuthor = "" + private var selectedMod: Github.Repo? = null private val modDescriptionLabel: WrappableLabel @@ -357,8 +362,6 @@ class ModManagementScreen( } private fun addModInfoToActionTable(modName: String, repoUrl: String, updatedAt: String, author: String, modSize: Int) { // remember selected mod - for now needed only to display a background-fetched image while the user is watching - selectedModName = modName - selectedAuthor = author // Display metadata if (author.isNotEmpty()) @@ -407,6 +410,13 @@ class ModManagementScreen( return downloadButton } + private fun updateModInfo() { + if (selectedMod != null) { + modActionTable.clear() + addModInfoToActionTable(selectedMod!!) + } + } + /** Used as onClick handler for the online Mod list buttons */ private fun onlineButtonAction(repo: Github.Repo, button: Button) { syncOnlineSelected(repo.name, button) @@ -417,6 +427,26 @@ class ModManagementScreen( val label = if (installedModInfo[repo.name]?.state?.hasUpdate == true) "Update [${repo.name}]" else "Download [${repo.name}]" + + if (!repo.hasUpdatedSize) { + Concurrency.run("GitHubParser") { + try { + val repoSize = Github.getRepoSize(repo) + if (repoSize > 0f) { + launchOnGLThread { + repo.size = repoSize.toInt() + repo.hasUpdatedSize = true + if (selectedMod == repo) + updateModInfo() + } + } + } catch (ignore: IOException) { + /* Parsing of mod size failed, do nothing */ + } + + }.start() + } + rightSideButton.setText(label.tr()) rightSideButton.onClick { rightSideButton.setText("Downloading...".tr()) @@ -426,8 +456,8 @@ class ModManagementScreen( } } - modActionTable.clear() - addModInfoToActionTable(repo) + selectedMod = repo + updateModInfo() } /** Download and install a mod in the background, called both from the right-bottom button and the URL entry popup */ @@ -481,6 +511,7 @@ class ModManagementScreen( * Display single mod metadata, offer additional actions (delete is elsewhere) */ private fun refreshInstalledModActions(mod: Ruleset) { + selectedMod = null modActionTable.clear() // show mod information first addModInfoToActionTable(mod.name, mod.modOptions)