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"
implementation("io.ktor:ktor-client-core:$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-serialization-kotlinx-json:$ktorVersion")

View File

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

View File

@ -1,16 +1,29 @@
package app.revanced.manager.di
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.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
import okhttp3.Dns
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import java.net.Inet4Address
import java.net.InetAddress
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()
install(ContentNegotiation) {
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
import app.revanced.manager.dto.github.APICommit
import app.revanced.manager.dto.github.APIContributor
import app.revanced.manager.dto.github.APIRelease
import app.revanced.manager.dto.github.Tools
import app.revanced.manager.dto.github.Repositories
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class GitHubRepository(private val client: HttpClient) {
suspend fun getLatestRelease(repo: String) = withContext(Dispatchers.IO) {
val res: List<APIRelease> = client.get("$baseUrl/$repo/releases") {
class GitHubRepository(val client: HttpClient) {
suspend fun fetchAssets() = withContext(Dispatchers.IO) {
client.get("$apiUrl/tools") {
parameter("per_page", 1)
}.body()
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
}.body() as Tools
}
suspend fun getContributors(org: String, repo: String) = withContext(Dispatchers.IO) {
client.get("$baseUrl/$org/$repo/contributors").body() as List<APIContributor>
suspend fun fetchContributors() = withContext(Dispatchers.IO) {
client.get("$apiUrl/contributors").body() as Repositories
}
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
import android.annotation.SuppressLint
import android.text.format.DateUtils
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
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.util.ghManager
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.util.*
class DashboardViewModel(private val repository: GitHubRepository) : ViewModel() {
var patcherCommitDate by mutableStateOf("")
private set
var managerCommitDate by mutableStateOf("")
private set
private var _latestPatcherCommit: Assets? by mutableStateOf(null)
val patcherCommitDate: String
get() = _latestPatcherCommit?.commitDate ?: "unknown"
private var _latestManagerCommit: Assets? by mutableStateOf(null)
val managerCommitDate: String
get() = _latestManagerCommit?.commitDate ?: "unknown"
init {
runBlocking {
patcherCommitDate = commitDateOf(ghPatcher)
managerCommitDate = commitDateOf(ghManager)
fetchLastCommit()
}
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 {
val commit = repository.getLatestCommit(repo, ref).commit
return DateUtils.getRelativeTimeSpanString(
formatter.parse(commit.committer.date)!!.time,
private val Assets.commitDate: String
get() = DateUtils.getRelativeTimeSpanString(
formatter.parse(timestamp)!!.time,
Calendar.getInstance().timeInMillis,
DateUtils.MINUTE_IN_MILLIS
).toString()
}
private companion object {
@SuppressLint("ConstantLocale")
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
}
}