feat: API

This commit is contained in:
Canny 2022-10-06 20:19:25 +03:00
parent bd2424611d
commit 1211fc42a9
No known key found for this signature in database
GPG Key ID: 395CCB0AA979F27B
8 changed files with 80 additions and 120 deletions

View File

@ -114,7 +114,7 @@ dependencies {
val ktorVersion = "2.0.3" val ktorVersion = "2.0.3"
implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-android:$ktorVersion") implementation("io.ktor:ktor-client-android:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")

View File

@ -1,42 +1,25 @@
package app.revanced.manager.api package app.revanced.manager.api
import android.util.Log import android.util.Log
import app.revanced.manager.dto.github.APIRelease import app.revanced.manager.dto.github.Assets
import app.revanced.manager.preferences.PreferencesManager import app.revanced.manager.preferences.PreferencesManager
import app.revanced.manager.repository.GitHubRepository import app.revanced.manager.repository.GitHubRepository
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.util.cio.* import io.ktor.util.cio.*
import io.ktor.utils.io.* import io.ktor.utils.io.*
import kotlinx.serialization.json.Json
import java.io.File import java.io.File
val client = HttpClient(Android) { class API(private val repository: GitHubRepository, private val prefs: PreferencesManager, val client: HttpClient) {
BrowserUserAgent()
install(ContentNegotiation) {
json(Json {
encodeDefaults = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
class API(private val repository: GitHubRepository, private val prefs: PreferencesManager) {
suspend fun findAsset(repo: String, file: String): PatchesAsset { suspend fun findAsset(repo: String, file: String): PatchesAsset {
val release = repository.getLatestRelease(repo) val asset = repository.fetchAssets().tools.findAsset(repo, file) ?: throw MissingAssetException()
val asset = release.assets.findAsset(file) ?: throw MissingAssetException() return PatchesAsset(asset)
return PatchesAsset(release, asset)
} }
private fun List<APIRelease.Asset>.findAsset(file: String) = find { asset -> private fun List<Assets>.findAsset(repo: String, file: String) = find { asset ->
(asset.name.contains(file) && !asset.name.contains("-sources") && !asset.name.contains("-javadoc")) (asset.name.contains(file) && asset.repository.contains(repo))
} }
suspend fun downloadPatchBundle(workdir: File): File { suspend fun downloadPatchBundle(workdir: File): File {
@ -61,28 +44,26 @@ class API(private val repository: GitHubRepository, private val prefs: Preferenc
suspend fun downloadAsset( suspend fun downloadAsset(
workdir: File, workdir: File,
patchesAsset: PatchesAsset assets: PatchesAsset
): Pair<PatchesAsset, File> { ): Pair<PatchesAsset, File> {
val (release, asset) = patchesAsset val out = workdir.resolve("${assets.asset.version}-${assets.asset.name}")
val out = workdir.resolve("${release.tagName}-${asset.name}")
if (out.exists()) { if (out.exists()) {
Log.d( Log.d(
"ReVanced Manager", "ReVanced Manager",
"Skipping downloading asset ${asset.name} because it exists in cache!" "Skipping downloading asset ${assets.asset.name} because it exists in cache!"
) )
return patchesAsset to out return assets to out
} }
Log.d("ReVanced Manager", "Downloading asset ${asset.name}") Log.d("ReVanced Manager", "Downloading asset ${assets.asset.name}")
client.get(asset.downloadUrl) client.get(assets.asset.downloadUrl)
.bodyAsChannel() .bodyAsChannel()
.copyAndClose(out.writeChannel()) .copyAndClose(out.writeChannel())
return patchesAsset to out return assets to out
} }
} }
data class PatchesAsset( data class PatchesAsset(
val release: APIRelease, val asset: Assets
val asset: APIRelease.Asset
) )

View File

@ -1,16 +1,29 @@
package app.revanced.manager.di package app.revanced.manager.di
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.cio.* import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.* import io.ktor.client.plugins.*
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.* import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Dns
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module import org.koin.dsl.module
import java.net.Inet4Address
import java.net.InetAddress
val httpModule = module { val httpModule = module {
fun provideHttpClient() = HttpClient(CIO) { fun provideHttpClient() = HttpClient(OkHttp) {
engine {
config {
dns(object : Dns {
override fun lookup(hostname: String): List<InetAddress> {
val addresses = Dns.SYSTEM.lookup(hostname)
return addresses.filterIsInstance<Inet4Address>()
}
})
}
}
BrowserUserAgent() BrowserUserAgent()
install(ContentNegotiation) { install(ContentNegotiation) {
json(Json { json(Json {

View File

@ -1,22 +0,0 @@
package app.revanced.manager.dto.github
import kotlinx.serialization.Serializable
@Serializable
class APICommit(
val sha: String,
val commit: Object
) {
@Serializable
class Object(
val message: String,
val author: Author,
val committer: Author
)
@Serializable
class Author(
val name: String,
val date: String
)
}

View File

@ -1,11 +0,0 @@
package app.revanced.manager.dto.github
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class APIContributor(
@SerialName("login") val username: String,
@SerialName("avatar_url") val avatarUrl: String,
@SerialName("html_url") val profileUrl: String,
)

View File

@ -1,19 +0,0 @@
package app.revanced.manager.dto.github
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class APIRelease(
@SerialName("tag_name") val tagName: String,
@SerialName("published_at") val publishedAt: String,
val prerelease: Boolean,
val assets: List<Asset>,
val body: String
) {
@Serializable
data class Asset(
@SerialName("browser_download_url") val downloadUrl: String,
val name: String
)
}

View File

@ -1,32 +1,27 @@
package app.revanced.manager.repository package app.revanced.manager.repository
import app.revanced.manager.dto.github.APICommit import app.revanced.manager.dto.github.Tools
import app.revanced.manager.dto.github.APIContributor import app.revanced.manager.dto.github.Repositories
import app.revanced.manager.dto.github.APIRelease
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.request.* import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class GitHubRepository(private val client: HttpClient) { class GitHubRepository(val client: HttpClient) {
suspend fun getLatestRelease(repo: String) = withContext(Dispatchers.IO) {
val res: List<APIRelease> = client.get("$baseUrl/$repo/releases") {
suspend fun fetchAssets() = withContext(Dispatchers.IO) {
client.get("$apiUrl/tools") {
parameter("per_page", 1) parameter("per_page", 1)
}.body() }.body() as Tools
res.first()
}
suspend fun getLatestCommit(repo: String, ref: String) = withContext(Dispatchers.IO) {
client.get("$baseUrl/$repo/commits/$ref") {
parameter("per_page", 1)
}.body() as APICommit
} }
suspend fun getContributors(org: String, repo: String) = withContext(Dispatchers.IO) { suspend fun fetchContributors() = withContext(Dispatchers.IO) {
client.get("$baseUrl/$org/$repo/contributors").body() as List<APIContributor> client.get("$apiUrl/contributors").body() as Repositories
} }
private companion object { private companion object {
private const val baseUrl = "https://api.github.com/repos" private const val apiUrl = "https://revanced-releases-api.afterst0rm.xyz"
} }
} }

View File

@ -1,40 +1,63 @@
package app.revanced.manager.ui.viewmodel package app.revanced.manager.ui.viewmodel
import android.annotation.SuppressLint
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Log
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.dto.github.Assets
import app.revanced.manager.repository.GitHubRepository import app.revanced.manager.repository.GitHubRepository
import app.revanced.manager.util.ghManager import app.revanced.manager.util.ghManager
import app.revanced.manager.util.ghPatcher import app.revanced.manager.util.ghPatcher
import kotlinx.coroutines.runBlocking import app.revanced.manager.util.tag
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class DashboardViewModel(private val repository: GitHubRepository) : ViewModel() { class DashboardViewModel(private val repository: GitHubRepository) : ViewModel() {
var patcherCommitDate by mutableStateOf("") private var _latestPatcherCommit: Assets? by mutableStateOf(null)
private set val patcherCommitDate: String
var managerCommitDate by mutableStateOf("") get() = _latestPatcherCommit?.commitDate ?: "unknown"
private set
private var _latestManagerCommit: Assets? by mutableStateOf(null)
val managerCommitDate: String
get() = _latestManagerCommit?.commitDate ?: "unknown"
init { init {
runBlocking { fetchLastCommit()
patcherCommitDate = commitDateOf(ghPatcher) }
managerCommitDate = commitDateOf(ghManager)
private fun fetchLastCommit() {
viewModelScope.launch {
try {
val repo = repository.fetchAssets()
for (asset in repo.tools) {
when (asset.repository) {
ghPatcher -> {
_latestPatcherCommit = asset
}
ghManager -> {
_latestManagerCommit = asset
}
}
}
} catch (e: Exception) {
Log.e(tag, "Failed to fetch latest patcher release", e)
}
} }
} }
private suspend fun commitDateOf(repo: String, ref: String = "HEAD"): String { private val Assets.commitDate: String
val commit = repository.getLatestCommit(repo, ref).commit get() = DateUtils.getRelativeTimeSpanString(
return DateUtils.getRelativeTimeSpanString( formatter.parse(timestamp)!!.time,
formatter.parse(commit.committer.date)!!.time,
Calendar.getInstance().timeInMillis, Calendar.getInstance().timeInMillis,
DateUtils.MINUTE_IN_MILLIS DateUtils.MINUTE_IN_MILLIS
).toString() ).toString()
}
private companion object { private companion object {
@SuppressLint("ConstantLocale")
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()) val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
} }
} }