Point updater at Forgejo releases and Wolfskin assets
Some checks failed
review/code-gpt Request changes: version equivalence and lost positive asset-selection coverage

This commit is contained in:
justinzeus 2026-10-22 14:47:50 +02:00
parent 7d6f041b55
commit 0f7a23faa8
5 changed files with 238 additions and 72 deletions

View file

@ -576,7 +576,7 @@ sealed interface AppPreference<Pref, T> {
val UpdateUrl =
AppStringPreference<AppPreferences>(
title = R.string.update_url,
defaultValue = "https://api.github.com/repos/damontecres/Wholphin/releases/latest",
defaultValue = "https://git.justinvisser.org/api/v1/repos/justinzeus/wolfskin/releases/latest",
getter = { it.updateUrl },
setter = { prefs, value ->
prefs.update { updateUrl = value }

View file

@ -57,7 +57,7 @@ class UpdateChecker
@param:StandardOkHttpClient private val okHttpClient: OkHttpClient,
) {
companion object {
const val ASSET_NAME = "Wholphin"
const val ASSET_NAME = "Wolfskin"
const val APK_NAME = "$ASSET_NAME.apk"
private const val APK_MIME_TYPE = "application/vnd.android.package-archive"
@ -65,6 +65,52 @@ class UpdateChecker
private val NOTE_REGEX = Regex("<!-- app-note:(.+) -->")
val ACTIVE = BuildConfig.UPDATING_ENABLED
private const val RELEASES_URL = "https://git.justinvisser.org/api/v1/repos/justinzeus/wolfskin/releases?limit=50"
/**
* Find the release in a Forgejo release list whose name parses to the given version
*/
fun findReleaseByVersion(
releases: JsonArray,
version: Version,
): JsonObject? =
releases
.firstOrNull { release ->
Version.tryFromString(release.jsonObject["name"]?.jsonPrimitive?.contentOrNull) == version
}?.jsonObject
/**
* Parse a single release object (Forgejo or GitHub shape) into a [Release], or null if its name is not a version
*/
fun parseRelease(
json: JsonObject,
debug: Boolean,
supportedABIs: List<String> = Build.SUPPORTED_ABIS.toList(),
): Release? {
val name = json["name"]?.jsonPrimitive?.contentOrNull
val version = Version.tryFromString(name)
if (version == null) {
Timber.w("Update version parsing failed. name=$name")
return null
}
val publishedAt = json["published_at"]?.jsonPrimitive?.contentOrNull
val body = json["body"]?.jsonPrimitive?.contentOrNull
val downloadUrl =
json["assets"]?.jsonArray?.let { assets -> getDownloadUrl(assets, debug, supportedABIs) }
Timber.v("version=$version, downloadUrl=$downloadUrl")
val notes =
if (body.isNotNullOrBlank()) {
NOTE_REGEX
.findAll(body)
.map { m ->
m.groupValues[1]
}.toList()
} else {
emptyList()
}
return Release(version, downloadUrl, publishedAt, body, notes)
}
}
/**
@ -127,17 +173,29 @@ class UpdateChecker
return Version.fromString(pkgInfo.versionName!!)
}
/**
* Get the release whose name parses to the given version.
*
* Wolfskin releases are named by `git describe` (e.g. `v1.0.9-2-g3094fc8c`) rather than tagged `vX.Y.Z`,
* so this searches the release list instead of fetching a tag.
*/
suspend fun getRelease(version: Version): Release? {
val url =
"https://api.github.com/repos/damontecres/Wholphin/releases/tags/v${version.major}.${version.minor}.${version.patch}"
val request =
Request
.Builder()
.url(RELEASES_URL)
.get()
.build()
return withContext(WholphinDispatchers.IO) {
val request =
Request
.Builder()
.url(url)
.get()
.build()
getRelease(request)
okHttpClient.newCall(request).execute().use {
if (it.isSuccessful) {
val releases = Json.parseToJsonElement(it.body.string()).jsonArray
findReleaseByVersion(releases, version)?.let { json -> parseRelease(json, BuildConfig.DEBUG) }
} else {
Timber.w("Release lookup failed ${it.code}: ${it.message}")
null
}
}
}
}
@ -158,36 +216,11 @@ class UpdateChecker
private fun getRelease(request: Request): Release? {
return okHttpClient.newCall(request).execute().use {
if (it.isSuccessful) {
val result = Json.parseToJsonElement(it.body.string())
val name = result.jsonObject["name"]?.jsonPrimitive?.contentOrNull
val version = Version.tryFromString(name)
val publishedAt =
result.jsonObject["published_at"]?.jsonPrimitive?.contentOrNull
val body = result.jsonObject["body"]?.jsonPrimitive?.contentOrNull
val downloadUrl =
result.jsonObject["assets"]
?.jsonArray
?.let { assets -> getDownloadUrl(assets, BuildConfig.DEBUG) }
Timber.v("version=$version, downloadUrl=$downloadUrl")
if (version != null) {
val notes =
if (body.isNotNullOrBlank()) {
NOTE_REGEX
.findAll(body)
.map { m ->
m.groupValues[1]
}.toList()
} else {
emptyList()
}
return@use Release(version, downloadUrl, publishedAt, body, notes)
} else {
Timber.w("Update version parsing failed. name=$name")
}
parseRelease(Json.parseToJsonElement(it.body.string()).jsonObject, BuildConfig.DEBUG)
} else {
Timber.w("Update check failed ${it.code}: ${it.message}")
null
}
return@use null
}
}