From 5f5a03411277bdd60dbac1f8b0ef686c4844b4f7 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 22 Jun 2026 00:26:38 +0600 Subject: [PATCH] Allow extension stores to have separate extension list file (#3454) --- .../backup/models/BackupExtensionStore.kt | 2 + .../restorers/ExtensionStoreRestorer.kt | 1 + .../extension/model/NetworkExtensionStore.kt | 58 +++++++++++-------- .../model/NetworkLegacyExtensionRepo.kt | 1 + .../ExtensionStoreRepositoryImpl.kt | 4 ++ .../service/ExtensionStoreService.kt | 16 ++++- .../tachiyomi/data/extension_store.sq | 10 ++-- .../sqldelight/tachiyomi/migrations/13.sqm | 1 + .../domain/extension/model/ExtensionStore.kt | 1 + 9 files changed, 64 insertions(+), 30 deletions(-) create mode 100644 data/src/main/sqldelight/tachiyomi/migrations/13.sqm diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionStore.kt index 2304f260c..93ec1a6a7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupExtensionStore.kt @@ -13,6 +13,7 @@ class BackupExtensionStore( @ProtoNumber(4) var contactWebsite: String, @ProtoNumber(6) var contactDiscord: String?, @ProtoNumber(7) var isLegacy: Boolean?, + @ProtoNumber(8) var extensionListUrl: String?, ) val backupExtensionStoreMapper = { store: ExtensionStore -> @@ -24,5 +25,6 @@ val backupExtensionStoreMapper = { store: ExtensionStore -> contactWebsite = store.contact.website, contactDiscord = store.contact.discord, isLegacy = store.isLegacy, + extensionListUrl = store.extensionListUrl, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionStoreRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionStoreRestorer.kt index 70702546d..a8fc4ddcf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionStoreRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/ExtensionStoreRestorer.kt @@ -20,6 +20,7 @@ class ExtensionStoreRestorer( contactWebsite = backupStore.contactWebsite, contactDiscord = backupStore.contactDiscord, isLegacy = backupStore.isLegacy ?: true, + extensionListUrl = backupStore.extensionListUrl, ) } } diff --git a/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt b/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt index d15e00977..eadfb05be 100644 --- a/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt +++ b/data/src/main/java/mihon/data/extension/model/NetworkExtensionStore.kt @@ -4,7 +4,10 @@ import android.annotation.SuppressLint import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonNames import kotlinx.serialization.protobuf.ProtoNumber +import mihon.data.extension.model.NetworkExtensionStore.ContentRating +import mihon.data.extension.model.NetworkExtensionStore.ExtensionList import mihon.domain.extension.model.ExtensionStore +import eu.kanade.tachiyomi.extension.model.Extension as TachiyomiExtension @SuppressLint("UnsafeOptInUsageError") @Serializable @@ -13,7 +16,8 @@ data class NetworkExtensionStore( @ProtoNumber(2) val badgeLabel: String, @ProtoNumber(3) val signingKey: String, @ProtoNumber(4) val contact: Contact, - @ProtoNumber(5) val extensions: List, + @ProtoNumber(101) val extensionList: ExtensionList?, + @ProtoNumber(102) val extensionListUrl: String?, ) : BaseNetworkExtensionStore { @Serializable data class Contact( @@ -21,6 +25,9 @@ data class NetworkExtensionStore( @ProtoNumber(2) val discord: String?, ) + @Serializable + data class ExtensionList(@ProtoNumber(1) val extensions: List) + @Serializable data class Extension( @ProtoNumber(1) val name: String, @@ -79,32 +86,33 @@ data class NetworkExtensionStore( discord = contact.discord, ), isLegacy = false, + extensionListUrl = extensionListUrl, ) } +} - fun toAvailableExtensions(store: ExtensionStore): List { - return extensions.map { extension -> - val lang = extension.sources.map { it.language }.toSet() - eu.kanade.tachiyomi.extension.model.Extension.Available( - name = extension.name, - pkgName = extension.packageName, - apkUrl = extension.resources.apkUrl, - iconUrl = extension.resources.iconUrl, - libVersion = extension.extensionLib.toDouble(), - versionCode = extension.versionCode, - versionName = extension.versionName, - lang = if (lang.size == 1) lang.first() else "all", - isNsfw = extension.sources.maxOfOrNull { it.contentRating } == ContentRating.PORNOGRAPHIC, - sources = extension.sources.map { source -> - eu.kanade.tachiyomi.extension.model.Extension.Available.Source( - id = source.id, - name = source.name, - lang = source.language, - baseUrl = source.homeUrl, - ) - }, - store = store, - ) - } +fun ExtensionList.toAvailableExtensions(store: ExtensionStore): List { + return extensions.map { extension -> + val lang = extension.sources.map { it.language }.toSet() + TachiyomiExtension.Available( + name = extension.name, + pkgName = extension.packageName, + apkUrl = extension.resources.apkUrl, + iconUrl = extension.resources.iconUrl, + libVersion = extension.extensionLib.toDouble(), + versionCode = extension.versionCode, + versionName = extension.versionName, + lang = if (lang.size == 1) lang.first() else "all", + isNsfw = extension.sources.maxOfOrNull { it.contentRating } == ContentRating.PORNOGRAPHIC, + sources = extension.sources.map { source -> + TachiyomiExtension.Available.Source( + id = source.id, + name = source.name, + lang = source.language, + baseUrl = source.homeUrl, + ) + }, + store = store, + ) } } diff --git a/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtensionRepo.kt b/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtensionRepo.kt index babc696bb..525240050 100644 --- a/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtensionRepo.kt +++ b/data/src/main/java/mihon/data/extension/model/NetworkLegacyExtensionRepo.kt @@ -31,6 +31,7 @@ data class NetworkLegacyExtensionRepo( discord = null, ), isLegacy = true, + extensionListUrl = null, ) } } diff --git a/data/src/main/java/mihon/data/extension/repository/ExtensionStoreRepositoryImpl.kt b/data/src/main/java/mihon/data/extension/repository/ExtensionStoreRepositoryImpl.kt index e04ede9cb..fbd431743 100644 --- a/data/src/main/java/mihon/data/extension/repository/ExtensionStoreRepositoryImpl.kt +++ b/data/src/main/java/mihon/data/extension/repository/ExtensionStoreRepositoryImpl.kt @@ -32,6 +32,7 @@ class ExtensionStoreRepositoryImpl( contactWebsite = indexUrl, contactDiscord = null, isLegacy = false, + extensionListUrl = null, ) } @@ -67,6 +68,7 @@ class ExtensionStoreRepositoryImpl( contactWebsite = store.contact.website, contactDiscord = store.contact.discord, isLegacy = store.isLegacy, + extensionListUrl = store.extensionListUrl, ) } @@ -117,6 +119,7 @@ class ExtensionStoreRepositoryImpl( contactWebsite: String, contactDiscord: String?, isLegacy: Boolean, + extensionListUrl: String?, ): ExtensionStore = ExtensionStore( indexUrl = indexUrl, name = name, @@ -127,5 +130,6 @@ class ExtensionStoreRepositoryImpl( discord = contactDiscord, ), isLegacy = isLegacy, + extensionListUrl = extensionListUrl, ) } diff --git a/data/src/main/java/mihon/data/extension/service/ExtensionStoreService.kt b/data/src/main/java/mihon/data/extension/service/ExtensionStoreService.kt index 6458b1989..155764518 100644 --- a/data/src/main/java/mihon/data/extension/service/ExtensionStoreService.kt +++ b/data/src/main/java/mihon/data/extension/service/ExtensionStoreService.kt @@ -12,6 +12,7 @@ import logcat.LogPriority import mihon.data.extension.model.NetworkExtensionStore import mihon.data.extension.model.NetworkLegacyExtension import mihon.data.extension.model.NetworkLegacyExtensionRepo +import mihon.data.extension.model.toAvailableExtensions import mihon.domain.extension.model.ExtensionStore import okio.BufferedSource import okio.buffer @@ -68,7 +69,19 @@ class ExtensionStoreService( suspend fun getExtensions(store: ExtensionStore): Result> { return try { - val extensions = if (!store.isLegacy) { + val extensions = if (store.extensionListUrl != null) { + val response = network.client.newCall(GET(store.extensionListUrl!!)).awaitSuccess() + response.body.source().decompressIfGzipped().use { source -> + when (source.peek().readByte()) { + // "{..." + 0x7B.toByte() -> json.decodeFromBufferedSource(source) + else -> protoBuf.decodeFromByteArray( + source.readByteArray(), + ) + } + .toAvailableExtensions(store) + } + } else if (!store.isLegacy) { val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess() response.body.source().decompressIfGzipped().use { source -> when (source.peek().readByte()) { @@ -76,6 +89,7 @@ class ExtensionStoreService( 0x7B.toByte() -> json.decodeFromBufferedSource(source) else -> protoBuf.decodeFromByteArray(source.readByteArray()) } + .extensionList!! .toAvailableExtensions(store) } } else { diff --git a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq index 82795ddc7..8f98bcf6d 100644 --- a/data/src/main/sqldelight/tachiyomi/data/extension_store.sq +++ b/data/src/main/sqldelight/tachiyomi/data/extension_store.sq @@ -7,7 +7,8 @@ CREATE TABLE extension_store( signing_key TEXT NOT NULL, contact_website TEXT NOT NULL, contact_discord TEXT, - is_legacy INTEGER AS Boolean NOT NULL + is_legacy INTEGER AS Boolean NOT NULL, + extension_list_url TEXT ); get: @@ -24,8 +25,8 @@ SELECT COUNT(*) FROM extension_store; upsert: -INSERT INTO extension_store(index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy) -VALUES (:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy) +INSERT INTO extension_store(index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy, extension_list_url) +VALUES (:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy, :extensionListUrl) ON CONFLICT(index_url) DO UPDATE SET @@ -34,7 +35,8 @@ SET signing_key =: signingKey, contact_website = :contactWebsite, contact_discord = :contactDiscord, - is_legacy = :isLegacy + is_legacy = :isLegacy, + extension_list_url = :extensionListUrl WHERE index_url = :indexUrl; delete: diff --git a/data/src/main/sqldelight/tachiyomi/migrations/13.sqm b/data/src/main/sqldelight/tachiyomi/migrations/13.sqm new file mode 100644 index 000000000..757fa2229 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/13.sqm @@ -0,0 +1 @@ +ALTER TABLE extension_store ADD COLUMN extension_list_url TEXT; diff --git a/domain/src/main/java/mihon/domain/extension/model/ExtensionStore.kt b/domain/src/main/java/mihon/domain/extension/model/ExtensionStore.kt index d2cadd0b6..31e258b71 100644 --- a/domain/src/main/java/mihon/domain/extension/model/ExtensionStore.kt +++ b/domain/src/main/java/mihon/domain/extension/model/ExtensionStore.kt @@ -7,6 +7,7 @@ data class ExtensionStore( val signingKey: String, val contact: Contact, val isLegacy: Boolean, + val extensionListUrl: String?, ) { data class Contact( val website: String,