feat: custom sources

This commit is contained in:
Canny 2022-10-12 16:19:21 +03:00 committed by GitHub
parent 69df08e0ed
commit 03fea253f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 247 additions and 143 deletions

View File

@ -1,81 +0,0 @@
package app.revanced.manager.api
import android.util.Log
import app.revanced.manager.dto.github.Assets
import app.revanced.manager.preferences.PreferencesManager
import app.revanced.manager.repository.GitHubRepository
import com.vk.knet.core.Knet
import com.vk.knet.core.http.HttpMethod
import com.vk.knet.core.http.HttpRequest
import com.vk.knet.cornet.CronetKnetEngine
import kotlinx.coroutines.*
import java.io.File
class API(private val repository: GitHubRepository, private val prefs: PreferencesManager, cronet: CronetKnetEngine) {
val client = Knet.Build(cronet)
suspend fun findAsset(repo: String, file: String): PatchesAsset {
val asset = repository.fetchAssets().tools.findAsset(repo, file) ?: throw MissingAssetException()
return PatchesAsset(asset)
}
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 {
return try {
withContext(Dispatchers.IO) {
val (_, out) = downloadAsset(
workdir,
findAsset(prefs.srcPatches.toString(), ".jar")
)
out
}
} catch (e: Exception) {
throw Exception("Failed to download patch bundle", e)
}
}
suspend fun downloadIntegrations(workdir: File): File {
return try {
val (_, out) = downloadAsset(
workdir,
findAsset(prefs.srcIntegrations.toString(), ".apk")
)
out
} catch (e: Exception) {
throw Exception("Failed to download integrations", e)
}
}
fun downloadAsset(
workdir: File,
assets: PatchesAsset
): Pair<PatchesAsset, File> {
val out = workdir.resolve("${assets.asset.version}-${assets.asset.name}")
if (out.exists()) {
Log.d(
"ReVanced Manager",
"Skipping downloading asset ${assets.asset.name} because it exists in cache!"
)
return assets to out
}
Log.d("ReVanced Manager", "Downloading asset ${assets.asset.name}")
val file = client.execute(
HttpRequest(
HttpMethod.GET,
assets.asset.downloadUrl
)
).body!!.asBytes()
out.writeBytes(file)
return assets to out
}
}
data class PatchesAsset(
val asset: Assets
)
class MissingAssetException : Exception()

View File

@ -0,0 +1,74 @@
package app.revanced.manager.api
import android.util.Log
import app.revanced.manager.dto.github.Release
import app.revanced.manager.util.tag
import com.vk.knet.core.Knet
import com.vk.knet.core.http.HttpMethod
import com.vk.knet.core.http.HttpRequest
import com.vk.knet.cornet.CronetKnetEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.File
class GitHubAPI(cronet: CronetKnetEngine, val json: Json) {
private val client = Knet.Build(cronet)
private suspend fun findAsset(repo: String, file: String): PatchesAsset {
val release = getLatestRelease(repo)
val asset = release.assets.findAsset(file) ?: throw MissingAssetException()
return PatchesAsset(release, asset)
}
private fun List<Release.Asset>.findAsset(file: String) = find { asset ->
(asset.name.contains(file) && !asset.name.contains("-sources") && !asset.name.contains("-javadoc"))
}
suspend fun downloadAsset(
workdir: File,
repo: String,
name: String
): File {
val asset = findAsset(repo, name).asset
val out = workdir.resolve(asset.name)
if (out.exists()) {
Log.d(
tag,
"Skipping downloading asset ${asset.name} because it exists in cache!"
)
return out
}
Log.d(tag, "Downloading asset ${asset.name} from GitHub API.")
val file = client.execute(
HttpRequest(
HttpMethod.GET,
asset.downloadUrl
)
).body!!.asBytes()
out.writeBytes(file)
return out
}
private suspend fun getLatestRelease(repo: String) = withContext(Dispatchers.IO) {
val stream = client.execute(
HttpRequest(
HttpMethod.GET,
"$baseUrl/$repo/releases"
)
).body!!.asString()
val res: List<Release> = json.decodeFromString(stream)
res.first()
}
data class PatchesAsset(
val release: Release,
val asset: Release.Asset
)
private companion object {
private const val baseUrl = "https://api.github.com/repos"
}
}

View File

@ -0,0 +1,88 @@
package app.revanced.manager.api
import android.util.Log
import app.revanced.manager.dto.revanced.Assets
import app.revanced.manager.dto.revanced.Repositories
import app.revanced.manager.dto.revanced.Tools
import app.revanced.manager.util.tag
import com.vk.knet.core.Knet
import com.vk.knet.core.http.HttpMethod
import com.vk.knet.core.http.HttpRequest
import com.vk.knet.cornet.CronetKnetEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.File
class ReVancedAPI(cronet: CronetKnetEngine, val json: Json) {
val client = Knet.Build(cronet)
suspend fun ping(): Boolean {
return try {
withContext(Dispatchers.Default) {
client.execute(HttpRequest(HttpMethod.GET, "$apiUrl/")).statusCode == 200
}
} catch (e: Exception) {
Log.e(tag, "ReVanced API isn't available at the moment. switching to GitHub API")
false
}
}
suspend fun fetchAssets() = withContext(Dispatchers.Default) {
val stream = client.execute(HttpRequest(HttpMethod.GET, "$apiUrl/tools")).body!!.asString()
json.decodeFromString(stream) as Tools
}
suspend fun fetchContributors() = withContext(Dispatchers.Default) {
val stream =
client.execute(HttpRequest(HttpMethod.GET, "$apiUrl/contributors")).body!!.asString()
json.decodeFromString(stream) as Repositories
}
suspend fun findAsset(repo: String, file: String): PatchesAsset {
val asset = fetchAssets().tools.findAsset(repo, file) ?: throw MissingAssetException()
return PatchesAsset(asset)
}
private fun List<Assets>.findAsset(repo: String, file: String) = find { asset ->
(asset.name.contains(file) && asset.repository.contains(repo))
}
suspend fun downloadAsset(
workdir: File,
repo: String,
name: String,
): File {
val asset = findAsset(repo, name).asset
val out = workdir.resolve(asset.name)
if (out.exists()) {
Log.d(
tag,
"Skipping downloading asset ${asset.name} because it exists in cache!"
)
return out
}
Log.d(tag, "Downloading asset ${asset.name} from ReVanced API.")
val file = client.execute(
HttpRequest(
HttpMethod.GET, asset.downloadUrl
)
).body!!.asBytes()
out.writeBytes(file)
return out
}
private companion object {
private const val apiUrl = "https://releases.rvcd.win"
}
}
data class PatchesAsset(
val asset: Assets
)
class MissingAssetException : Exception()

View File

@ -1,11 +1,11 @@
package app.revanced.manager.di package app.revanced.manager.di
import app.revanced.manager.api.API import app.revanced.manager.api.GitHubAPI
import app.revanced.manager.repository.GitHubRepository import app.revanced.manager.api.ReVancedAPI
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module import org.koin.dsl.module
val repositoryModule = module { val repositoryModule = module {
singleOf(::GitHubRepository) singleOf(::GitHubAPI)
singleOf(::API) singleOf(::ReVancedAPI)
} }

View File

@ -6,5 +6,5 @@ import org.koin.androidx.workmanager.dsl.worker
import org.koin.dsl.module import org.koin.dsl.module
val workerModule = module { val workerModule = module {
worker { PatcherWorker( androidContext(), get(), get())} worker { PatcherWorker(androidContext(), get(), get(), get(), get()) }
} }

View File

@ -0,0 +1,15 @@
package app.revanced.manager.dto.github
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class Release(
val assets: List<Asset>,
) {
@Serializable
data class Asset(
@SerialName("browser_download_url") val downloadUrl: String,
@SerialName("name") val name: String
)
}

View File

@ -1,4 +1,4 @@
package app.revanced.manager.dto.github package app.revanced.manager.dto.revanced
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package app.revanced.manager.dto.github package app.revanced.manager.dto.revanced
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -18,14 +18,17 @@ import app.revanced.manager.R
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
import app.revanced.manager.Variables.selectedAppPackage import app.revanced.manager.Variables.selectedAppPackage
import app.revanced.manager.Variables.selectedPatches import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.api.API import app.revanced.manager.api.GitHubAPI
import app.revanced.manager.api.ReVancedAPI
import app.revanced.manager.patcher.aapt.Aapt import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.patcher.aligning.ZipAligner import app.revanced.manager.patcher.aligning.ZipAligner
import app.revanced.manager.patcher.aligning.zip.ZipFile import app.revanced.manager.patcher.aligning.zip.ZipFile
import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry import app.revanced.manager.patcher.aligning.zip.structures.ZipEntry
import app.revanced.manager.patcher.signing.Signer import app.revanced.manager.patcher.signing.Signer
import app.revanced.manager.preferences.PreferencesManager
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.ui.viewmodel.Logging import app.revanced.manager.ui.viewmodel.Logging
import app.revanced.manager.util.ghIntegrations
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
@ -38,8 +41,15 @@ import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption import java.nio.file.StandardCopyOption
import java.time.LocalDateTime
class PatcherWorker(context: Context, parameters: WorkerParameters, private val api: API) : class PatcherWorker(
context: Context,
parameters: WorkerParameters,
private val reVancedAPI: ReVancedAPI,
private val gitHubAPI: GitHubAPI,
private val prefs: PreferencesManager
) :
CoroutineWorker(context, parameters), KoinComponent { CoroutineWorker(context, parameters), KoinComponent {
val tag = "ReVanced Manager" val tag = "ReVanced Manager"
private val workdir = createWorkDir() private val workdir = createWorkDir()
@ -122,8 +132,16 @@ class PatcherWorker(context: Context, parameters: WorkerParameters, private val
val outputFile = File(applicationContext.filesDir, "output.apk") val outputFile = File(applicationContext.filesDir, "output.apk")
val cacheDirectory = workdir.resolve("cache") val cacheDirectory = workdir.resolve("cache")
Logging.log += "Downloading integrations\n" val integrations = if (prefs.srcIntegrations != ghIntegrations || !reVancedAPI.ping()) {
val integrations = api.downloadIntegrations(integrationsCacheDir) Logging.log += "Downloading integrations from GitHub API\n"
gitHubAPI.downloadAsset(integrationsCacheDir, prefs.srcIntegrations!!, ".apk")
} else {
Logging.log += "Downloading integrations from ReVanced API\n"
reVancedAPI.downloadAsset(
integrationsCacheDir,
prefs.srcIntegrations!!, ".apk"
)
}
Logging.log += "Copying base.apk from device\n" Logging.log += "Copying base.apk from device\n"
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -181,7 +199,7 @@ class PatcherWorker(context: Context, parameters: WorkerParameters, private val
Logging.log += "Saving file\n" Logging.log += "Saving file\n"
val result = patcher.save() // compile apk val result = patcher.save() // compile apk
ZipFile(patchedFile).use { fs -> // somehow this function is the most resource intensive ZipFile(patchedFile).use { fs ->
result.dexFiles.forEach { result.dexFiles.forEach {
Logging.log += "Writing dex file ${it.name}\n" Logging.log += "Writing dex file ${it.name}\n"
fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes()) fs.addEntryCompressData(ZipEntry.createWithName(it.name), it.stream.readBytes())
@ -200,7 +218,8 @@ class PatcherWorker(context: Context, parameters: WorkerParameters, private val
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Files.copy( Files.copy(
outputFile.inputStream(), outputFile.inputStream(),
reVancedFolder.resolve(appInfo.packageName + ".apk").toPath(), reVancedFolder.resolve(appInfo.packageName + "-" + LocalDateTime.now() + ".apk")
.toPath(),
StandardCopyOption.REPLACE_EXISTING StandardCopyOption.REPLACE_EXISTING
) )
} }

View File

@ -1,31 +0,0 @@
package app.revanced.manager.repository
import app.revanced.manager.dto.github.Repositories
import app.revanced.manager.dto.github.Tools
import com.vk.knet.core.Knet
import com.vk.knet.core.http.HttpMethod
import com.vk.knet.core.http.HttpRequest
import com.vk.knet.cornet.CronetKnetEngine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
class GitHubRepository(cronet: CronetKnetEngine, private val json: Json) {
private val client = Knet.Build(cronet)
suspend fun fetchAssets() = withContext(Dispatchers.Default) {
val stream = client.execute(HttpRequest(HttpMethod.GET, "$apiUrl/tools")).body!!.asString()
json.decodeFromString(stream) as Tools
}
suspend fun fetchContributors() = withContext(Dispatchers.Default) {
val stream = client.execute(HttpRequest(HttpMethod.GET,"$apiUrl/contributors")).body!!.asString()
json.decodeFromString(stream) as Repositories
}
private companion object {
private const val apiUrl = "https://releases.rvcd.win"
}
}

View File

@ -15,7 +15,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.dto.github.Contributor import app.revanced.manager.dto.revanced.Contributor
import app.revanced.manager.ui.viewmodel.ContributorsViewModel import app.revanced.manager.ui.viewmodel.ContributorsViewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.google.accompanist.flowlayout.FlowRow import com.google.accompanist.flowlayout.FlowRow
@ -62,7 +62,13 @@ fun ContributorsCard(
.combinedClickable( .combinedClickable(
onClick = { vm.openUserProfile(contributor.username) }, onClick = { vm.openUserProfile(contributor.username) },
onLongClick = { onLongClick = {
Toast.makeText(context, contributor.username, Toast.LENGTH_SHORT).show() Toast
.makeText(
context,
contributor.username,
Toast.LENGTH_SHORT
)
.show()
} }
) )
) )

View File

@ -131,12 +131,14 @@ fun PatchesSelectorSubscreen(
} }
} }
} }
} } else {
else { Column(
Column(Modifier.fillMaxSize(), Arrangement.Center, Alignment.CenterHorizontally) { Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text(text = "No compatible patches found.") Text(text = "No compatible patches found.")
} }
} }
} }
else -> LoadingIndicator(null) else -> LoadingIndicator(null)

View File

@ -4,14 +4,14 @@ import android.app.Application
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import app.revanced.manager.dto.github.Contributor import app.revanced.manager.api.ReVancedAPI
import app.revanced.manager.repository.GitHubRepository import app.revanced.manager.dto.revanced.Contributor
import app.revanced.manager.util.* import app.revanced.manager.util.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ContributorsViewModel( class ContributorsViewModel(
private val app: Application, private val app: Application,
private val repository: GitHubRepository private val reVancedAPI: ReVancedAPI
) : ViewModel() { ) : ViewModel() {
val patcherContributorsList = mutableStateListOf<Contributor>() val patcherContributorsList = mutableStateListOf<Contributor>()
val patchesContributorsList = mutableStateListOf<Contributor>() val patchesContributorsList = mutableStateListOf<Contributor>()
@ -21,7 +21,7 @@ class ContributorsViewModel(
private fun loadContributors() { private fun loadContributors() {
viewModelScope.launch { viewModelScope.launch {
val contributors = repository.fetchContributors() val contributors = reVancedAPI.fetchContributors()
contributors.repositories.forEach { repo -> contributors.repositories.forEach { repo ->
when (repo.name) { when (repo.name) {
ghCli -> { ghCli -> {

View File

@ -8,8 +8,8 @@ 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 androidx.lifecycle.viewModelScope
import app.revanced.manager.dto.github.Assets import app.revanced.manager.api.ReVancedAPI
import app.revanced.manager.repository.GitHubRepository import app.revanced.manager.dto.revanced.Assets
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 app.revanced.manager.util.tag import app.revanced.manager.util.tag
@ -19,7 +19,7 @@ import kotlinx.coroutines.withContext
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 reVancedApi: ReVancedAPI) : ViewModel() {
private var _latestPatcherCommit: Assets? by mutableStateOf(null) private var _latestPatcherCommit: Assets? by mutableStateOf(null)
val patcherCommitDate: String val patcherCommitDate: String
get() = _latestPatcherCommit?.commitDate ?: "unknown" get() = _latestPatcherCommit?.commitDate ?: "unknown"
@ -35,7 +35,7 @@ class DashboardViewModel(private val repository: GitHubRepository) : ViewModel()
private fun fetchLastCommit() { private fun fetchLastCommit() {
viewModelScope.launch { viewModelScope.launch {
try { try {
val repo = withContext(Dispatchers.Default) {repository.fetchAssets()} val repo = withContext(Dispatchers.Default) { reVancedApi.fetchAssets() }
for (asset in repo.tools) { for (asset in repo.tools) {
when (asset.repository) { when (asset.repository) {
ghPatcher -> { ghPatcher -> {

View File

@ -11,8 +11,12 @@ import androidx.lifecycle.viewModelScope
import app.revanced.manager.Variables.patches import app.revanced.manager.Variables.patches
import app.revanced.manager.Variables.selectedAppPackage import app.revanced.manager.Variables.selectedAppPackage
import app.revanced.manager.Variables.selectedPatches import app.revanced.manager.Variables.selectedPatches
import app.revanced.manager.api.API import app.revanced.manager.api.GitHubAPI
import app.revanced.manager.api.ReVancedAPI
import app.revanced.manager.preferences.PreferencesManager
import app.revanced.manager.ui.Resource import app.revanced.manager.ui.Resource
import app.revanced.manager.util.ghPatches
import app.revanced.manager.util.tag
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.options import app.revanced.patcher.extensions.PatchExtensions.options
@ -23,9 +27,13 @@ import dalvik.system.DexClassLoader
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
class PatcherScreenViewModel(private val app: Application, private val api: API) : ViewModel() { class PatcherScreenViewModel(
private val app: Application,
private val reVancedApi: ReVancedAPI,
private val gitHubAPI: GitHubAPI,
private val prefs: PreferencesManager
) : ViewModel() {
lateinit var patchBundleFile: String lateinit var patchBundleFile: String
private val tag = "ReVanced Manager"
init { init {
viewModelScope.launch { viewModelScope.launch {
@ -92,7 +100,11 @@ class PatcherScreenViewModel(private val app: Application, private val api: API)
private fun loadPatches() = viewModelScope.launch { private fun loadPatches() = viewModelScope.launch {
try { try {
val file = api.downloadPatchBundle(app.filesDir) val file = if (prefs.srcPatches != ghPatches || !reVancedApi.ping()) {
gitHubAPI.downloadAsset(app.cacheDir, prefs.srcPatches!!, ".jar")
} else {
reVancedApi.downloadAsset(app.cacheDir, prefs.srcPatches!!, ".jar")
}
patchBundleFile = file.absolutePath patchBundleFile = file.absolutePath
loadPatches0() loadPatches0()
} catch (e: Exception) { } catch (e: Exception) {