Support automatically download ci

This commit is contained in:
Dude so hot committed 2026-10-08 16:19:03 +08:00
1 parent de2321ce8f
commit d901326640
10 files changed
+84 -110

No files matched your search

-3
View File
@@ -54,9 +54,6 @@ jobs:
- name: Spotless Check - name: Spotless Check
run: ./gradlew spotlessCheck run: ./gradlew spotlessCheck
- name: Lint Check
run: ./gradlew lintDefaultRelease
default: default:
runs-on: nekomoee runs-on: nekomoee
steps: steps:
+3 -3
View File
@@ -47,9 +47,9 @@ android {
commandLine = "git log -1 --format=%ct".split(' ') commandLine = "git log -1 --format=%ct".split(' ')
}.standardOutput.asText.get().trim() }.standardOutput.asText.get().trim()
val repoUrl = providers.exec { val repoName = providers.exec {
commandLine = "git remote get-url origin".split(' ') commandLine = "git remote get-url origin".split(' ')
}.standardOutput.asText.get().trim().removeSuffix(".git") }.standardOutput.asText.get().trim().removeSuffix(".git").removePrefix("https://codeberg.org/")
val snapshot = !hasProperty("release") val snapshot = !hasProperty("release")
@@ -65,7 +65,7 @@ android {
buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName\"") buildConfigField("String", "RAW_VERSION_NAME", "\"$versionName\"")
buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"") buildConfigField("String", "COMMIT_SHA", "\"$commitSha\"")
buildConfigField("long", "COMMIT_TIME", commitTime) buildConfigField("long", "COMMIT_TIME", commitTime)
buildConfigField("String", "REPO_URL", "\"$repoUrl\"") buildConfigField("String", "REPO_NAME", "\"$repoName\"")
ndk { ndk {
if (isRelease) { if (isRelease) {
abiFilters.addAll(supportedAbis) abiFilters.addAll(supportedAbis)
@@ -36,7 +36,7 @@ import com.hippo.ehviewer.ui.main.NavigationIcon
import com.hippo.ehviewer.ui.tools.DialogState import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel
import com.hippo.ehviewer.updater.AppUpdater import com.hippo.ehviewer.updater.AppUpdater
import com.hippo.ehviewer.updater.Release import com.hippo.ehviewer.updater.EhRelease
import com.hippo.ehviewer.util.AppConfig import com.hippo.ehviewer.util.AppConfig
import com.hippo.ehviewer.util.ReadableTime import com.hippo.ehviewer.util.ReadableTime
import com.hippo.ehviewer.util.displayString import com.hippo.ehviewer.util.displayString
@@ -49,7 +49,7 @@ import moe.tarsin.navigate
import moe.tarsin.snackbar import moe.tarsin.snackbar
import moe.tarsin.string import moe.tarsin.string
const val REPO_URL = BuildConfig.REPO_URL const val REPO_URL = "https://codeberg.org/${BuildConfig.REPO_NAME}"
const val RELEASE_URL = "$REPO_URL/releases" const val RELEASE_URL = "$REPO_URL/releases"
@Composable @Composable
@@ -124,7 +124,7 @@ fun AnimatedVisibilityScope.AboutScreen(navigator: DestinationsNavigator) = Scre
} }
context(_: Context, _: DialogState) context(_: Context, _: DialogState)
suspend fun showNewVersion(release: Release) { suspend fun showNewVersion(release: EhRelease) {
awaitConfirmationOrCancel( awaitConfirmationOrCancel(
confirmText = R.string.download, confirmText = R.string.download,
title = R.string.new_version_available, title = R.string.new_version_available,
@@ -6,12 +6,9 @@ import com.hippo.ehviewer.EhApplication.Companion.ktorClient
import com.hippo.ehviewer.Settings import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.executeAndParseAs import com.hippo.ehviewer.client.executeAndParseAs
import com.hippo.ehviewer.spider.timeoutBySpeed import com.hippo.ehviewer.spider.timeoutBySpeed
import com.hippo.ehviewer.ui.settings.RELEASE_URL
import com.hippo.ehviewer.ui.settings.REPO_URL
import com.hippo.ehviewer.util.copyTo import com.hippo.ehviewer.util.copyTo
import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.accept import io.ktor.client.request.accept
import io.ktor.client.request.bearerAuth
import io.ktor.client.request.prepareGet import io.ktor.client.request.prepareGet
import io.ktor.client.statement.bodyAsChannel import io.ktor.client.statement.bodyAsChannel
import io.ktor.http.ContentType import io.ktor.http.ContentType
@@ -23,17 +20,18 @@ import kotlin.time.Instant
import kotlinx.io.asSource import kotlinx.io.asSource
import moe.tarsin.coroutines.runSuspendCatching import moe.tarsin.coroutines.runSuspendCatching
import okio.Path import okio.Path
import tachiyomi.data.release.GithubArtifacts import tachiyomi.data.release.Artifacts
import tachiyomi.data.release.GithubCommitComparison import tachiyomi.data.release.CommitComparison
import tachiyomi.data.release.GithubRelease import tachiyomi.data.release.Release
import tachiyomi.data.release.GithubRepo import tachiyomi.data.release.Repository
import tachiyomi.data.release.GithubWorkflowRuns import tachiyomi.data.release.WorkflowRuns
import tachiyomi.data.release.downloadLink
private const val API_URL = "$REPO_URL/actions/runs" const val API_URL = "https://codeberg.org/api/v1/repos/${BuildConfig.REPO_NAME}"
const val LATEST_RELEASE_URL = "$RELEASE_URL/latest" const val LATEST_RELEASE_URL = "$API_URL/releases/latest"
object AppUpdater { object AppUpdater {
suspend fun checkForUpdate(forceCheck: Boolean = false): Release? { suspend fun checkForUpdate(forceCheck: Boolean = false): EhRelease? {
val now = Clock.System.now() val now = Clock.System.now()
val last = Instant.fromEpochSeconds(Settings.lastUpdateTime) val last = Instant.fromEpochSeconds(Settings.lastUpdateTime)
val interval = Settings.updateIntervalDays.value val interval = Settings.updateIntervalDays.value
@@ -41,31 +39,30 @@ object AppUpdater {
Settings.lastUpdateTime = now.epochSeconds Settings.lastUpdateTime = now.epochSeconds
if (Settings.useCIUpdateChannel.value) { if (Settings.useCIUpdateChannel.value) {
val curSha = BuildConfig.COMMIT_SHA val curSha = BuildConfig.COMMIT_SHA
val branch = ghStatement(API_URL).executeAndParseAs<GithubRepo>().defaultBranch val branch = statement(API_URL).executeAndParseAs<Repository>().defaultBranch
val workflowRunsUrl = "$API_URL/actions/workflows/ci.yml/runs?branch=$branch&event=push&status=success&per_page=1" val run = statement("$API_URL/actions/runs?event=push&status=success&ref=refs/heads/$branch").executeAndParseAs<WorkflowRuns>().workflowRuns[0]
val workflowRun = ghStatement(workflowRunsUrl).executeAndParseAs<GithubWorkflowRuns>().workflowRuns[0] val shortSha = run.headSha.take(7)
val shortSha = workflowRun.headSha.take(7)
if (shortSha != curSha) { if (shortSha != curSha) {
val artifacts = ghStatement(workflowRun.artifactsUrl).executeAndParseAs<GithubArtifacts>() val artifacts = statement("$API_URL/actions/runs/${run.id}/artifacts").executeAndParseAs<Artifacts>()
val archiveUrl = artifacts.getDownloadLink() val archiveUrl = artifacts.downloadLink
val changelog = runSuspendCatching { val changelog = runSuspendCatching {
val commitComparisonUrl = "$API_URL/compare/$curSha...$shortSha" val commitComparisonUrl = "$API_URL/compare/$curSha...$shortSha"
val result = ghStatement(commitComparisonUrl).executeAndParseAs<GithubCommitComparison>() val result = statement(commitComparisonUrl).executeAndParseAs<CommitComparison>()
// TODO: Prettier format, Markdown? // TODO: Prettier format, Markdown?
result.commits.joinToString("\n") { commit -> result.commits.joinToString("\n") { commit ->
"${commit.commit.message.takeWhile { it != '\n' }} (@${commit.commit.author.name})" "${commit.commit.message.takeWhile { it != '\n' }} (@${commit.commit.author.name})"
} }
}.getOrDefault(workflowRun.title) }.getOrDefault(run.title)
return Release(shortSha, changelog, archiveUrl) return EhRelease(shortSha, changelog, archiveUrl)
} }
} else { } else {
val curVersion = BuildConfig.RAW_VERSION_NAME val curVersion = BuildConfig.RAW_VERSION_NAME
val release = ghStatement(LATEST_RELEASE_URL).executeAndParseAs<GithubRelease>() val release = statement(LATEST_RELEASE_URL).executeAndParseAs<Release>()
val latestVersion = release.version val latestVersion = release.version
val description = release.info val description = release.info
val downloadUrl = release.getDownloadLink() val downloadUrl = release.getDownloadLink()
if (latestVersion != curVersion) { if (latestVersion != curVersion) {
return Release(latestVersion, description, downloadUrl) return EhRelease(latestVersion, description, downloadUrl)
} }
} }
} }
@@ -77,7 +74,7 @@ object AppUpdater {
timeoutBySpeed( timeoutBySpeed(
url, url,
{ {
ghStatement(url) { statement(url) {
// https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#get-a-release-asset // https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#get-a-release-asset
if (!isZip) accept(ContentType.Application.OctetStream) if (!isZip) accept(ContentType.Application.OctetStream)
it() it()
@@ -100,22 +97,14 @@ object AppUpdater {
} }
} }
private suspend inline fun ghStatement( suspend inline fun statement(
url: String, url: String,
builder: HttpRequestBuilder.() -> Unit = {}, builder: HttpRequestBuilder.() -> Unit = {},
) = ktorClient.prepareGet(url) { ) = ktorClient.prepareGet(url) {
bearerAuth(GithubTokenParts.joinToString("_"))
apply(builder) apply(builder)
} }
private val GithubTokenParts = arrayOf( data class EhRelease(
"github",
"pat",
"11A4H2ACI0iGDuL1O6wPYW",
"OTFg8xaCNUwR1NHaJE1AT3LoYPfz6bouI7E7ReLf8GjIRFHCL5UsHL9EnWP",
)
data class Release(
val version: String, val version: String,
val changelog: String, val changelog: String,
val downloadLink: String, val downloadLink: String,
@@ -39,13 +39,11 @@ object AppConfig {
private const val CRASH = "crash" private const val CRASH = "crash"
private const val TAG_TRANSLATIONS = "tag-translations" private const val TAG_TRANSLATIONS = "tag-translations"
private val abi = Build.SUPPORTED_ABIS[0].takeIf { private val abi = Build.SUPPORTED_ABIS[0].takeIf { it in ["arm64-v8a", "x86_64", "armeabi-v7a"] } ?: "universal"
it in setOf("arm64-v8a", "x86_64", "armeabi-v7a")
} ?: "universal"
val isBenchmark = "nonMinified" in BuildConfig.BUILD_TYPE || "benchmark" in BuildConfig.BUILD_TYPE val isBenchmark = "nonMinified" in BuildConfig.BUILD_TYPE || "benchmark" in BuildConfig.BUILD_TYPE
fun matchVariant(name: String) = name.contains(BuildConfig.FLAVOR) && name.contains(abi) fun matchVariant(name: String) = abi in name
val commitTime = ParserUtils.formatDate(BuildConfig.COMMIT_TIME * 1000) val commitTime = ParserUtils.formatDate(BuildConfig.COMMIT_TIME * 1000)
@@ -0,0 +1,15 @@
package tachiyomi.data.release
import kotlinx.serialization.Serializable
@Serializable
data class CommitComparison(val commits: List<Commit>)
@Serializable
data class Commit(val commit: CommitDetail)
@Serializable
data class CommitDetail(val author: CommitAuthor, val message: String)
@Serializable
data class CommitAuthor(val name: String)
@@ -1,15 +0,0 @@
package tachiyomi.data.release
import kotlinx.serialization.Serializable
@Serializable
data class GithubCommitComparison(val commits: List<GithubCommit>)
@Serializable
data class GithubCommit(val commit: GithubCommitDetail)
@Serializable
data class GithubCommitDetail(val author: GithubCommitAuthor, val message: String)
@Serializable
data class GithubCommitAuthor(val name: String)
@@ -1,40 +0,0 @@
package tachiyomi.data.release
import com.hippo.ehviewer.util.AppConfig
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GithubRepo(
@SerialName("default_branch") val defaultBranch: String,
)
@Serializable
data class GithubWorkflowRuns(
@SerialName("workflow_runs") val workflowRuns: List<GithubWorkflowRun>,
)
@Serializable
data class GithubWorkflowRun(
@SerialName("head_sha") val headSha: String,
@SerialName("display_title") val title: String,
@SerialName("artifacts_url") val artifactsUrl: String,
)
@Serializable
data class GithubArtifacts(
@SerialName("artifacts") val artifacts: List<GithubArtifact>,
) {
fun getDownloadLink(): String {
// The default order is upload order, so we need to sort it
return artifacts.sortedBy { it.name }.run {
find { AppConfig.matchVariant(it.name) } ?: this[0]
}.downloadLink
}
}
@Serializable
data class GithubArtifact(
val name: String,
@SerialName("archive_download_url") val downloadLink: String,
)
@@ -4,15 +4,12 @@ import com.hippo.ehviewer.util.AppConfig
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/**
* Contains information about the latest release from GitHub.
*/
@Serializable @Serializable
data class GithubRelease( data class Release(
@SerialName("tag_name") val version: String, @SerialName("tag_name") val version: String,
@SerialName("body") val info: String, @SerialName("body") val info: String,
@SerialName("html_url") val releaseLink: String, @SerialName("html_url") val releaseLink: String,
@SerialName("assets") val assets: List<GitHubAssets>, @SerialName("assets") val assets: List<Assets>,
) { ) {
fun getDownloadLink(): String { fun getDownloadLink(): String {
val asset = assets.find { AppConfig.matchVariant(it.name) } ?: assets[0] val asset = assets.find { AppConfig.matchVariant(it.name) } ?: assets[0]
@@ -20,8 +17,10 @@ data class GithubRelease(
} }
} }
/**
* Assets class containing download url.
*/
@Serializable @Serializable
data class GitHubAssets(val url: String, val name: String) data class Assets(
@SerialName("browser_download_url")
val url: String,
val name: String,
)
@@ -0,0 +1,31 @@
package tachiyomi.data.release
import com.ehviewer.core.util.unreachable
import com.hippo.ehviewer.util.AppConfig
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Repository(@SerialName("default_branch") val defaultBranch: String)
@Serializable
data class WorkflowRuns(@SerialName("workflow_runs") val workflowRuns: List<WorkflowRun>)
@Serializable
data class WorkflowRun(
@SerialName("commit_sha") val headSha: String,
@SerialName("title") val title: String,
@SerialName("id") val id: String,
)
typealias Artifacts = List<Artifact>
val Artifacts.downloadLink
get() = (find { AppConfig.matchVariant(it.name) } ?: unreachable()).downloadLink
@Serializable
data class Artifact(
val name: String,
@SerialName("archive_download_url")
val downloadLink: String,
)