Change extension repo to extension store and add support for newer extension index format (#3349)
This commit is contained in:
@@ -6,13 +6,13 @@ import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.CategoriesBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.ExtensionRepoBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.ExtensionStoresBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.MangaBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.PreferenceBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.create.creators.SourcesBackupCreator
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSource
|
||||
@@ -49,7 +49,7 @@ class BackupCreator(
|
||||
private val categoriesBackupCreator: CategoriesBackupCreator = CategoriesBackupCreator(),
|
||||
private val mangaBackupCreator: MangaBackupCreator = MangaBackupCreator(),
|
||||
private val preferenceBackupCreator: PreferenceBackupCreator = PreferenceBackupCreator(),
|
||||
private val extensionRepoBackupCreator: ExtensionRepoBackupCreator = ExtensionRepoBackupCreator(),
|
||||
private val extensionStoresBackupCreator: ExtensionStoresBackupCreator = ExtensionStoresBackupCreator(),
|
||||
private val sourcesBackupCreator: SourcesBackupCreator = SourcesBackupCreator(),
|
||||
) {
|
||||
|
||||
@@ -85,7 +85,7 @@ class BackupCreator(
|
||||
backupCategories = backupCategories(options),
|
||||
backupSources = backupSources(backupManga),
|
||||
backupPreferences = backupAppPreferences(options),
|
||||
backupExtensionRepo = backupExtensionRepos(options),
|
||||
backupExtensionStores = backupExtensionStores(options),
|
||||
backupSourcePreferences = backupSourcePreferences(options),
|
||||
)
|
||||
|
||||
@@ -141,10 +141,10 @@ class BackupCreator(
|
||||
return preferenceBackupCreator.createApp(includePrivatePreferences = options.privateSettings)
|
||||
}
|
||||
|
||||
private suspend fun backupExtensionRepos(options: BackupOptions): List<BackupExtensionRepos> {
|
||||
if (!options.extensionRepoSettings) return emptyList()
|
||||
private suspend fun backupExtensionStores(options: BackupOptions): List<BackupExtensionStore> {
|
||||
if (!options.extensionStores) return emptyList()
|
||||
|
||||
return extensionRepoBackupCreator()
|
||||
return extensionStoresBackupCreator()
|
||||
}
|
||||
|
||||
private fun backupSourcePreferences(options: BackupOptions): List<BackupSourcePreferences> {
|
||||
|
||||
@@ -12,7 +12,7 @@ data class BackupOptions(
|
||||
val history: Boolean = true,
|
||||
val readEntries: Boolean = true,
|
||||
val appSettings: Boolean = true,
|
||||
val extensionRepoSettings: Boolean = true,
|
||||
val extensionStores: Boolean = true,
|
||||
val sourceSettings: Boolean = true,
|
||||
val privateSettings: Boolean = false,
|
||||
) {
|
||||
@@ -25,12 +25,12 @@ data class BackupOptions(
|
||||
history,
|
||||
readEntries,
|
||||
appSettings,
|
||||
extensionRepoSettings,
|
||||
extensionStores,
|
||||
sourceSettings,
|
||||
privateSettings,
|
||||
)
|
||||
|
||||
fun canCreate() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings
|
||||
fun canCreate() = libraryEntries || categories || appSettings || extensionStores || sourceSettings
|
||||
|
||||
companion object {
|
||||
val libraryOptions = persistentListOf(
|
||||
@@ -77,9 +77,9 @@ data class BackupOptions(
|
||||
setter = { options, enabled -> options.copy(appSettings = enabled) },
|
||||
),
|
||||
Entry(
|
||||
label = MR.strings.extensionRepo_settings,
|
||||
getter = BackupOptions::extensionRepoSettings,
|
||||
setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) },
|
||||
label = MR.strings.extensionStores,
|
||||
getter = BackupOptions::extensionStores,
|
||||
setter = { options, enabled -> options.copy(extensionStores = enabled) },
|
||||
),
|
||||
Entry(
|
||||
label = MR.strings.source_settings,
|
||||
@@ -102,7 +102,7 @@ data class BackupOptions(
|
||||
history = array[4],
|
||||
readEntries = array[5],
|
||||
appSettings = array[6],
|
||||
extensionRepoSettings = array[7],
|
||||
extensionStores = array[7],
|
||||
sourceSettings = array[8],
|
||||
privateSettings = array[9],
|
||||
)
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.backup.create.creators
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
|
||||
import eu.kanade.tachiyomi.data.backup.models.backupExtensionReposMapper
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionRepoBackupCreator(
|
||||
private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): List<BackupExtensionRepos> {
|
||||
return getExtensionRepos.getAll()
|
||||
.map(backupExtensionReposMapper)
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package eu.kanade.tachiyomi.data.backup.create.creators
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
|
||||
import eu.kanade.tachiyomi.data.backup.models.backupExtensionStoreMapper
|
||||
import mihon.domain.extension.interactor.GetExtensionStores
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionStoresBackupCreator(
|
||||
private val getExtensionStores: GetExtensionStores = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): List<BackupExtensionStore> {
|
||||
return getExtensionStores.get()
|
||||
.map(backupExtensionStoreMapper)
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,5 @@ data class Backup(
|
||||
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
|
||||
@ProtoNumber(104) var backupPreferences: List<BackupPreference> = emptyList(),
|
||||
@ProtoNumber(105) var backupSourcePreferences: List<BackupSourcePreferences> = emptyList(),
|
||||
@ProtoNumber(106) var backupExtensionRepo: List<BackupExtensionRepos> = emptyList(),
|
||||
@ProtoNumber(106) var backupExtensionStores: List<BackupExtensionStore> = emptyList(),
|
||||
)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.backup.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
|
||||
@Serializable
|
||||
class BackupExtensionRepos(
|
||||
@ProtoNumber(1) var baseUrl: String,
|
||||
@ProtoNumber(2) var name: String,
|
||||
@ProtoNumber(3) var shortName: String?,
|
||||
@ProtoNumber(4) var website: String,
|
||||
@ProtoNumber(5) var signingKeyFingerprint: String,
|
||||
)
|
||||
|
||||
val backupExtensionReposMapper = { repo: ExtensionRepo ->
|
||||
BackupExtensionRepos(
|
||||
baseUrl = repo.baseUrl,
|
||||
name = repo.name,
|
||||
shortName = repo.shortName,
|
||||
website = repo.website,
|
||||
signingKeyFingerprint = repo.signingKeyFingerprint,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package eu.kanade.tachiyomi.data.backup.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import mihon.domain.extension.model.ExtensionStore
|
||||
|
||||
@Serializable
|
||||
class BackupExtensionStore(
|
||||
@ProtoNumber(1) var indexUrl: String,
|
||||
@ProtoNumber(2) var name: String,
|
||||
@ProtoNumber(3) var badgeLabel: String?,
|
||||
@ProtoNumber(5) var signingKey: String,
|
||||
@ProtoNumber(4) var contactWebsite: String,
|
||||
@ProtoNumber(6) var contactDiscord: String?,
|
||||
@ProtoNumber(7) var isLegacy: Boolean?,
|
||||
)
|
||||
|
||||
val backupExtensionStoreMapper = { store: ExtensionStore ->
|
||||
BackupExtensionStore(
|
||||
indexUrl = store.indexUrl,
|
||||
name = store.name,
|
||||
badgeLabel = store.badgeLabel,
|
||||
signingKey = store.signingKey,
|
||||
contactWebsite = store.contact.website,
|
||||
contactDiscord = store.contact.discord,
|
||||
isLegacy = store.isLegacy,
|
||||
)
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import android.net.Uri
|
||||
import eu.kanade.tachiyomi.data.backup.BackupDecoder
|
||||
import eu.kanade.tachiyomi.data.backup.BackupNotifier
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupPreference
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences
|
||||
import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesRestorer
|
||||
import eu.kanade.tachiyomi.data.backup.restore.restorers.ExtensionRepoRestorer
|
||||
import eu.kanade.tachiyomi.data.backup.restore.restorers.ExtensionStoreRestorer
|
||||
import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer
|
||||
import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
@@ -32,7 +32,7 @@ class BackupRestorer(
|
||||
|
||||
private val categoriesRestorer: CategoriesRestorer = CategoriesRestorer(),
|
||||
private val preferenceRestorer: PreferenceRestorer = PreferenceRestorer(context),
|
||||
private val extensionRepoRestorer: ExtensionRepoRestorer = ExtensionRepoRestorer(),
|
||||
private val extensionStoreRestorer: ExtensionStoreRestorer = ExtensionStoreRestorer(),
|
||||
private val mangaRestorer: MangaRestorer = MangaRestorer(),
|
||||
) {
|
||||
|
||||
@@ -79,8 +79,8 @@ class BackupRestorer(
|
||||
if (options.appSettings) {
|
||||
restoreAmount += 1
|
||||
}
|
||||
if (options.extensionRepoSettings) {
|
||||
restoreAmount += backup.backupExtensionRepo.size
|
||||
if (options.extensionStores) {
|
||||
restoreAmount += backup.backupExtensionStores.size
|
||||
}
|
||||
if (options.sourceSettings) {
|
||||
restoreAmount += 1
|
||||
@@ -99,8 +99,8 @@ class BackupRestorer(
|
||||
if (options.libraryEntries) {
|
||||
restoreManga(backup.backupManga, if (options.categories) backup.backupCategories else emptyList())
|
||||
}
|
||||
if (options.extensionRepoSettings) {
|
||||
restoreExtensionRepos(backup.backupExtensionRepo)
|
||||
if (options.extensionStores) {
|
||||
restoreExtensionStores(backup.backupExtensionStores)
|
||||
}
|
||||
|
||||
// TODO: optionally trigger online library + tracker update
|
||||
@@ -172,22 +172,22 @@ class BackupRestorer(
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.restoreExtensionRepos(
|
||||
backupExtensionRepo: List<BackupExtensionRepos>,
|
||||
private fun CoroutineScope.restoreExtensionStores(
|
||||
backupExtensionStores: List<BackupExtensionStore>,
|
||||
) = launch {
|
||||
backupExtensionRepo
|
||||
backupExtensionStores
|
||||
.forEach {
|
||||
ensureActive()
|
||||
|
||||
try {
|
||||
extensionRepoRestorer(it)
|
||||
extensionStoreRestorer(it)
|
||||
} catch (e: Exception) {
|
||||
errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}")
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
notifier.showRestoreProgress(
|
||||
context.stringResource(MR.strings.extensionRepo_settings),
|
||||
context.stringResource(MR.strings.extensionStores),
|
||||
restoreProgress,
|
||||
restoreAmount,
|
||||
isSync,
|
||||
|
||||
@@ -8,7 +8,7 @@ data class RestoreOptions(
|
||||
val libraryEntries: Boolean = true,
|
||||
val categories: Boolean = true,
|
||||
val appSettings: Boolean = true,
|
||||
val extensionRepoSettings: Boolean = true,
|
||||
val extensionStores: Boolean = true,
|
||||
val sourceSettings: Boolean = true,
|
||||
) {
|
||||
|
||||
@@ -16,11 +16,11 @@ data class RestoreOptions(
|
||||
libraryEntries,
|
||||
categories,
|
||||
appSettings,
|
||||
extensionRepoSettings,
|
||||
extensionStores,
|
||||
sourceSettings,
|
||||
)
|
||||
|
||||
fun canRestore() = libraryEntries || categories || appSettings || extensionRepoSettings || sourceSettings
|
||||
fun canRestore() = libraryEntries || categories || appSettings || extensionStores || sourceSettings
|
||||
|
||||
companion object {
|
||||
val options = persistentListOf(
|
||||
@@ -40,9 +40,9 @@ data class RestoreOptions(
|
||||
setter = { options, enabled -> options.copy(appSettings = enabled) },
|
||||
),
|
||||
Entry(
|
||||
label = MR.strings.extensionRepo_settings,
|
||||
getter = RestoreOptions::extensionRepoSettings,
|
||||
setter = { options, enabled -> options.copy(extensionRepoSettings = enabled) },
|
||||
label = MR.strings.extensionStores,
|
||||
getter = RestoreOptions::extensionStores,
|
||||
setter = { options, enabled -> options.copy(extensionStores = enabled) },
|
||||
),
|
||||
Entry(
|
||||
label = MR.strings.source_settings,
|
||||
@@ -55,7 +55,7 @@ data class RestoreOptions(
|
||||
libraryEntries = array[0],
|
||||
categories = array[1],
|
||||
appSettings = array[2],
|
||||
extensionRepoSettings = array[3],
|
||||
extensionStores = array[3],
|
||||
sourceSettings = array[4],
|
||||
)
|
||||
}
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package eu.kanade.tachiyomi.data.backup.restore.restorers
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionRepos
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
import tachiyomi.data.Database
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionRepoRestorer(
|
||||
private val database: Database = Injekt.get(),
|
||||
private val getExtensionRepos: GetExtensionRepo = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
backupRepo: BackupExtensionRepos,
|
||||
) {
|
||||
val dbRepos = getExtensionRepos.getAll()
|
||||
val existingReposBySHA = dbRepos.associateBy { it.signingKeyFingerprint }
|
||||
val existingReposByUrl = dbRepos.associateBy { it.baseUrl }
|
||||
|
||||
val urlExists = existingReposByUrl[backupRepo.baseUrl]
|
||||
val shaExists = existingReposBySHA[backupRepo.signingKeyFingerprint]
|
||||
|
||||
if (urlExists != null && urlExists.signingKeyFingerprint != backupRepo.signingKeyFingerprint) {
|
||||
error("Already Exists with different signing key fingerprint")
|
||||
} else if (shaExists != null) {
|
||||
error("${shaExists.name} has the same signing key fingerprint")
|
||||
} else {
|
||||
database.extension_reposQueries.insert(
|
||||
backupRepo.baseUrl,
|
||||
backupRepo.name,
|
||||
backupRepo.shortName,
|
||||
backupRepo.website,
|
||||
backupRepo.signingKeyFingerprint,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package eu.kanade.tachiyomi.data.backup.restore.restorers
|
||||
|
||||
import eu.kanade.tachiyomi.data.backup.models.BackupExtensionStore
|
||||
import tachiyomi.data.Database
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class ExtensionStoreRestorer(
|
||||
private val database: Database = Injekt.get(),
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
backupStore: BackupExtensionStore,
|
||||
) {
|
||||
database.extension_storeQueries.upsert(
|
||||
indexUrl = backupStore.indexUrl,
|
||||
name = backupStore.name,
|
||||
badgeLabel = backupStore.badgeLabel ?: backupStore.name,
|
||||
signingKey = backupStore.signingKey,
|
||||
contactWebsite = backupStore.contactWebsite,
|
||||
contactDiscord = backupStore.contactDiscord,
|
||||
isLegacy = backupStore.isLegacy ?: true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -204,11 +204,11 @@ class ExtensionManager(
|
||||
if (extension.hasUpdate != hasUpdate) {
|
||||
installedExtensionsMap[pkgName] = extension.copy(
|
||||
hasUpdate = hasUpdate,
|
||||
repoUrl = availableExt.repoUrl,
|
||||
store = availableExt.store,
|
||||
)
|
||||
} else {
|
||||
installedExtensionsMap[pkgName] = extension.copy(
|
||||
repoUrl = availableExt.repoUrl,
|
||||
store = availableExt.store,
|
||||
)
|
||||
}
|
||||
changed = true
|
||||
@@ -228,7 +228,7 @@ class ExtensionManager(
|
||||
* @param extension The extension to be installed.
|
||||
*/
|
||||
fun installExtension(extension: Extension.Available): Flow<InstallStep> {
|
||||
return installer.downloadAndInstall(api.getApkUrl(extension), extension)
|
||||
return installer.downloadAndInstall(extension.apkUrl, extension)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,64 +5,29 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import tachiyomi.core.common.preference.Preference
|
||||
import tachiyomi.core.common.preference.PreferenceStore
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
internal class ExtensionApi {
|
||||
|
||||
private val networkService: NetworkHelper by injectLazy()
|
||||
private val repository: ExtensionStoreRepository by injectLazy()
|
||||
|
||||
private val preferenceStore: PreferenceStore by injectLazy()
|
||||
private val getExtensionRepo: GetExtensionRepo by injectLazy()
|
||||
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
|
||||
private val updateExtensionStores: UpdateExtensionStores by injectLazy()
|
||||
private val extensionManager: ExtensionManager by injectLazy()
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val lastExtCheck: Preference<Long> by lazy {
|
||||
preferenceStore.getLong(Preference.appStateKey("last_ext_check"), 0)
|
||||
}
|
||||
|
||||
suspend fun findExtensions(): List<Extension.Available> {
|
||||
return withIOContext {
|
||||
getExtensionRepo.getAll()
|
||||
.map { async { getExtensions(it) } }
|
||||
.awaitAll()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getExtensions(extRepo: ExtensionRepo): List<Extension.Available> {
|
||||
val repoBaseUrl = extRepo.baseUrl
|
||||
return try {
|
||||
val response = networkService.client
|
||||
.newCall(GET("$repoBaseUrl/index.min.json"))
|
||||
.awaitSuccess()
|
||||
|
||||
with(json) {
|
||||
response
|
||||
.parseAs<List<ExtensionJsonObject>>()
|
||||
.toExtensions(repoBaseUrl)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e) { "Failed to get extensions from $repoBaseUrl" }
|
||||
emptyList()
|
||||
}
|
||||
return withIOContext { repository.fetchExtensions() }
|
||||
}
|
||||
|
||||
suspend fun checkForUpdates(
|
||||
@@ -76,8 +41,7 @@ internal class ExtensionApi {
|
||||
return null
|
||||
}
|
||||
|
||||
// Update extension repo details
|
||||
updateExtensionRepo.awaitAll()
|
||||
updateExtensionStores()
|
||||
|
||||
val extensions = if (fromAvailableExtensionList) {
|
||||
extensionManager.availableExtensionsFlow.value
|
||||
@@ -107,64 +71,4 @@ internal class ExtensionApi {
|
||||
|
||||
return extensionsWithUpdate
|
||||
}
|
||||
|
||||
private fun List<ExtensionJsonObject>.toExtensions(repoUrl: String): List<Extension.Available> {
|
||||
return this
|
||||
.filter {
|
||||
val libVersion = it.extractLibVersion()
|
||||
libVersion >= ExtensionLoader.LIB_VERSION_MIN && libVersion <= ExtensionLoader.LIB_VERSION_MAX
|
||||
}
|
||||
.map {
|
||||
Extension.Available(
|
||||
name = it.name.substringAfter("Tachiyomi: "),
|
||||
pkgName = it.pkg,
|
||||
versionName = it.version,
|
||||
versionCode = it.code,
|
||||
libVersion = it.extractLibVersion(),
|
||||
lang = it.lang,
|
||||
isNsfw = it.nsfw == 1,
|
||||
sources = it.sources?.map(extensionSourceMapper).orEmpty(),
|
||||
apkName = it.apk,
|
||||
iconUrl = "$repoUrl/icon/${it.pkg}.png",
|
||||
repoUrl = repoUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun getApkUrl(extension: Extension.Available): String {
|
||||
return "${extension.repoUrl}/apk/${extension.apkName}"
|
||||
}
|
||||
|
||||
private fun ExtensionJsonObject.extractLibVersion(): Double {
|
||||
return version.substringBeforeLast('.').toDouble()
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class ExtensionJsonObject(
|
||||
val name: String,
|
||||
val pkg: String,
|
||||
val apk: String,
|
||||
val lang: String,
|
||||
val code: Long,
|
||||
val version: String,
|
||||
val nsfw: Int,
|
||||
val sources: List<ExtensionSourceJsonObject>?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
private data class ExtensionSourceJsonObject(
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
)
|
||||
|
||||
private val extensionSourceMapper: (ExtensionSourceJsonObject) -> Extension.Available.Source = {
|
||||
Extension.Available.Source(
|
||||
id = it.id,
|
||||
lang = it.lang,
|
||||
name = it.name,
|
||||
baseUrl = it.baseUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package eu.kanade.tachiyomi.extension.model
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
|
||||
sealed class Extension {
|
||||
|
||||
abstract val name: String
|
||||
abstract val pkgName: String
|
||||
abstract val versionName: String
|
||||
abstract val versionCode: Long
|
||||
abstract val libVersion: Double
|
||||
abstract val lang: String?
|
||||
abstract val isNsfw: Boolean
|
||||
|
||||
data class Installed(
|
||||
override val name: String,
|
||||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Long,
|
||||
override val libVersion: Double,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val pkgFactory: String?,
|
||||
val sources: List<Source>,
|
||||
val icon: Drawable?,
|
||||
val hasUpdate: Boolean = false,
|
||||
val isObsolete: Boolean = false,
|
||||
val isShared: Boolean,
|
||||
val repoUrl: String? = null,
|
||||
) : Extension()
|
||||
|
||||
data class Available(
|
||||
override val name: String,
|
||||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Long,
|
||||
override val libVersion: Double,
|
||||
override val lang: String,
|
||||
override val isNsfw: Boolean,
|
||||
val sources: List<Source>,
|
||||
val apkName: String,
|
||||
val iconUrl: String,
|
||||
val repoUrl: String,
|
||||
) : Extension() {
|
||||
|
||||
data class Source(
|
||||
val id: Long,
|
||||
val lang: String,
|
||||
val name: String,
|
||||
val baseUrl: String,
|
||||
) {
|
||||
fun toStubSource(): StubSource {
|
||||
return StubSource(
|
||||
id = this.id,
|
||||
lang = this.lang,
|
||||
name = this.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Untrusted(
|
||||
override val name: String,
|
||||
override val pkgName: String,
|
||||
override val versionName: String,
|
||||
override val versionCode: Long,
|
||||
override val libVersion: Double,
|
||||
val signatureHash: String,
|
||||
override val lang: String? = null,
|
||||
override val isNsfw: Boolean = false,
|
||||
) : Extension()
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.browse.ExtensionScreen
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.TabContent
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
||||
@@ -45,8 +45,8 @@ fun extensionsTab(
|
||||
onClick = { navigator.push(ExtensionFilterScreen()) },
|
||||
),
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(MR.strings.label_extension_repos),
|
||||
onClick = { navigator.push(ExtensionReposScreen()) },
|
||||
title = stringResource(MR.strings.extensionStores),
|
||||
onClick = { navigator.push(ExtensionStoresScreen()) },
|
||||
),
|
||||
),
|
||||
content = { contentPadding, _ ->
|
||||
|
||||
@@ -74,7 +74,7 @@ import eu.kanade.presentation.components.AppStateBanners
|
||||
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
|
||||
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
|
||||
import eu.kanade.presentation.components.IndexingBannerBackgroundColor
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
||||
import eu.kanade.presentation.util.AssistContentScreen
|
||||
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
|
||||
@@ -562,11 +562,11 @@ class MainActivity : BaseActivity() {
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(RestoreBackupScreen(intent.data.toString()))
|
||||
}
|
||||
// Deep link to add extension repo
|
||||
else if (intent.scheme == "tachiyomi" && intent.data?.host == "add-repo") {
|
||||
// Deep link to add extension store
|
||||
else if (intent.isAddExtensionStoreIntent()) {
|
||||
intent.data?.getQueryParameter("url")?.let { repoUrl ->
|
||||
navigator.popUntilRoot()
|
||||
navigator.push(ExtensionReposScreen(repoUrl))
|
||||
navigator.push(ExtensionStoresScreen(repoUrl))
|
||||
}
|
||||
}
|
||||
null
|
||||
@@ -582,6 +582,11 @@ class MainActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun Intent.isAddExtensionStoreIntent(): Boolean {
|
||||
return (scheme == "tachiyomi" && data?.host == "add-repo") ||
|
||||
(scheme == "mihon" && data?.host == "extension-store")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INTENT_SEARCH = "eu.kanade.tachiyomi.SEARCH"
|
||||
const val INTENT_SEARCH_QUERY = "query"
|
||||
|
||||
Reference in New Issue
Block a user