Allow extension stores to have separate extension list file (#3454)

This commit is contained in:
AntsyLich
2026-06-22 00:26:38 +06:00
committed by GitHub
parent eb5f57a48c
commit 5f5a034112
9 changed files with 64 additions and 30 deletions
@@ -13,6 +13,7 @@ class BackupExtensionStore(
@ProtoNumber(4) var contactWebsite: String, @ProtoNumber(4) var contactWebsite: String,
@ProtoNumber(6) var contactDiscord: String?, @ProtoNumber(6) var contactDiscord: String?,
@ProtoNumber(7) var isLegacy: Boolean?, @ProtoNumber(7) var isLegacy: Boolean?,
@ProtoNumber(8) var extensionListUrl: String?,
) )
val backupExtensionStoreMapper = { store: ExtensionStore -> val backupExtensionStoreMapper = { store: ExtensionStore ->
@@ -24,5 +25,6 @@ val backupExtensionStoreMapper = { store: ExtensionStore ->
contactWebsite = store.contact.website, contactWebsite = store.contact.website,
contactDiscord = store.contact.discord, contactDiscord = store.contact.discord,
isLegacy = store.isLegacy, isLegacy = store.isLegacy,
extensionListUrl = store.extensionListUrl,
) )
} }
@@ -20,6 +20,7 @@ class ExtensionStoreRestorer(
contactWebsite = backupStore.contactWebsite, contactWebsite = backupStore.contactWebsite,
contactDiscord = backupStore.contactDiscord, contactDiscord = backupStore.contactDiscord,
isLegacy = backupStore.isLegacy ?: true, isLegacy = backupStore.isLegacy ?: true,
extensionListUrl = backupStore.extensionListUrl,
) )
} }
} }
@@ -4,7 +4,10 @@ import android.annotation.SuppressLint
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames import kotlinx.serialization.json.JsonNames
import kotlinx.serialization.protobuf.ProtoNumber 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 mihon.domain.extension.model.ExtensionStore
import eu.kanade.tachiyomi.extension.model.Extension as TachiyomiExtension
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
@Serializable @Serializable
@@ -13,7 +16,8 @@ data class NetworkExtensionStore(
@ProtoNumber(2) val badgeLabel: String, @ProtoNumber(2) val badgeLabel: String,
@ProtoNumber(3) val signingKey: String, @ProtoNumber(3) val signingKey: String,
@ProtoNumber(4) val contact: Contact, @ProtoNumber(4) val contact: Contact,
@ProtoNumber(5) val extensions: List<Extension>, @ProtoNumber(101) val extensionList: ExtensionList?,
@ProtoNumber(102) val extensionListUrl: String?,
) : BaseNetworkExtensionStore { ) : BaseNetworkExtensionStore {
@Serializable @Serializable
data class Contact( data class Contact(
@@ -21,6 +25,9 @@ data class NetworkExtensionStore(
@ProtoNumber(2) val discord: String?, @ProtoNumber(2) val discord: String?,
) )
@Serializable
data class ExtensionList(@ProtoNumber(1) val extensions: List<Extension>)
@Serializable @Serializable
data class Extension( data class Extension(
@ProtoNumber(1) val name: String, @ProtoNumber(1) val name: String,
@@ -79,32 +86,33 @@ data class NetworkExtensionStore(
discord = contact.discord, discord = contact.discord,
), ),
isLegacy = false, isLegacy = false,
extensionListUrl = extensionListUrl,
) )
} }
}
fun toAvailableExtensions(store: ExtensionStore): List<eu.kanade.tachiyomi.extension.model.Extension.Available> { fun ExtensionList.toAvailableExtensions(store: ExtensionStore): List<TachiyomiExtension.Available> {
return extensions.map { extension -> return extensions.map { extension ->
val lang = extension.sources.map { it.language }.toSet() val lang = extension.sources.map { it.language }.toSet()
eu.kanade.tachiyomi.extension.model.Extension.Available( TachiyomiExtension.Available(
name = extension.name, name = extension.name,
pkgName = extension.packageName, pkgName = extension.packageName,
apkUrl = extension.resources.apkUrl, apkUrl = extension.resources.apkUrl,
iconUrl = extension.resources.iconUrl, iconUrl = extension.resources.iconUrl,
libVersion = extension.extensionLib.toDouble(), libVersion = extension.extensionLib.toDouble(),
versionCode = extension.versionCode, versionCode = extension.versionCode,
versionName = extension.versionName, versionName = extension.versionName,
lang = if (lang.size == 1) lang.first() else "all", lang = if (lang.size == 1) lang.first() else "all",
isNsfw = extension.sources.maxOfOrNull { it.contentRating } == ContentRating.PORNOGRAPHIC, isNsfw = extension.sources.maxOfOrNull { it.contentRating } == ContentRating.PORNOGRAPHIC,
sources = extension.sources.map { source -> sources = extension.sources.map { source ->
eu.kanade.tachiyomi.extension.model.Extension.Available.Source( TachiyomiExtension.Available.Source(
id = source.id, id = source.id,
name = source.name, name = source.name,
lang = source.language, lang = source.language,
baseUrl = source.homeUrl, baseUrl = source.homeUrl,
) )
}, },
store = store, store = store,
) )
}
} }
} }
@@ -31,6 +31,7 @@ data class NetworkLegacyExtensionRepo(
discord = null, discord = null,
), ),
isLegacy = true, isLegacy = true,
extensionListUrl = null,
) )
} }
} }
@@ -32,6 +32,7 @@ class ExtensionStoreRepositoryImpl(
contactWebsite = indexUrl, contactWebsite = indexUrl,
contactDiscord = null, contactDiscord = null,
isLegacy = false, isLegacy = false,
extensionListUrl = null,
) )
} }
@@ -67,6 +68,7 @@ class ExtensionStoreRepositoryImpl(
contactWebsite = store.contact.website, contactWebsite = store.contact.website,
contactDiscord = store.contact.discord, contactDiscord = store.contact.discord,
isLegacy = store.isLegacy, isLegacy = store.isLegacy,
extensionListUrl = store.extensionListUrl,
) )
} }
@@ -117,6 +119,7 @@ class ExtensionStoreRepositoryImpl(
contactWebsite: String, contactWebsite: String,
contactDiscord: String?, contactDiscord: String?,
isLegacy: Boolean, isLegacy: Boolean,
extensionListUrl: String?,
): ExtensionStore = ExtensionStore( ): ExtensionStore = ExtensionStore(
indexUrl = indexUrl, indexUrl = indexUrl,
name = name, name = name,
@@ -127,5 +130,6 @@ class ExtensionStoreRepositoryImpl(
discord = contactDiscord, discord = contactDiscord,
), ),
isLegacy = isLegacy, isLegacy = isLegacy,
extensionListUrl = extensionListUrl,
) )
} }
@@ -12,6 +12,7 @@ import logcat.LogPriority
import mihon.data.extension.model.NetworkExtensionStore import mihon.data.extension.model.NetworkExtensionStore
import mihon.data.extension.model.NetworkLegacyExtension import mihon.data.extension.model.NetworkLegacyExtension
import mihon.data.extension.model.NetworkLegacyExtensionRepo import mihon.data.extension.model.NetworkLegacyExtensionRepo
import mihon.data.extension.model.toAvailableExtensions
import mihon.domain.extension.model.ExtensionStore import mihon.domain.extension.model.ExtensionStore
import okio.BufferedSource import okio.BufferedSource
import okio.buffer import okio.buffer
@@ -68,7 +69,19 @@ class ExtensionStoreService(
suspend fun getExtensions(store: ExtensionStore): Result<List<Extension.Available>> { suspend fun getExtensions(store: ExtensionStore): Result<List<Extension.Available>> {
return try { 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<NetworkExtensionStore.ExtensionList>(source)
else -> protoBuf.decodeFromByteArray<NetworkExtensionStore.ExtensionList>(
source.readByteArray(),
)
}
.toAvailableExtensions(store)
}
} else if (!store.isLegacy) {
val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess() val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess()
response.body.source().decompressIfGzipped().use { source -> response.body.source().decompressIfGzipped().use { source ->
when (source.peek().readByte()) { when (source.peek().readByte()) {
@@ -76,6 +89,7 @@ class ExtensionStoreService(
0x7B.toByte() -> json.decodeFromBufferedSource<NetworkExtensionStore>(source) 0x7B.toByte() -> json.decodeFromBufferedSource<NetworkExtensionStore>(source)
else -> protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.readByteArray()) else -> protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.readByteArray())
} }
.extensionList!!
.toAvailableExtensions(store) .toAvailableExtensions(store)
} }
} else { } else {
@@ -7,7 +7,8 @@ CREATE TABLE extension_store(
signing_key TEXT NOT NULL, signing_key TEXT NOT NULL,
contact_website TEXT NOT NULL, contact_website TEXT NOT NULL,
contact_discord TEXT, contact_discord TEXT,
is_legacy INTEGER AS Boolean NOT NULL is_legacy INTEGER AS Boolean NOT NULL,
extension_list_url TEXT
); );
get: get:
@@ -24,8 +25,8 @@ SELECT COUNT(*)
FROM extension_store; FROM extension_store;
upsert: upsert:
INSERT INTO extension_store(index_url, name, badge_label, signing_key, contact_website, contact_discord, is_legacy) 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) VALUES (:indexUrl, :name, :badgeLabel, :signingKey, :contactWebsite, :contactDiscord, :isLegacy, :extensionListUrl)
ON CONFLICT(index_url) ON CONFLICT(index_url)
DO UPDATE DO UPDATE
SET SET
@@ -34,7 +35,8 @@ SET
signing_key =: signingKey, signing_key =: signingKey,
contact_website = :contactWebsite, contact_website = :contactWebsite,
contact_discord = :contactDiscord, contact_discord = :contactDiscord,
is_legacy = :isLegacy is_legacy = :isLegacy,
extension_list_url = :extensionListUrl
WHERE index_url = :indexUrl; WHERE index_url = :indexUrl;
delete: delete:
@@ -0,0 +1 @@
ALTER TABLE extension_store ADD COLUMN extension_list_url TEXT;
@@ -7,6 +7,7 @@ data class ExtensionStore(
val signingKey: String, val signingKey: String,
val contact: Contact, val contact: Contact,
val isLegacy: Boolean, val isLegacy: Boolean,
val extensionListUrl: String?,
) { ) {
data class Contact( data class Contact(
val website: String, val website: String,