diff --git a/app/src/main/java/app/revanced/manager/data/room/Converters.kt b/app/src/main/java/app/revanced/manager/data/room/Converters.kt
index f20e2636..f8aa073d 100644
--- a/app/src/main/java/app/revanced/manager/data/room/Converters.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/Converters.kt
@@ -7,10 +7,7 @@ import java.io.File
class Converters {
@TypeConverter
- fun sourceFromString(value: String) = when(value) {
- Source.Local.SENTINEL -> Source.Local
- else -> Source.Remote(Url(value))
- }
+ fun sourceFromString(value: String) = Source.from(value)
@TypeConverter
fun sourceToString(value: Source) = value.toString()
diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
index 391e4aa3..b801800a 100644
--- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
+++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt
@@ -10,9 +10,23 @@ sealed class Source {
override fun toString() = SENTINEL
}
+ object API : Source() {
+ const val SENTINEL = "manager://api"
+
+ override fun toString() = SENTINEL
+ }
+
data class Remote(val url: Url) : Source() {
override fun toString() = url.toString()
}
+
+ companion object {
+ fun from(value: String) = when(value) {
+ Local.SENTINEL -> Local
+ API.SENTINEL -> API
+ else -> Remote(Url(value))
+ }
+ }
}
data class VersionInfo(
diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt
index 5edfa618..43f86e72 100644
--- a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt
+++ b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt
@@ -11,10 +11,10 @@ class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSour
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) {
withContext(Dispatchers.IO) {
patches?.let {
- Files.copy(it, patchesJar.toPath(), StandardCopyOption.REPLACE_EXISTING)
+ Files.copy(it, patchesFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
integrations?.let {
- Files.copy(it, this@LocalPatchBundle.integrations.toPath(), StandardCopyOption.REPLACE_EXISTING)
+ Files.copy(it, this@LocalPatchBundle.integrationsFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt
index 6117df71..609bdf40 100644
--- a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt
+++ b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt
@@ -12,19 +12,19 @@ import java.io.File
*/
@Stable
sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) {
- protected val patchesJar = directory.resolve("patches.jar")
- protected val integrations = directory.resolve("integrations.apk")
+ protected val patchesFile = directory.resolve("patches.jar")
+ protected val integrationsFile = directory.resolve("integrations.apk")
/**
* Returns true if the bundle has been downloaded to local storage.
*/
- fun hasInstalled() = patchesJar.exists()
+ fun hasInstalled() = patchesFile.exists()
private fun load(): State {
if (!hasInstalled()) return State.Missing
return try {
- State.Loaded(PatchBundle(patchesJar, integrations.takeIf(File::exists)))
+ State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
} catch (t: Throwable) {
State.Failed(t)
}
@@ -48,6 +48,7 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
companion object {
val PatchBundleSource.isDefault get() = uid == 0
- fun PatchBundleSource.propsOrNullFlow() = (this as? RemotePatchBundle)?.propsFlow() ?: flowOf(null)
+ val PatchBundleSource.asRemoteOrNull get() = this as? RemotePatchBundle<*>
+ fun PatchBundleSource.propsOrNullFlow() = asRemoteOrNull?.propsFlow() ?: flowOf(null)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt
index 118c66ba..af2fe1f3 100644
--- a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt
+++ b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt
@@ -1,44 +1,66 @@
package app.revanced.manager.domain.bundles
import androidx.compose.runtime.Stable
+import app.revanced.manager.data.room.bundles.VersionInfo
+import app.revanced.manager.domain.repository.Assets
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
-import app.revanced.manager.network.api.ManagerAPI
+import app.revanced.manager.domain.repository.ReVancedRepository
+import app.revanced.manager.network.dto.BundleInfo
+import app.revanced.manager.network.service.HttpService
+import app.revanced.manager.network.utils.getOrThrow
+import app.revanced.manager.util.ghIntegrations
+import app.revanced.manager.util.ghPatches
+import io.ktor.client.request.url
+import io.ktor.http.Url
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.File
@Stable
-class RemotePatchBundle(name: String, id: Int, directory: File, val apiUrl: String) :
+sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpoint: String) :
PatchBundleSource(name, id, directory), KoinComponent {
private val configRepository: PatchBundlePersistenceRepository by inject()
- private val api: ManagerAPI by inject()
+ protected val http: HttpService by inject()
- private suspend fun currentVersion() = configRepository.getProps(uid).first().versionInfo
- private suspend fun saveVersion(patches: String, integrations: String) =
- configRepository.updateVersion(uid, patches, integrations)
+ protected abstract suspend fun download(metadata: Meta)
+ protected abstract suspend fun getLatestMetadata(): Meta
+ protected abstract fun getVersionInfo(metadata: Meta): VersionInfo
- suspend fun downloadLatest() = withContext(Dispatchers.IO) {
- api.downloadBundle(apiUrl, patchesJar, integrations).also { (patchesVer, integrationsVer) ->
- saveVersion(patchesVer, integrationsVer)
- reload()
- }
+ suspend fun downloadLatest() {
+ download(getLatestMetadata())
+ }
- return@withContext
+ protected suspend fun downloadAssets(assets: Map) = coroutineScope {
+ assets.forEach { (asset, file) ->
+ launch {
+ http.download(file) {
+ url(asset)
+ }
+ }
+ }
}
suspend fun update(): Boolean = withContext(Dispatchers.IO) {
- val current = currentVersion().let { it.patches to it.integrations }
- if (!hasInstalled() || current != api.getLatestBundleVersion(apiUrl)) {
- downloadLatest()
- true
- } else false
+ val metadata = getLatestMetadata()
+ if (hasInstalled() && getVersionInfo(metadata) == currentVersion()) {
+ return@withContext false
+ }
+
+ download(metadata)
+ true
}
- suspend fun deleteLocalFiles() = withContext(Dispatchers.IO) {
- arrayOf(patchesJar, integrations).forEach(File::delete)
+ private suspend fun currentVersion() = configRepository.getProps(uid).first().versionInfo
+ protected suspend fun saveVersion(patches: String, integrations: String) =
+ configRepository.updateVersion(uid, patches, integrations)
+
+ suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
+ arrayOf(patchesFile, integrationsFile).forEach(File::delete)
reload()
}
@@ -49,4 +71,60 @@ class RemotePatchBundle(name: String, id: Int, directory: File, val apiUrl: Stri
companion object {
const val updateFailMsg = "Failed to update patch bundle(s)"
}
+}
+
+class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
+ RemotePatchBundle(name, id, directory, endpoint) {
+ override suspend fun getLatestMetadata() = withContext(Dispatchers.IO) {
+ http.request {
+ url(endpoint)
+ }.getOrThrow()
+ }
+
+ override fun getVersionInfo(metadata: BundleInfo) =
+ VersionInfo(metadata.patches.version, metadata.integrations.version)
+
+ override suspend fun download(metadata: BundleInfo) = withContext(Dispatchers.IO) {
+ val (patches, integrations) = metadata
+ downloadAssets(
+ mapOf(
+ patches.url to patchesFile,
+ integrations.url to integrationsFile
+ )
+ )
+
+ saveVersion(patches.version, integrations.version)
+ reload()
+ }
+}
+
+class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) :
+ RemotePatchBundle(name, id, directory, endpoint) {
+ private val api: ReVancedRepository by inject()
+
+ override suspend fun getLatestMetadata() = api.getAssets()
+ override fun getVersionInfo(metadata: Assets) = metadata.let { (patches, integrations) ->
+ VersionInfo(
+ patches.version,
+ integrations.version
+ )
+ }
+
+ override suspend fun download(metadata: Assets) = withContext(Dispatchers.IO) {
+ val (patches, integrations) = metadata
+ downloadAssets(
+ mapOf(
+ patches.downloadUrl to patchesFile,
+ integrations.downloadUrl to integrationsFile
+ )
+ )
+
+ saveVersion(patches.version, integrations.version)
+ reload()
+ }
+
+ private companion object {
+ operator fun Assets.component1() = find(ghPatches, ".jar")
+ operator fun Assets.component2() = find(ghIntegrations, ".apk")
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt
index e7a21e42..c33e7ba2 100644
--- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt
+++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt
@@ -16,7 +16,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
uid = 0,
name = "Main",
versionInfo = VersionInfo(),
- source = Source.Remote(Url("manager://api")),
+ source = Source.API,
autoUpdate = false
)
}
@@ -33,25 +33,23 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
suspend fun reset() = dao.reset()
- suspend fun create(name: String, source: Source, autoUpdate: Boolean = false): Int {
- val uid = generateUid()
- dao.add(
- PatchBundleEntity(
- uid = uid,
- name = name,
- versionInfo = VersionInfo(),
- source = source,
- autoUpdate = autoUpdate
- )
- )
- return uid
- }
+ suspend fun create(name: String, source: Source, autoUpdate: Boolean = false) =
+ PatchBundleEntity(
+ uid = generateUid(),
+ name = name,
+ versionInfo = VersionInfo(),
+ source = source,
+ autoUpdate = autoUpdate
+ ).also {
+ dao.add(it)
+ }
suspend fun delete(uid: Int) = dao.remove(uid)
suspend fun updateVersion(uid: Int, patches: String, integrations: String) =
dao.updateVersion(uid, patches, integrations)
+
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
fun getProps(id: Int) = dao.getPropsById(id).distinctUntilChanged()
diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt
index a9419194..d10928ea 100644
--- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt
+++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt
@@ -5,14 +5,14 @@ import android.content.Context
import android.util.Log
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.data.room.bundles.PatchBundleEntity
+import app.revanced.manager.domain.bundles.APIPatchBundle
+import app.revanced.manager.domain.bundles.JsonPatchBundle
import app.revanced.manager.data.room.bundles.Source as SourceInfo
-import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.bundles.LocalPatchBundle
import app.revanced.manager.domain.bundles.RemotePatchBundle
import app.revanced.manager.domain.bundles.PatchBundleSource
import app.revanced.manager.util.flatMapLatestAndCombine
import app.revanced.manager.util.tag
-import io.ktor.http.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
@@ -21,18 +21,17 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
-import java.io.File
import java.io.InputStream
class PatchBundleRepository(
app: Application,
private val persistenceRepo: PatchBundlePersistenceRepository,
private val networkInfo: NetworkInfo,
- private val prefs: PreferencesManager
) {
private val bundlesDir = app.getDir("patch_bundles", Context.MODE_PRIVATE)
- private val _sources: MutableStateFlow