TachiyomiSY-Plus/app/src/main/java/exh/EXHMigrations.kt
arkon 1ec0a22e37 Clean up external repos
- Accept full URL as input instead, which allows for non-GitHub
- Remove automatic CDN fallback in favor of adding that as an external repo if needed

(cherry picked from commit 9c899e97a97480545d022974ffd3ea1248634155)

# Conflicts:
#	app/build.gradle.kts
#	app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
2024-01-09 18:51:37 -05:00

822 lines
37 KiB
Kotlin

package exh
import android.content.Context
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import eu.kanade.tachiyomi.data.cache.PagePreviewCache
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.workManager
import exh.eh.EHentaiUpdateWorker
import exh.log.xLogE
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.HBROWSE_SOURCE_ID
import exh.source.MERGED_SOURCE_ID
import exh.source.TSUMINO_SOURCE_ID
import exh.util.nullIfBlank
import exh.util.under
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import logcat.LogPriority
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.getAndSet
import tachiyomi.core.preference.getEnum
import tachiyomi.core.preference.minusAssign
import tachiyomi.core.util.system.logcat
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.category.CategoryMapper
import tachiyomi.data.chapter.ChapterMapper
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.chapter.interactor.DeleteChapters
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaBySource
import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.MergedMangaReference
import tachiyomi.domain.source.interactor.InsertFeedSavedSearch
import tachiyomi.domain.source.interactor.InsertSavedSearch
import tachiyomi.domain.source.model.FeedSavedSearch
import tachiyomi.domain.source.model.SavedSearch
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.URI
import java.net.URISyntaxException
object EXHMigrations {
private val handler: DatabaseHandler by injectLazy()
private val sourceManager: SourceManager by injectLazy()
private val getManga: GetManga by injectLazy()
private val getMangaBySource: GetMangaBySource by injectLazy()
private val updateManga: UpdateManga by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val deleteChapters: DeleteChapters by injectLazy()
private val insertMergedReference: InsertMergedReference by injectLazy()
private val insertSavedSearch: InsertSavedSearch by injectLazy()
private val insertFeedSavedSearch: InsertFeedSavedSearch by injectLazy()
/**
* Performs a migration when the application is updated.
*
* @return true if a migration is performed, false otherwise.
*/
fun upgrade(
context: Context,
preferenceStore: PreferenceStore,
basePreferences: BasePreferences,
uiPreferences: UiPreferences,
networkPreferences: NetworkPreferences,
sourcePreferences: SourcePreferences,
securityPreferences: SecurityPreferences,
libraryPreferences: LibraryPreferences,
readerPreferences: ReaderPreferences,
backupPreferences: BackupPreferences,
trackerManager: TrackerManager,
pagePreviewCache: PagePreviewCache,
): Boolean {
val lastVersionCode = preferenceStore.getInt("eh_last_version_code", 0)
val oldVersion = lastVersionCode.get()
try {
if (oldVersion < BuildConfig.VERSION_CODE) {
lastVersionCode.set(BuildConfig.VERSION_CODE)
LibraryUpdateJob.setupTask(context)
BackupCreateJob.setupTask(context)
EHentaiUpdateWorker.scheduleBackground(context)
// Fresh install
if (oldVersion == 0) {
return false
}
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (oldVersion under 4) {
updateSourceId(HBROWSE_SOURCE_ID, 6912)
// Migrate BHrowse URLs
val hBrowseManga = runBlocking { getMangaBySource.await(HBROWSE_SOURCE_ID) }
val mangaUpdates = hBrowseManga.map {
MangaUpdate(it.id, url = it.url + "/c00001/")
}
runBlocking {
updateManga.awaitAll(mangaUpdates)
}
}
if (oldVersion under 6) {
updateSourceId(NHentai.otherId, 6907)
}
if (oldVersion under 7) {
val mergedMangas = runBlocking { getMangaBySource.await(MERGED_SOURCE_ID) }
if (mergedMangas.isNotEmpty()) {
val mangaConfigs = mergedMangas.mapNotNull { mergedManga ->
readMangaConfig(mergedManga)?.let { mergedManga to it }
}
if (mangaConfigs.isNotEmpty()) {
val mangaToUpdate = mutableListOf<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga ->
val newFirst = mergedManga.second.children.firstOrNull()?.url?.let {
if (runBlocking { getManga.await(it, MERGED_SOURCE_ID) } != null) return@onEach
mangaToUpdate += MangaUpdate(id = mergedManga.first.id, url = it)
mergedManga.first.copy(url = it)
} ?: mergedManga.first
mergedMangaReferences += MergedMangaReference(
id = -1,
isInfoManga = false,
getChapterUpdates = false,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = false,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = newFirst.id,
mangaUrl = newFirst.url,
mangaSourceId = MERGED_SOURCE_ID,
)
mergedManga.second.children.distinct().forEachIndexed { index, mangaSource ->
val load = mangaSource.load() ?: return@forEachIndexed
mergedMangaReferences += MergedMangaReference(
id = -1,
isInfoManga = index == 0,
getChapterUpdates = true,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = load.manga.id,
mangaUrl = load.manga.url,
mangaSourceId = load.source.id,
)
}
}
runBlocking {
updateManga.awaitAll(mangaToUpdate)
insertMergedReference.awaitAll(mergedMangaReferences)
}
val loadedMangaList = mangaConfigs
.map { it.second.children }
.flatten()
.mapNotNull { it.load() }
.distinct()
val chapters =
runBlocking {
handler.awaitList {
ehQueries.getChaptersByMangaIds(
mergedMangas.map { it.id },
ChapterMapper::mapChapter,
)
}
}
val mergedMangaChapters =
runBlocking {
handler.awaitList {
ehQueries.getChaptersByMangaIds(
loadedMangaList.map { it.manga.id },
ChapterMapper::mapChapter,
)
}
}
val mergedMangaChaptersMatched = mergedMangaChapters.mapNotNull { chapter ->
loadedMangaList.firstOrNull {
it.manga.id == chapter.id
}?.let { it to chapter }
}
val parsedChapters = chapters.filter {
it.read || it.lastPageRead != 0L
}.mapNotNull { chapter -> readUrlConfig(chapter.url)?.let { chapter to it } }
val chaptersToUpdate = mutableListOf<ChapterUpdate>()
parsedChapters.forEach { parsedChapter ->
mergedMangaChaptersMatched.firstOrNull {
it.second.url == parsedChapter.second.url &&
it.first.source.id == parsedChapter.second.source &&
it.first.manga.url == parsedChapter.second.mangaUrl
}?.let {
chaptersToUpdate += ChapterUpdate(
it.second.id,
read = parsedChapter.first.read,
lastPageRead = parsedChapter.first.lastPageRead,
)
}
}
runBlocking {
deleteChapters.await(mergedMangaChapters.map { it.id })
updateChapter.awaitAll(chaptersToUpdate)
}
}
}
}
if (oldVersion under 12) {
// Force MAL log out due to login flow change
trackerManager.myAnimeList.logout()
}
if (oldVersion under 14) {
// Migrate DNS over HTTPS setting
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
if (wasDohEnabled) {
prefs.edit {
putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE)
remove("enable_doh")
}
}
}
if (oldVersion under 16) {
// Reset rotation to Free after replacing Lock
if (prefs.contains("pref_rotation_type_key")) {
prefs.edit {
putInt("pref_rotation_type_key", 1)
}
}
// Disable update check for Android 5.x users
// if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT under Build.VERSION_CODES.M) {
// UpdaterJob.cancelTask(context)
// }
}
if (oldVersion under 17) {
// Migrate Rotation and Viewer values to default values for viewer_flags
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
1 -> ReaderOrientation.FREE.flagValue
2 -> ReaderOrientation.PORTRAIT.flagValue
3 -> ReaderOrientation.LANDSCAPE.flagValue
4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue
5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue
else -> ReaderOrientation.FREE.flagValue
}
// Reading mode flag and prefValue is the same value
val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
prefs.edit {
putInt("pref_default_orientation_type_key", newOrientation)
remove("pref_rotation_type_key")
putInt("pref_default_reading_mode_key", newReadingMode)
remove("pref_default_viewer_key")
}
// Delete old mangadex trackers
runBlocking {
handler.await { ehQueries.deleteBySyncId(6) }
}
}
if (oldVersion under 18) {
val readerTheme = readerPreferences.readerTheme().get()
if (readerTheme == 4) {
readerPreferences.readerTheme().set(3)
}
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval == 1 || updateInterval == 2) {
libraryPreferences.autoUpdateInterval().set(3)
}
}
if (oldVersion under 20) {
try {
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0 /* ALPHABETICAL */)
val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
val newSortingMode = when (oldSortingMode) {
0 -> "ALPHABETICAL"
1 -> "LAST_READ"
2 -> "LAST_MANGA_UPDATE"
3 -> "UNREAD_COUNT"
4 -> "TOTAL_CHAPTERS"
6 -> "LATEST_CHAPTER"
7 -> "DRAG_AND_DROP"
8 -> "DATE_ADDED"
9 -> "TAG_LIST"
10 -> "CHAPTER_FETCH_DATE"
else -> "ALPHABETICAL"
}
val newSortingDirection = when (oldSortingDirection) {
true -> "ASCENDING"
else -> "DESCENDING"
}
prefs.edit(commit = true) {
remove(libraryPreferences.sortingMode().key())
remove("library_sorting_ascending")
}
prefs.edit {
putString(libraryPreferences.sortingMode().key(), newSortingMode)
putString("library_sorting_ascending", newSortingDirection)
}
} catch (e: Exception) {
logcat(throwable = e) { "Already done migration" }
}
}
if (oldVersion under 22) {
// Handle removed every 3, 4, 6, and 8 hour library updates
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval in listOf(3, 4, 6, 8)) {
libraryPreferences.autoUpdateInterval().set(12)
}
}
if (oldVersion under 23) {
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
if (!oldUpdateOngoingOnly) {
libraryPreferences.autoUpdateMangaRestrictions() -= MANGA_NON_COMPLETED
}
}
if (oldVersion under 24) {
try {
sequenceOf(
"fav-sync",
"fav-sync.management",
"fav-sync.lock",
"fav-sync.note",
).map {
File(context.filesDir, it)
}.filter(File::exists).forEach {
if (it.isDirectory) {
it.deleteRecursively()
} else {
it.delete()
}
}
} catch (e: Exception) {
xLogE("Failed to delete old favorites database", e)
}
}
if (oldVersion under 27) {
val oldSecureScreen = prefs.getBoolean("secure_screen", false)
if (oldSecureScreen) {
securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
}
if (
DeviceUtil.isMiui &&
basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller
.PACKAGEINSTALLER
) {
basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
}
}
if (oldVersion under 28) {
if (prefs.getString("pref_display_mode_library", null) == "NO_TITLE_GRID") {
prefs.edit(commit = true) {
putString("pref_display_mode_library", "COVER_ONLY_GRID")
}
}
}
if (oldVersion under 29) {
if (prefs.getString("pref_display_mode_catalogue", null) == "NO_TITLE_GRID") {
prefs.edit(commit = true) {
putString("pref_display_mode_catalogue", "COMPACT_GRID")
}
}
}
if (oldVersion under 31) {
runBlocking {
val savedSearch = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull {
runCatching {
val content = Json.decodeFromString<JsonObject>(it.substringAfter(':'))
SavedSearch(
id = -1,
source = it.substringBefore(':').toLongOrNull()
?: return@runCatching null,
name = content["name"]!!.jsonPrimitive.content,
query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
filtersJson = Json.encodeToString(content["filters"]!!.jsonArray),
)
}.getOrNull()
}
if (!savedSearch.isNullOrEmpty()) {
insertSavedSearch.awaitAll(savedSearch)
}
val feedSavedSearch = prefs.getStringSet("latest_tab_sources", emptySet())?.map {
FeedSavedSearch(
id = -1,
source = it.toLong(),
savedSearch = null,
global = true,
)
}
if (!feedSavedSearch.isNullOrEmpty()) {
insertFeedSavedSearch.awaitAll(feedSavedSearch)
}
}
prefs.edit(commit = true) {
remove("eh_saved_searches")
remove("latest_tab_sources")
}
}
if (oldVersion under 32) {
val oldReaderTap = prefs.getBoolean("reader_tap", false)
if (!oldReaderTap) {
readerPreferences.navigationModePager().set(5)
readerPreferences.navigationModeWebtoon().set(5)
}
}
if (oldVersion under 38) {
// Handle renamed enum values
val newSortingMode = when (
val oldSortingMode = prefs.getString(libraryPreferences.sortingMode().key(), "ALPHABETICAL")
) {
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
"UNREAD" -> "UNREAD_COUNT"
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
"DRAG_AND_DROP" -> "ALPHABETICAL"
else -> oldSortingMode
}
prefs.edit {
putString(libraryPreferences.sortingMode().key(), newSortingMode)
}
runBlocking {
handler.await(true) {
categoriesQueries.getCategories(CategoryMapper::mapCategory).executeAsList()
.filter { (it.flags and 0b00111100L) == 0b00100000L }
.forEach {
categoriesQueries.update(
categoryId = it.id,
flags = it.flags and 0b00111100L.inv(),
name = null,
order = null,
)
}
}
}
}
if (oldVersion under 39) {
prefs.edit {
val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit
val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
remove("library_sorting_ascending")
}
}
if (oldVersion under 40) {
if (backupPreferences.backupInterval().get() == 0) {
backupPreferences.backupInterval().set(12)
}
}
if (oldVersion under 41) {
val preferences = listOf(
libraryPreferences.filterChapterByRead(),
libraryPreferences.filterChapterByDownloaded(),
libraryPreferences.filterChapterByBookmarked(),
libraryPreferences.sortChapterBySourceOrNumber(),
libraryPreferences.displayChapterByNameOrNumber(),
libraryPreferences.sortChapterByAscendingOrDescending(),
)
prefs.edit {
preferences.forEach { preference ->
val key = preference.key()
val value = prefs.getInt(key, Int.MIN_VALUE)
if (value == Int.MIN_VALUE) return@forEach
remove(key)
putLong(key, value.toLong())
}
}
}
if (oldVersion under 42) {
if (uiPreferences.themeMode().isSet()) {
prefs.edit {
val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit
putString(uiPreferences.themeMode().key(), themeMode.uppercase())
}
}
}
if (oldVersion under 43) {
if (preferenceStore.getBoolean("start_reading_button").get()) {
libraryPreferences.showContinueReadingButton().set(true)
}
}
if (oldVersion under 44) {
val trackingQueuePref = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
trackingQueuePref.all.forEach {
val (_, lastChapterRead) = it.value.toString().split(":")
trackingQueuePref.edit {
remove(it.key)
putFloat(it.key, lastChapterRead.toFloat())
}
}
}
if (oldVersion under 45) {
// Force MangaDex log out due to login flow change
trackerManager.mdList.logout()
}
if (oldVersion under 52) {
// Removed background jobs
context.workManager.cancelAllWorkByTag("UpdateChecker")
context.workManager.cancelAllWorkByTag("ExtensionUpdate")
prefs.edit {
remove("automatic_ext_updates")
}
val prefKeys = listOf(
"pref_filter_library_downloaded",
"pref_filter_library_unread",
"pref_filter_library_started",
"pref_filter_library_bookmarked",
"pref_filter_library_completed",
"pref_filter_library_lewd",
) + trackerManager.trackers.map { "pref_filter_library_tracked_${it.id}" }
prefKeys.forEach { key ->
val pref = preferenceStore.getInt(key, 0)
prefs.edit {
remove(key)
val newValue = when (pref.get()) {
1 -> TriState.ENABLED_IS
2 -> TriState.ENABLED_NOT
else -> TriState.DISABLED
}
preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
}
}
}
// if (oldVersion under 53) {
// // This was accidentally visible from the reader settings sheet, but should always
// // be disabled in release builds.
// if (isReleaseBuildType) {
// readerPreferences.longStripSplitWebtoon().set(false)
// }
// }
if (oldVersion under 56) {
val pref = libraryPreferences.autoUpdateDeviceRestrictions()
if (pref.isSet() && "battery_not_low" in pref.get()) {
pref.getAndSet { it - "battery_not_low" }
}
}
if (oldVersion under 57) {
val pref = preferenceStore.getInt("relative_time", 7)
if (pref.get() == 0) {
uiPreferences.relativeTime().set(false)
}
}
if (oldVersion under 58) {
pagePreviewCache.clear()
File(context.cacheDir, PagePreviewCache.PARAMETER_CACHE_DIRECTORY).listFiles()?.forEach {
if (it.name == "journal" || it.name.startsWith("journal.")) {
return@forEach
}
try {
it.delete()
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Failed to remove file from cache" }
}
}
preferenceStore.getAll()
.filter { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }
.forEach { (key, value) ->
if (value is String) {
preferenceStore
.getString(Preference.privateKey(key))
.set(value)
preferenceStore.getString(key).delete()
}
}
}
if (oldVersion under 59) {
val prefsToReplace = listOf(
"pref_download_only",
"incognito_mode",
"last_catalogue_source",
"trusted_signatures",
"last_app_closed",
"library_update_last_timestamp",
"library_unseen_updates_count",
"last_used_category",
"last_app_check",
"last_ext_check",
"last_version_code",
"skip_pre_migration",
"eh_auto_update_stats",
"storage_dir",
)
replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key in prefsToReplace },
newKey = { Preference.appStateKey(it) },
)
val privatePrefsToReplace = listOf(
"sql_password",
"encrypt_database",
"cbz_password",
"password_protect_downloads",
"eh_ipb_member_id",
"enable_exhentai",
"eh_ipb_member_id",
"eh_ipb_pass_hash",
"eh_igneous",
"eh_ehSettingsProfile",
"eh_exhSettingsProfile",
"eh_settingsKey",
"eh_sessionCookie",
"eh_hathPerksCookie",
)
replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key in privatePrefsToReplace },
newKey = { Preference.privateKey(it) },
)
// Deleting old download cache index files, but might as well clear it all out
context.cacheDir.deleteRecursively()
}
if (oldVersion under 60) {
sourcePreferences.extensionRepos().getAndSet {
it.map { "https://raw.githubusercontent.com/$it/repo" }.toSet()
}
}
// if (oldVersion under 1) { } (1 is current release version)
// do stuff here when releasing changed crap
return true
}
} catch (e: Exception) {
xLogE("Failed to migrate app from $oldVersion -> ${BuildConfig.VERSION_CODE}!", e)
}
return false
}
fun migrateBackupEntry(manga: Manga): Manga {
var newManga = manga
if (newManga.source == 6907L) {
newManga = newManga.copy(
// Migrate the old source to the delegated one
source = NHentai.otherId,
// Migrate nhentai URLs
url = getUrlWithoutDomain(newManga.url),
)
}
// Migrate Tsumino source IDs
if (newManga.source == 6909L) {
newManga = newManga.copy(
source = TSUMINO_SOURCE_ID,
)
}
if (newManga.source == 6912L) {
newManga = newManga.copy(
source = HBROWSE_SOURCE_ID,
url = newManga.url + "/c00001/",
)
}
// Allow importing of EHentai extension backups
if (newManga.source in BlacklistedSources.EHENTAI_EXT_SOURCES) {
newManga = newManga.copy(
source = EH_SOURCE_ID,
)
}
return newManga
}
private fun getUrlWithoutDomain(orig: String): String {
return try {
val uri = URI(orig)
var out = uri.path
if (uri.query != null) {
out += "?" + uri.query
}
if (uri.fragment != null) {
out += "#" + uri.fragment
}
out
} catch (e: URISyntaxException) {
orig
}
}
@Serializable
private data class UrlConfig(
@SerialName("s")
val source: Long,
@SerialName("u")
val url: String,
@SerialName("m")
val mangaUrl: String,
)
@Serializable
private data class MangaConfig(
@SerialName("c")
val children: List<MangaSource>,
) {
companion object {
fun readFromUrl(url: String): MangaConfig? {
return try {
Json.decodeFromString(url)
} catch (e: Exception) {
null
}
}
}
}
private fun readMangaConfig(manga: Manga): MangaConfig? {
return MangaConfig.readFromUrl(manga.url)
}
@Serializable
private data class MangaSource(
@SerialName("s")
val source: Long,
@SerialName("u")
val url: String,
) {
fun load(): LoadedMangaSource? {
val manga = runBlocking { getManga.await(url, source) } ?: return null
val source = sourceManager.getOrStub(source)
return LoadedMangaSource(source, manga)
}
}
private fun readUrlConfig(url: String): UrlConfig? {
return try {
Json.decodeFromString(url)
} catch (e: Exception) {
null
}
}
private data class LoadedMangaSource(val source: Source, val manga: Manga)
private fun updateSourceId(newId: Long, oldId: Long) {
runBlocking {
handler.await { ehQueries.migrateSource(newId, oldId) }
}
}
@Suppress("UNCHECKED_CAST")
private fun replacePreferences(
preferenceStore: PreferenceStore,
filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
newKey: (String) -> String,
) {
preferenceStore.getAll()
.filter(filterPredicate)
.forEach { (key, value) ->
when (value) {
is Int -> {
preferenceStore.getInt(newKey(key)).set(value)
preferenceStore.getInt(key).delete()
}
is Long -> {
preferenceStore.getLong(newKey(key)).set(value)
preferenceStore.getLong(key).delete()
}
is Float -> {
preferenceStore.getFloat(newKey(key)).set(value)
preferenceStore.getFloat(key).delete()
}
is String -> {
preferenceStore.getString(newKey(key)).set(value)
preferenceStore.getString(key).delete()
}
is Boolean -> {
preferenceStore.getBoolean(newKey(key)).set(value)
preferenceStore.getBoolean(key).delete()
}
is Set<*> -> (value as? Set<String>)?.let {
preferenceStore.getStringSet(newKey(key)).set(value)
preferenceStore.getStringSet(key).delete()
}
}
}
}
}