From 90d5104bdc91e66ce89ef384253428f25419d58a Mon Sep 17 00:00:00 2001 From: Andreas Date: Mon, 25 Mar 2024 18:26:19 +0100 Subject: [PATCH] Rewrite Migrations (#577) * Rewrite Migrations * Fix Detekt errors * Do migrations synchronous * Filter and sort migrations * Review changes * Review changes 2 * Fix Detekt errors (cherry picked from commit 666d6aa117756f0a9a57b31f91b7acb0ee5d7409) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/Migrations.kt # app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt --- .../java/eu/kanade/tachiyomi/Migrations.kt | 71 -- .../kanade/tachiyomi/ui/main/MainActivity.kt | 42 +- app/src/main/java/exh/EXHMigrations.kt | 806 ------------------ app/src/main/java/exh/debug/DebugFunctions.kt | 70 +- .../java/mihon/core/migration/MigrateUtils.kt | 53 ++ .../java/mihon/core/migration/Migration.kt | 19 + .../mihon/core/migration/MigrationContext.kt | 10 + .../java/mihon/core/migration/Migrator.kt | 53 ++ .../migrations/AlwaysBackupMigration.kt | 19 + .../ChangeMiuiExtensionInstallerMigration.kt | 24 + .../ChangeThemeModeToUppercaseMigration.kt | 27 + .../ChangeTrackingQueueTypeMigration.kt | 26 + .../ClearBrokenPagePreviewCacheMigration.kt | 33 + .../migrations/DelegateHBrowseMigration.kt | 28 + .../migrations/DelegateNHentaiMigration.kt | 17 + .../DeleteOldEhFavoritesDatabaseMigration.kt | 36 + .../DeleteOldMangaDexTracksMigration.kt | 17 + .../migrations/LogoutFromMALMigration.kt | 17 + .../migrations/LogoutFromMangaDexMigration.kt | 17 + .../migrations/MergedMangaRewriteMigration.kt | 191 +++++ .../core/migration/migrations/Migrations.kt | 48 ++ .../MoveCacheToDiskSettingMigration.kt | 24 + ...eCatalogueCoverOnlyGridSettingMigration.kt | 24 + .../MoveCoverOnlyGridSettingMigration.kt | 24 + .../migrations/MoveDOHSettingMigration.kt | 30 + ...veEncryptionSettingsToAppStateMigration.kt | 45 + .../MoveExtensionRepoSettingsMigration.kt | 37 + .../migrations/MoveLatestToFeedMigration.kt | 63 ++ .../MoveLibraryNonCompleteSettingMigration.kt | 25 + .../MoveLibrarySortingSettingsMigration.kt | 57 ++ .../MoveReaderTapSettingMigration.kt | 25 + .../MoveReadingButtonSettingMigration.kt | 23 + .../MoveRelativeTimeSettingMigration.kt | 22 + .../MoveSecureScreenSettingMigration.kt | 24 + ...oveSettingsToPrivateOrAppStateMigration.kt | 67 ++ .../MoveSortingModeSettingMigration.kt | 27 + .../MoveSortingModeSettingsMigration.kt | 49 ++ ...RemoveBatteryNotLowRestrictionMigration.kt | 21 + .../RemoveOldReaderThemeMigration.kt | 20 + .../RemoveShortLibraryUpdatesMigration.kt | 20 + .../RemoveShorterLibraryUpdatesMigration.kt | 20 + .../RemoveUpdateCheckerJobsMigration.kt | 55 ++ .../ResetFilterAndSortSettingsMigration.kt | 39 + .../ResetReaderSettingsMigration.kt | 39 + .../ResetRotationSettingMigration.kt | 25 + .../migrations/SetupBackupCreateMigration.kt | 16 + .../migrations/SetupEHentaiUpdateMigration.kt | 16 + .../migrations/SetupLibraryUpdateMigration.kt | 16 + .../migrations/SetupSyncDataMigration.kt | 16 + .../TrustExtensionRepositoryMigration.kt | 35 + .../java/mihon/core/migration/MigratorTest.kt | 96 +++ 51 files changed, 1670 insertions(+), 954 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/Migrations.kt create mode 100644 app/src/main/java/mihon/core/migration/MigrateUtils.kt create mode 100644 app/src/main/java/mihon/core/migration/Migration.kt create mode 100644 app/src/main/java/mihon/core/migration/MigrationContext.kt create mode 100644 app/src/main/java/mihon/core/migration/Migrator.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/AlwaysBackupMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ChangeMiuiExtensionInstallerMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ChangeThemeModeToUppercaseMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ChangeTrackingQueueTypeMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ClearBrokenPagePreviewCacheMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/DelegateHBrowseMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/DelegateNHentaiMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/DeleteOldEhFavoritesDatabaseMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/DeleteOldMangaDexTracksMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/LogoutFromMALMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/LogoutFromMangaDexMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MergedMangaRewriteMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/Migrations.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveCacheToDiskSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveCatalogueCoverOnlyGridSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveCoverOnlyGridSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveDOHSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveEncryptionSettingsToAppStateMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveExtensionRepoSettingsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveLatestToFeedMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveLibraryNonCompleteSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveLibrarySortingSettingsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveReaderTapSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveReadingButtonSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveRelativeTimeSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveSecureScreenSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveSettingsToPrivateOrAppStateMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/RemoveBatteryNotLowRestrictionMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/RemoveOldReaderThemeMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/RemoveShortLibraryUpdatesMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/RemoveShorterLibraryUpdatesMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/RemoveUpdateCheckerJobsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ResetFilterAndSortSettingsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ResetReaderSettingsMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/ResetRotationSettingMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/SetupBackupCreateMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/SetupEHentaiUpdateMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/SetupLibraryUpdateMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/SetupSyncDataMigration.kt create mode 100644 app/src/main/java/mihon/core/migration/migrations/TrustExtensionRepositoryMigration.kt create mode 100644 app/src/test/java/mihon/core/migration/MigratorTest.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt deleted file mode 100644 index ad4d581d4..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ /dev/null @@ -1,71 +0,0 @@ -package eu.kanade.tachiyomi - -import android.content.Context -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob -import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import logcat.LogPriority -import mihon.domain.extensionrepo.exception.SaveExtensionRepoException -import mihon.domain.extensionrepo.repository.ExtensionRepoRepository -import tachiyomi.core.common.preference.Preference -import tachiyomi.core.common.preference.PreferenceStore -import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.core.common.util.system.logcat - -object Migrations { - - // TODO NATIVE TACHIYOMI MIGRATIONS ARE FUCKED UP DUE TO DIFFERING VERSION NUMBERS - - /** - * Performs a migration when the application is updated. - * - * @return true if a migration is performed, false otherwise. - */ - @Suppress("SameReturnValue", "MagicNumber") - fun upgrade( - context: Context, - preferenceStore: PreferenceStore, - sourcePreferences: SourcePreferences, - extensionRepoRepository: ExtensionRepoRepository, - ): Boolean { - val lastVersionCode = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0) - val oldVersion = lastVersionCode.get() - if (oldVersion < BuildConfig.VERSION_CODE) { - lastVersionCode.set(BuildConfig.VERSION_CODE) - - // Always set up background tasks to ensure they're running - LibraryUpdateJob.setupTask(context) - BackupCreateJob.setupTask(context) - - // Fresh install - if (oldVersion == 0) { - return false - } - - val coroutineScope = CoroutineScope(Dispatchers.IO) - - if (oldVersion < 6) { - coroutineScope.launchIO { - for ((index, source) in sourcePreferences.extensionRepos().get().withIndex()) { - try { - extensionRepoRepository.upsertRepo( - source, - "Repo #${index + 1}", - null, - source, - "NOFINGERPRINT-${index + 1}", - ) - } catch (e: SaveExtensionRepoException) { - logcat(LogPriority.ERROR, e) { "Error Migrating Extension Repo with baseUrl: $source" } - } - } - sourcePreferences.extensionRepos().delete() - } - } - } - - return false - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index d4d79a6a7..2f5a1ff06 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -50,8 +50,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.firebase.analytics.ktx.analytics import com.google.firebase.ktx.Firebase import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.domain.ui.UiPreferences import eu.kanade.presentation.components.AppStateBanners import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor @@ -80,7 +78,6 @@ import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim import eu.kanade.tachiyomi.util.system.isPreviewBuildType import eu.kanade.tachiyomi.util.view.setComposeContent -import exh.EXHMigrations import exh.debug.DebugToggles import exh.eh.EHentaiUpdateWorker import exh.log.DebugModeOverlay @@ -97,7 +94,11 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import logcat.LogPriority +import mihon.core.migration.Migrator +import mihon.core.migration.migrations.migrations import tachiyomi.core.common.Constants +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.UnsortedPreferences @@ -113,9 +114,7 @@ import androidx.compose.ui.graphics.Color.Companion as ComposeColor class MainActivity : BaseActivity() { - private val sourcePreferences: SourcePreferences by injectLazy() private val libraryPreferences: LibraryPreferences by injectLazy() - private val uiPreferences: UiPreferences by injectLazy() private val preferences: BasePreferences by injectLazy() // SY --> @@ -165,21 +164,7 @@ class MainActivity : BaseActivity() { val didMigration = if (isLaunch) { addAnalytics() - EXHMigrations.upgrade( - context = applicationContext, - basePreferences = preferences, - uiPreferences = uiPreferences, - preferenceStore = Injekt.get(), - networkPreferences = Injekt.get(), - sourcePreferences = sourcePreferences, - securityPreferences = Injekt.get(), - libraryPreferences = libraryPreferences, - readerPreferences = Injekt.get(), - backupPreferences = Injekt.get(), - trackerManager = Injekt.get(), - pagePreviewCache = Injekt.get(), - extensionRepoRepository = Injekt.get(), - ) + migrate() } else { false } @@ -423,6 +408,23 @@ class MainActivity : BaseActivity() { } } + private fun migrate(): Boolean { + val preferenceStore = Injekt.get() + // SY --> + val preference = preferenceStore.getInt(Preference.appStateKey("eh_last_version_code"), 0) + // SY <-- + logcat { "Migration from ${preference.get()} to ${BuildConfig.VERSION_CODE}" } + return Migrator.migrate( + old = preference.get(), + new = BuildConfig.VERSION_CODE, + migrations = migrations, + onMigrationComplete = { + logcat { "Updating last version to ${BuildConfig.VERSION_CODE}" } + preference.set(BuildConfig.VERSION_CODE) + }, + ) + } + /** * Sets custom splash screen exit animation on devices prior to Android 12. * diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index 5e5b87cfb..81ba03268 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -1,723 +1,15 @@ package exh -import android.content.Context -import android.widget.Toast -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.sync.SyncDataJob -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.toast -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 mihon.domain.extensionrepo.exception.SaveExtensionRepoException -import mihon.domain.extensionrepo.repository.ExtensionRepoRepository -import tachiyomi.core.common.preference.Preference -import tachiyomi.core.common.preference.PreferenceStore -import tachiyomi.core.common.preference.TriState -import tachiyomi.core.common.preference.getAndSet -import tachiyomi.core.common.preference.getEnum -import tachiyomi.core.common.preference.minusAssign -import tachiyomi.core.common.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, - extensionRepoRepository: ExtensionRepoRepository, - ): 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) - SyncDataJob.setupTask(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() - val mergedMangaReferences = mutableListOf() - 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() - 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(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" } - } - } - } - 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() - } - replacePreferences( - preferenceStore = preferenceStore, - filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }, - newKey = { Preference.privateKey(it) }, - ) - prefs.edit { - remove(Preference.appStateKey("trusted_signatures")) - } - } - if (oldVersion under 66) { - val cacheImagesToDisk = prefs.getBoolean("cache_archive_manga_on_disk", false) - if (cacheImagesToDisk) { - readerPreferences.archiveReaderMode().set(ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) - } - } - - if (oldVersion under 66) { - if (prefs.getBoolean(Preference.privateKey("encrypt_database"), false)) { - context.toast( - "Restart the app to load your encrypted library", - Toast.LENGTH_LONG - ) - } - - val appStatePrefsToReplace = listOf( - "__PRIVATE_sql_password", - "__PRIVATE_encrypt_database", - "__PRIVATE_cbz_password", - ) - - replacePreferences( - preferenceStore = preferenceStore, - filterPredicate = { it.key in appStatePrefsToReplace }, - newKey = { Preference.appStateKey(it.replace("__PRIVATE_", "").trim()) }, - ) - } - - if (oldVersion under 67) { - runBlocking { - for ((index, source) in sourcePreferences.extensionRepos().get().withIndex()) { - try { - extensionRepoRepository.upsertRepo( - source, - "Repo #${index + 1}", - null, - source, - "NOFINGERPRINT-${index + 1}", - ) - } catch (e: SaveExtensionRepoException) { - logcat(LogPriority.ERROR, e) { "Error Migrating Extension Repo with baseUrl: $source" } - } - } - sourcePreferences.extensionRepos().delete() - } - } - - // 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 @@ -769,102 +61,4 @@ object EXHMigrations { 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, - ) { - 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) -> 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)?.let { - preferenceStore.getStringSet(newKey(key)).set(value) - preferenceStore.getStringSet(key).delete() - } - } - } - } } diff --git a/app/src/main/java/exh/debug/DebugFunctions.kt b/app/src/main/java/exh/debug/DebugFunctions.kt index 6d7be9655..91e600439 100644 --- a/app/src/main/java/exh/debug/DebugFunctions.kt +++ b/app/src/main/java/exh/debug/DebugFunctions.kt @@ -1,23 +1,15 @@ package exh.debug import android.app.Application -import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.model.toSManga -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.domain.ui.UiPreferences -import eu.kanade.tachiyomi.core.security.SecurityPreferences +import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.cache.PagePreviewCache import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.sync.SyncDataJob -import eu.kanade.tachiyomi.data.track.TrackerManager -import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.source.AndroidSourceManager import eu.kanade.tachiyomi.source.online.all.NHentai -import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.system.workManager -import exh.EXHMigrations import exh.eh.EHentaiThrottleManager import exh.eh.EHentaiUpdateWorker import exh.metadata.metadata.EHentaiSearchMetadata @@ -27,11 +19,9 @@ import exh.source.nHentaiSourceIds import exh.util.jobScheduler import kotlinx.coroutines.runBlocking import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator -import mihon.domain.extensionrepo.repository.ExtensionRepoRepository -import tachiyomi.core.common.preference.PreferenceStore +import mihon.core.migration.Migrator +import mihon.core.migration.migrations.migrations import tachiyomi.data.DatabaseHandler -import tachiyomi.domain.backup.service.BackupPreferences -import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetAllManga import tachiyomi.domain.manga.interactor.GetExhFavoriteMangaWithMetadata import tachiyomi.domain.manga.interactor.GetFavorites @@ -48,16 +38,6 @@ import java.util.UUID object DebugFunctions { private val app: Application by injectLazy() private val handler: DatabaseHandler by injectLazy() - private val prefsStore: PreferenceStore by injectLazy() - private val basePrefs: BasePreferences by injectLazy() - private val uiPrefs: UiPreferences by injectLazy() - private val networkPrefs: NetworkPreferences by injectLazy() - private val sourcePrefs: SourcePreferences by injectLazy() - private val securityPrefs: SecurityPreferences by injectLazy() - private val libraryPrefs: LibraryPreferences by injectLazy() - private val readerPrefs: ReaderPreferences by injectLazy() - private val backupPrefs: BackupPreferences by injectLazy() - private val trackerManager: TrackerManager by injectLazy() private val sourceManager: SourceManager by injectLazy() private val updateManga: UpdateManga by injectLazy() private val getFavorites: GetFavorites by injectLazy() @@ -66,46 +46,22 @@ object DebugFunctions { private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy() private val getSearchMetadata: GetSearchMetadata by injectLazy() private val getAllManga: GetAllManga by injectLazy() - private val pagePreviewCache: PagePreviewCache by injectLazy() - private val extensionRepoRepository: ExtensionRepoRepository by injectLazy() fun forceUpgradeMigration() { - val lastVersionCode = prefsStore.getInt("eh_last_version_code", 0) - lastVersionCode.set(1) - EXHMigrations.upgrade( - context = app, - preferenceStore = prefsStore, - basePreferences = basePrefs, - uiPreferences = uiPrefs, - networkPreferences = networkPrefs, - sourcePreferences = sourcePrefs, - securityPreferences = securityPrefs, - libraryPreferences = libraryPrefs, - readerPreferences = readerPrefs, - backupPreferences = backupPrefs, - trackerManager = trackerManager, - pagePreviewCache = pagePreviewCache, - extensionRepoRepository = extensionRepoRepository, + Migrator.migrate( + old = 1, + new = BuildConfig.VERSION_CODE, + migrations = migrations, + onMigrationComplete = {} ) } fun forceSetupJobs() { - val lastVersionCode = prefsStore.getInt("eh_last_version_code", 0) - lastVersionCode.set(0) - EXHMigrations.upgrade( - context = app, - preferenceStore = prefsStore, - basePreferences = basePrefs, - uiPreferences = uiPrefs, - networkPreferences = networkPrefs, - sourcePreferences = sourcePrefs, - securityPreferences = securityPrefs, - libraryPreferences = libraryPrefs, - readerPreferences = readerPrefs, - backupPreferences = backupPrefs, - trackerManager = trackerManager, - pagePreviewCache = pagePreviewCache, - extensionRepoRepository = extensionRepoRepository, + Migrator.migrate( + old = 0, + new = BuildConfig.VERSION_CODE, + migrations = migrations, + onMigrationComplete = {} ) } diff --git a/app/src/main/java/mihon/core/migration/MigrateUtils.kt b/app/src/main/java/mihon/core/migration/MigrateUtils.kt new file mode 100644 index 000000000..951c4d01c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/MigrateUtils.kt @@ -0,0 +1,53 @@ +package mihon.core.migration + +import kotlinx.coroutines.runBlocking +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.data.DatabaseHandler + +object MigrateUtils { + fun updateSourceId(migrationContext: MigrationContext, newId: Long, oldId: Long) { + val handler = migrationContext.get() ?: return + runBlocking { + handler.await { ehQueries.migrateSource(newId, oldId) } + } + } + + @Suppress("UNCHECKED_CAST") + fun replacePreferences( + preferenceStore: PreferenceStore, + filterPredicate: (Map.Entry) -> 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)?.let { + preferenceStore.getStringSet(newKey(key)).set(value) + preferenceStore.getStringSet(key).delete() + } + } + } + } +} + diff --git a/app/src/main/java/mihon/core/migration/Migration.kt b/app/src/main/java/mihon/core/migration/Migration.kt new file mode 100644 index 000000000..2fa04d1c9 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/Migration.kt @@ -0,0 +1,19 @@ +package mihon.core.migration + +interface Migration { + val version: Float + + suspend operator fun invoke(migrationContext: MigrationContext): Boolean + + companion object { + const val ALWAYS = -1f + + fun of(version: Float, action: suspend (MigrationContext) -> Boolean): Migration = object : Migration { + override val version: Float = version + + override suspend operator fun invoke(migrationContext: MigrationContext): Boolean { + return action(migrationContext) + } + } + } +} diff --git a/app/src/main/java/mihon/core/migration/MigrationContext.kt b/app/src/main/java/mihon/core/migration/MigrationContext.kt new file mode 100644 index 000000000..68ddf6464 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/MigrationContext.kt @@ -0,0 +1,10 @@ +package mihon.core.migration + +import uy.kohesive.injekt.Injekt + +class MigrationContext { + + inline fun get(): T? { + return Injekt.getInstanceOrNull(T::class.java) + } +} diff --git a/app/src/main/java/mihon/core/migration/Migrator.kt b/app/src/main/java/mihon/core/migration/Migrator.kt new file mode 100644 index 000000000..86288e2a0 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/Migrator.kt @@ -0,0 +1,53 @@ +package mihon.core.migration + +import kotlinx.coroutines.runBlocking +import tachiyomi.core.common.util.system.logcat + +object Migrator { + + @SuppressWarnings("ReturnCount") + fun migrate( + old: Int, + new: Int, + migrations: List, + dryrun: Boolean = false, + onMigrationComplete: () -> Unit + ): Boolean { + val migrationContext = MigrationContext() + + if (old == 0) { + return migrationContext.migrate( + migrations = migrations.filter { it.isAlways() }, + dryrun = dryrun + ) + .also { onMigrationComplete() } + } + + if (old >= new) { + return false + } + + return migrationContext.migrate( + migrations = migrations.filter { it.isAlways() || it.version.toInt() in (old + 1)..new }, + dryrun = dryrun + ) + .also { onMigrationComplete() } + } + + private fun Migration.isAlways() = version == Migration.ALWAYS + + @SuppressWarnings("MaxLineLength") + private fun MigrationContext.migrate(migrations: List, dryrun: Boolean): Boolean { + return migrations.sortedBy { it.version } + .map { migration -> + if (!dryrun) { + logcat { "Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" } + runBlocking { migration(this@migrate) } + } else { + logcat { "(Dry-run) Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" } + true + } + } + .reduce { acc, b -> acc || b } + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/AlwaysBackupMigration.kt b/app/src/main/java/mihon/core/migration/migrations/AlwaysBackupMigration.kt new file mode 100644 index 000000000..5aafb8ce3 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/AlwaysBackupMigration.kt @@ -0,0 +1,19 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.backup.service.BackupPreferences + +class AlwaysBackupMigration : Migration { + override val version: Float = 40f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val backupPreferences = migrationContext.get() ?: return@withIOContext false + if (backupPreferences.backupInterval().get() == 0) { + backupPreferences.backupInterval().set(12) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ChangeMiuiExtensionInstallerMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ChangeMiuiExtensionInstallerMigration.kt new file mode 100644 index 000000000..cdee8ae4d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ChangeMiuiExtensionInstallerMigration.kt @@ -0,0 +1,24 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.util.system.DeviceUtil +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class ChangeMiuiExtensionInstallerMigration : Migration { + override val version: Float = 27f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val basePreferences = migrationContext.get() ?: return@withIOContext false + if ( + DeviceUtil.isMiui && + basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller + .PACKAGEINSTALLER + ) { + basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ChangeThemeModeToUppercaseMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ChangeThemeModeToUppercaseMigration.kt new file mode 100644 index 000000000..0df2fede1 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ChangeThemeModeToUppercaseMigration.kt @@ -0,0 +1,27 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.domain.ui.UiPreferences +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class ChangeThemeModeToUppercaseMigration : Migration { + override val version: Float = 42f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val uiPreferences = migrationContext.get() ?: return@withIOContext false + if (uiPreferences.themeMode().isSet()) { + prefs.edit { + val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit + putString(uiPreferences.themeMode().key(), themeMode.uppercase()) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ChangeTrackingQueueTypeMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ChangeTrackingQueueTypeMigration.kt new file mode 100644 index 000000000..18dbb3616 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ChangeTrackingQueueTypeMigration.kt @@ -0,0 +1,26 @@ +package mihon.core.migration.migrations + +import android.content.Context +import androidx.core.content.edit +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class ChangeTrackingQueueTypeMigration : Migration { + override val version: Float = 44f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + 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()) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ClearBrokenPagePreviewCacheMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ClearBrokenPagePreviewCacheMigration.kt new file mode 100644 index 000000000..63d4b6005 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ClearBrokenPagePreviewCacheMigration.kt @@ -0,0 +1,33 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.cache.PagePreviewCache +import logcat.LogPriority +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.core.common.util.system.logcat +import java.io.File + +class ClearBrokenPagePreviewCacheMigration : Migration { + override val version: Float = 58f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val pagePreviewCache = migrationContext.get() ?: return@withIOContext false + 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" } + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DelegateHBrowseMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DelegateHBrowseMigration.kt new file mode 100644 index 000000000..6bdf8c28d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DelegateHBrowseMigration.kt @@ -0,0 +1,28 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.manga.interactor.UpdateManga +import exh.source.HBROWSE_SOURCE_ID +import mihon.core.migration.MigrateUtils +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.manga.interactor.GetMangaBySource +import tachiyomi.domain.manga.model.MangaUpdate + +class DelegateHBrowseMigration : Migration { + override val version: Float = 4f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val getMangaBySource = migrationContext.get() ?: return@withIOContext false + val updateManga = migrationContext.get() ?: return@withIOContext false + MigrateUtils.updateSourceId(migrationContext, HBROWSE_SOURCE_ID, 6912) + + // Migrate BHrowse URLs + val hBrowseManga = getMangaBySource.await(HBROWSE_SOURCE_ID) + val mangaUpdates = hBrowseManga.map { + MangaUpdate(it.id, url = it.url + "/c00001/") + } + updateManga.awaitAll(mangaUpdates) + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DelegateNHentaiMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DelegateNHentaiMigration.kt new file mode 100644 index 000000000..e86c2da7c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DelegateNHentaiMigration.kt @@ -0,0 +1,17 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.source.online.all.NHentai +import mihon.core.migration.MigrateUtils +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class DelegateNHentaiMigration : Migration { + override val version: Float = 6f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + MigrateUtils.updateSourceId(migrationContext, NHentai.otherId, 6907) + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DeleteOldEhFavoritesDatabaseMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DeleteOldEhFavoritesDatabaseMigration.kt new file mode 100644 index 000000000..814ef5358 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DeleteOldEhFavoritesDatabaseMigration.kt @@ -0,0 +1,36 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import exh.log.xLogE +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import java.io.File + +class DeleteOldEhFavoritesDatabaseMigration : Migration { + override val version: Float = 24f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + 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) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DeleteOldMangaDexTracksMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DeleteOldMangaDexTracksMigration.kt new file mode 100644 index 000000000..528d8d769 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DeleteOldMangaDexTracksMigration.kt @@ -0,0 +1,17 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.data.DatabaseHandler + +class DeleteOldMangaDexTracksMigration : Migration { + override val version: Float = 17f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val handler = migrationContext.get() ?: return@withIOContext false + // Delete old mangadex trackers + handler.await { ehQueries.deleteBySyncId(6) } + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/LogoutFromMALMigration.kt b/app/src/main/java/mihon/core/migration/migrations/LogoutFromMALMigration.kt new file mode 100644 index 000000000..5d4d5ffb2 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/LogoutFromMALMigration.kt @@ -0,0 +1,17 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.data.track.TrackerManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class LogoutFromMALMigration : Migration { + override val version: Float = 12f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + // Force MAL log out due to login flow change + migrationContext.get()?.myAnimeList?.logout() + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/LogoutFromMangaDexMigration.kt b/app/src/main/java/mihon/core/migration/migrations/LogoutFromMangaDexMigration.kt new file mode 100644 index 000000000..ae3e23cd9 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/LogoutFromMangaDexMigration.kt @@ -0,0 +1,17 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.data.track.TrackerManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class LogoutFromMangaDexMigration : Migration { + override val version: Float = 45f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + // Force MangaDex log out due to login flow change + migrationContext.get()?.mdList?.logout() + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MergedMangaRewriteMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MergedMangaRewriteMigration.kt new file mode 100644 index 000000000..983bbe347 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MergedMangaRewriteMigration.kt @@ -0,0 +1,191 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.tachiyomi.source.Source +import exh.source.MERGED_SOURCE_ID +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.data.DatabaseHandler +import tachiyomi.data.chapter.ChapterMapper +import tachiyomi.domain.chapter.interactor.DeleteChapters +import tachiyomi.domain.chapter.interactor.UpdateChapter +import tachiyomi.domain.chapter.model.ChapterUpdate +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.service.SourceManager + +class MergedMangaRewriteMigration : Migration { + override val version: Float = 7f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val handler = migrationContext.get() ?: return@withIOContext false + val getMangaBySource = migrationContext.get() ?: return@withIOContext false + val getManga = migrationContext.get() ?: return@withIOContext false + val updateManga = migrationContext.get() ?: return@withIOContext false + val insertMergedReference = migrationContext.get() ?: return@withIOContext false + val sourceManager = migrationContext.get() ?: return@withIOContext false + val deleteChapters = migrationContext.get() ?: return@withIOContext false + val updateChapter = migrationContext.get() ?: return@withIOContext false + val mergedMangas = 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() + val mergedMangaReferences = mutableListOf() + mangaConfigs.onEach { mergedManga -> + val newFirst = mergedManga.second.children.firstOrNull()?.url?.let { + if (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(getManga, sourceManager) ?: 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, + ) + } + } + + updateManga.awaitAll(mangaToUpdate) + insertMergedReference.awaitAll(mergedMangaReferences) + + val loadedMangaList = mangaConfigs + .map { it.second.children } + .flatten() + .mapNotNull { it.load(getManga, sourceManager) } + .distinct() + val chapters = + handler.awaitList { + ehQueries.getChaptersByMangaIds( + mergedMangas.map { it.id }, + ChapterMapper::mapChapter, + ) + } + + val mergedMangaChapters = + 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() + 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, + ) + } + } + + deleteChapters.await(mergedMangaChapters.map { it.id }) + updateChapter.awaitAll(chaptersToUpdate) + } + } + return@withIOContext true + } + + + @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, + ) { + 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, + ) { + suspend fun load(getManga: GetManga, sourceManager: SourceManager): LoadedMangaSource? { + val manga = 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) +} diff --git a/app/src/main/java/mihon/core/migration/migrations/Migrations.kt b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt new file mode 100644 index 000000000..514825365 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt @@ -0,0 +1,48 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration + +val migrations: List + get() = listOf( + SetupBackupCreateMigration(), + SetupLibraryUpdateMigration(), + SetupEHentaiUpdateMigration(), + SetupSyncDataMigration(), + DelegateHBrowseMigration(), + DelegateNHentaiMigration(), + MergedMangaRewriteMigration(), + LogoutFromMALMigration(), + MoveDOHSettingMigration(), + ResetRotationSettingMigration(), + ResetReaderSettingsMigration(), + DeleteOldMangaDexTracksMigration(), + RemoveOldReaderThemeMigration(), + RemoveShorterLibraryUpdatesMigration(), + MoveLibrarySortingSettingsMigration(), + RemoveShortLibraryUpdatesMigration(), + MoveLibraryNonCompleteSettingMigration(), + DeleteOldEhFavoritesDatabaseMigration(), + MoveSecureScreenSettingMigration(), + ChangeMiuiExtensionInstallerMigration(), + MoveCoverOnlyGridSettingMigration(), + MoveCatalogueCoverOnlyGridSettingMigration(), + MoveLatestToFeedMigration(), + MoveReaderTapSettingMigration(), + MoveSortingModeSettingsMigration(), + MoveSortingModeSettingMigration(), + AlwaysBackupMigration(), + ResetFilterAndSortSettingsMigration(), + ChangeThemeModeToUppercaseMigration(), + MoveReadingButtonSettingMigration(), + ChangeTrackingQueueTypeMigration(), + LogoutFromMangaDexMigration(), + RemoveUpdateCheckerJobsMigration(), + RemoveBatteryNotLowRestrictionMigration(), + MoveRelativeTimeSettingMigration(), + ClearBrokenPagePreviewCacheMigration(), + MoveSettingsToPrivateOrAppStateMigration(), + MoveExtensionRepoSettingsMigration(), + MoveCacheToDiskSettingMigration(), + MoveEncryptionSettingsToAppStateMigration(), + TrustExtensionRepositoryMigration(), + ) diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveCacheToDiskSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveCacheToDiskSettingMigration.kt new file mode 100644 index 000000000..e0ff302cc --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveCacheToDiskSettingMigration.kt @@ -0,0 +1,24 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveCacheToDiskSettingMigration : Migration { + override val version: Float = 66f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val readerPreferences = migrationContext.get() ?: return@withIOContext false + val cacheImagesToDisk = prefs.getBoolean("cache_archive_manga_on_disk", false) + if (cacheImagesToDisk) { + readerPreferences.archiveReaderMode().set(ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveCatalogueCoverOnlyGridSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveCatalogueCoverOnlyGridSettingMigration.kt new file mode 100644 index 000000000..2d769f64c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveCatalogueCoverOnlyGridSettingMigration.kt @@ -0,0 +1,24 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveCatalogueCoverOnlyGridSettingMigration : Migration { + override val version: Float = 29f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + if (prefs.getString("pref_display_mode_catalogue", null) == "NO_TITLE_GRID") { + prefs.edit(commit = true) { + putString("pref_display_mode_catalogue", "COMPACT_GRID") + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveCoverOnlyGridSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveCoverOnlyGridSettingMigration.kt new file mode 100644 index 000000000..ad31967f0 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveCoverOnlyGridSettingMigration.kt @@ -0,0 +1,24 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveCoverOnlyGridSettingMigration : Migration { + override val version: Float = 28f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + if (prefs.getString("pref_display_mode_library", null) == "NO_TITLE_GRID") { + prefs.edit(commit = true) { + putString("pref_display_mode_library", "COVER_ONLY_GRID") + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveDOHSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveDOHSettingMigration.kt new file mode 100644 index 000000000..2e3656462 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveDOHSettingMigration.kt @@ -0,0 +1,30 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.network.NetworkPreferences +import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveDOHSettingMigration : Migration { + override val version: Float = 14f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val networkPreferences = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + // 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") + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveEncryptionSettingsToAppStateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveEncryptionSettingsToAppStateMigration.kt new file mode 100644 index 000000000..ab817dd06 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveEncryptionSettingsToAppStateMigration.kt @@ -0,0 +1,45 @@ +package mihon.core.migration.migrations + +import android.widget.Toast +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.util.system.toast +import mihon.core.migration.MigrateUtils +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.core.common.util.lang.withUIContext + +class MoveEncryptionSettingsToAppStateMigration : Migration { + override val version: Float = 66f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val preferenceStore = migrationContext.get() ?: return@withIOContext false + if (prefs.getBoolean(Preference.privateKey("encrypt_database"), false)) { + withUIContext { + context.toast( + "Restart the app to load your encrypted library", + Toast.LENGTH_LONG + ) + } + } + + val appStatePrefsToReplace = listOf( + "__PRIVATE_sql_password", + "__PRIVATE_encrypt_database", + "__PRIVATE_cbz_password", + ) + + MigrateUtils.replacePreferences( + preferenceStore = preferenceStore, + filterPredicate = { it.key in appStatePrefsToReplace }, + newKey = { Preference.appStateKey(it.replace("__PRIVATE_", "").trim()) }, + ) + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveExtensionRepoSettingsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveExtensionRepoSettingsMigration.kt new file mode 100644 index 000000000..a29731fe1 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveExtensionRepoSettingsMigration.kt @@ -0,0 +1,37 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.domain.source.service.SourcePreferences +import eu.kanade.tachiyomi.App +import mihon.core.migration.MigrateUtils +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.core.common.util.lang.withIOContext + +class MoveExtensionRepoSettingsMigration : Migration { + override val version: Float = 60f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val preferenceStore = migrationContext.get() ?: return@withIOContext false + val sourcePreferences = migrationContext.get() ?: return@withIOContext false + sourcePreferences.extensionRepos().getAndSet { + it.map { "https://raw.githubusercontent.com/$it/repo" }.toSet() + } + MigrateUtils.replacePreferences( + preferenceStore = preferenceStore, + filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }, + newKey = { Preference.privateKey(it) }, + ) + prefs.edit { + remove(Preference.appStateKey("trusted_signatures")) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveLatestToFeedMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveLatestToFeedMigration.kt new file mode 100644 index 000000000..4eea4b709 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveLatestToFeedMigration.kt @@ -0,0 +1,63 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import exh.util.nullIfBlank +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 mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.source.interactor.InsertFeedSavedSearch +import tachiyomi.domain.source.interactor.InsertSavedSearch +import tachiyomi.domain.source.model.FeedSavedSearch +import tachiyomi.domain.source.model.SavedSearch + +class MoveLatestToFeedMigration : Migration { + override val version: Float = 31f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val insertSavedSearch = migrationContext.get() ?: return@withIOContext false + val insertFeedSavedSearch = migrationContext.get() ?: return@withIOContext false + val savedSearch = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull { + runCatching { + val content = Json.decodeFromString(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") + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveLibraryNonCompleteSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveLibraryNonCompleteSettingMigration.kt new file mode 100644 index 000000000..04a6d4a99 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveLibraryNonCompleteSettingMigration.kt @@ -0,0 +1,25 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.minusAssign +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class MoveLibraryNonCompleteSettingMigration : Migration { + override val version: Float = 23f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true) + if (!oldUpdateOngoingOnly) { + libraryPreferences.autoUpdateMangaRestrictions() -= LibraryPreferences.MANGA_NON_COMPLETED + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveLibrarySortingSettingsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveLibrarySortingSettingsMigration.kt new file mode 100644 index 000000000..d91eff916 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveLibrarySortingSettingsMigration.kt @@ -0,0 +1,57 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.core.common.util.system.logcat +import tachiyomi.domain.library.service.LibraryPreferences + +class MoveLibrarySortingSettingsMigration : Migration { + override val version: Float = 20f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + 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" } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveReaderTapSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveReaderTapSettingMigration.kt new file mode 100644 index 000000000..d117a8cdf --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveReaderTapSettingMigration.kt @@ -0,0 +1,25 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveReaderTapSettingMigration : Migration { + override val version: Float = 32f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val readerPreferences = migrationContext.get() ?: return@withIOContext false + val oldReaderTap = prefs.getBoolean("reader_tap", false) + if (!oldReaderTap) { + readerPreferences.navigationModePager().set(5) + readerPreferences.navigationModeWebtoon().set(5) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveReadingButtonSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveReadingButtonSettingMigration.kt new file mode 100644 index 000000000..7a7398a55 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveReadingButtonSettingMigration.kt @@ -0,0 +1,23 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class MoveReadingButtonSettingMigration : Migration { + override val version: Float = 43f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + if (prefs.getBoolean("start_reading_button", false)) { + libraryPreferences.showContinueReadingButton().set(true) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveRelativeTimeSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveRelativeTimeSettingMigration.kt new file mode 100644 index 000000000..82d955307 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveRelativeTimeSettingMigration.kt @@ -0,0 +1,22 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.ui.UiPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.util.lang.withIOContext + +class MoveRelativeTimeSettingMigration : Migration { + override val version: Float = 57f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val preferenceStore = migrationContext.get() ?: return@withIOContext false + val uiPreferences = migrationContext.get() ?: return@withIOContext false + val pref = preferenceStore.getInt("relative_time", 7) + if (pref.get() == 0) { + uiPreferences.relativeTime().set(false) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveSecureScreenSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveSecureScreenSettingMigration.kt new file mode 100644 index 000000000..b2cd04296 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveSecureScreenSettingMigration.kt @@ -0,0 +1,24 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.core.security.SecurityPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class MoveSecureScreenSettingMigration : Migration { + override val version: Float = 27f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val securityPreferences = migrationContext.get() ?: return@withIOContext false + val oldSecureScreen = prefs.getBoolean("secure_screen", false) + if (oldSecureScreen) { + securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveSettingsToPrivateOrAppStateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveSettingsToPrivateOrAppStateMigration.kt new file mode 100644 index 000000000..de9766cf8 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveSettingsToPrivateOrAppStateMigration.kt @@ -0,0 +1,67 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import mihon.core.migration.MigrateUtils +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.util.lang.withIOContext + +class MoveSettingsToPrivateOrAppStateMigration : Migration { + override val version: Float = 59f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val preferenceStore = migrationContext.get() ?: return@withIOContext false + 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", + ) + MigrateUtils.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", + ) + + MigrateUtils.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() + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingMigration.kt new file mode 100644 index 000000000..14a97195a --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingMigration.kt @@ -0,0 +1,27 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class MoveSortingModeSettingMigration : Migration { + override val version: Float = 39f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + 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") + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingsMigration.kt new file mode 100644 index 000000000..6026da11c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveSortingModeSettingsMigration.kt @@ -0,0 +1,49 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.data.DatabaseHandler +import tachiyomi.data.category.CategoryMapper +import tachiyomi.domain.library.service.LibraryPreferences + +class MoveSortingModeSettingsMigration : Migration { + override val version: Float = 38f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val handler = migrationContext.get() ?: return@withIOContext false + // 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) + } + 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, + ) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveBatteryNotLowRestrictionMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveBatteryNotLowRestrictionMigration.kt new file mode 100644 index 000000000..4381f3a4f --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveBatteryNotLowRestrictionMigration.kt @@ -0,0 +1,21 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class RemoveBatteryNotLowRestrictionMigration : Migration { + override val version: Float = 56f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val pref = libraryPreferences.autoUpdateDeviceRestrictions() + if (pref.isSet() && "battery_not_low" in pref.get()) { + pref.getAndSet { it - "battery_not_low" } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveOldReaderThemeMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveOldReaderThemeMigration.kt new file mode 100644 index 000000000..feeddfb80 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveOldReaderThemeMigration.kt @@ -0,0 +1,20 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class RemoveOldReaderThemeMigration : Migration { + override val version: Float = 18f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val readerPreferences = migrationContext.get() ?: return@withIOContext false + val readerTheme = readerPreferences.readerTheme().get() + if (readerTheme == 4) { + readerPreferences.readerTheme().set(3) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveShortLibraryUpdatesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveShortLibraryUpdatesMigration.kt new file mode 100644 index 000000000..5c8d08cfe --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveShortLibraryUpdatesMigration.kt @@ -0,0 +1,20 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class RemoveShortLibraryUpdatesMigration : Migration { + override val version: Float = 22f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val updateInterval = libraryPreferences.autoUpdateInterval().get() + if (updateInterval in listOf(3, 4, 6, 8)) { + libraryPreferences.autoUpdateInterval().set(12) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveShorterLibraryUpdatesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveShorterLibraryUpdatesMigration.kt new file mode 100644 index 000000000..5716ca125 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveShorterLibraryUpdatesMigration.kt @@ -0,0 +1,20 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class RemoveShorterLibraryUpdatesMigration : Migration { + override val version: Float = 18f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + val updateInterval = libraryPreferences.autoUpdateInterval().get() + if (updateInterval == 1 || updateInterval == 2) { + libraryPreferences.autoUpdateInterval().set(3) + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveUpdateCheckerJobsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveUpdateCheckerJobsMigration.kt new file mode 100644 index 000000000..eddcf6515 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveUpdateCheckerJobsMigration.kt @@ -0,0 +1,55 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.track.TrackerManager +import eu.kanade.tachiyomi.util.system.workManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.TriState +import tachiyomi.core.common.preference.getEnum +import tachiyomi.core.common.util.lang.withIOContext + +class RemoveUpdateCheckerJobsMigration : Migration { + override val version: Float = 52f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val preferenceStore = migrationContext.get() ?: return@withIOContext false + val trackerManager = migrationContext.get() ?: return@withIOContext false + // 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 = prefs.getInt(key, 0) + prefs.edit { + remove(key) + + val newValue = when (pref) { + 1 -> TriState.ENABLED_IS + 2 -> TriState.ENABLED_NOT + else -> TriState.DISABLED + } + + preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ResetFilterAndSortSettingsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ResetFilterAndSortSettingsMigration.kt new file mode 100644 index 000000000..4dced90ed --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ResetFilterAndSortSettingsMigration.kt @@ -0,0 +1,39 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.domain.library.service.LibraryPreferences + +class ResetFilterAndSortSettingsMigration : Migration { + override val version: Float = 41f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val libraryPreferences = migrationContext.get() ?: return@withIOContext false + 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()) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ResetReaderSettingsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ResetReaderSettingsMigration.kt new file mode 100644 index 000000000..1f7e1fb2f --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ResetReaderSettingsMigration.kt @@ -0,0 +1,39 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class ResetReaderSettingsMigration : Migration { + override val version: Float = 17f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + // 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") + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ResetRotationSettingMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ResetRotationSettingMigration.kt new file mode 100644 index 000000000..b005ea978 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ResetRotationSettingMigration.kt @@ -0,0 +1,25 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.util.lang.withIOContext + +class ResetRotationSettingMigration : Migration { + override val version: Float = 16f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val context = migrationContext.get() ?: return@withIOContext false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + // Reset rotation to Free after replacing Lock + if (prefs.contains("pref_rotation_type_key")) { + prefs.edit { + putInt("pref_rotation_type_key", 1) + } + } + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupBackupCreateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupBackupCreateMigration.kt new file mode 100644 index 000000000..44c0b557c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupBackupCreateMigration.kt @@ -0,0 +1,16 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupBackupCreateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + BackupCreateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupEHentaiUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupEHentaiUpdateMigration.kt new file mode 100644 index 000000000..00097dd4e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupEHentaiUpdateMigration.kt @@ -0,0 +1,16 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import exh.eh.EHentaiUpdateWorker +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupEHentaiUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + EHentaiUpdateWorker.scheduleBackground(context) + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupLibraryUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupLibraryUpdateMigration.kt new file mode 100644 index 000000000..65482cd89 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupLibraryUpdateMigration.kt @@ -0,0 +1,16 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupLibraryUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + LibraryUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupSyncDataMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupSyncDataMigration.kt new file mode 100644 index 000000000..1f668724c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupSyncDataMigration.kt @@ -0,0 +1,16 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.sync.SyncDataJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupSyncDataMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + SyncDataJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/TrustExtensionRepositoryMigration.kt b/app/src/main/java/mihon/core/migration/migrations/TrustExtensionRepositoryMigration.kt new file mode 100644 index 000000000..b5dd80ff5 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/TrustExtensionRepositoryMigration.kt @@ -0,0 +1,35 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.source.service.SourcePreferences +import logcat.LogPriority +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import mihon.domain.extensionrepo.exception.SaveExtensionRepoException +import mihon.domain.extensionrepo.repository.ExtensionRepoRepository +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.core.common.util.system.logcat + +class TrustExtensionRepositoryMigration : Migration { + override val version: Float = 67f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val sourcePreferences = migrationContext.get() ?: return@withIOContext false + val extensionRepositoryRepository = + migrationContext.get() ?: return@withIOContext false + for ((index, source) in sourcePreferences.extensionRepos().get().withIndex()) { + try { + extensionRepositoryRepository.upsertRepo( + source, + "Repo #${index + 1}", + null, + source, + "NOFINGERPRINT-${index + 1}", + ) + } catch (e: SaveExtensionRepoException) { + logcat(LogPriority.ERROR, e) { "Error Migrating Extension Repo with baseUrl: $source" } + } + } + sourcePreferences.extensionRepos().delete() + return@withIOContext true + } +} diff --git a/app/src/test/java/mihon/core/migration/MigratorTest.kt b/app/src/test/java/mihon/core/migration/MigratorTest.kt new file mode 100644 index 000000000..89fe4db8c --- /dev/null +++ b/app/src/test/java/mihon/core/migration/MigratorTest.kt @@ -0,0 +1,96 @@ +package mihon.core.migration + +import io.mockk.Called +import io.mockk.spyk +import io.mockk.verify +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class MigratorTest { + + @Test + fun initialVersion() { + val onMigrationComplete: () -> Unit = {} + val onMigrationCompleteSpy = spyk(onMigrationComplete) + val didMigration = Migrator.migrate( + old = 0, + new = 1, + migrations = listOf(Migration.of(Migration.ALWAYS) { true }, Migration.of(2f) { false }), + onMigrationComplete = onMigrationCompleteSpy + ) + verify { onMigrationCompleteSpy() } + Assertions.assertTrue(didMigration) + } + + @Test + fun sameVersion() { + val onMigrationComplete: () -> Unit = {} + val onMigrationCompleteSpy = spyk(onMigrationComplete) + val didMigration = Migrator.migrate( + old = 1, + new = 1, + migrations = listOf(Migration.of(Migration.ALWAYS) { true }, Migration.of(2f) { true }), + onMigrationComplete = onMigrationCompleteSpy + ) + verify { onMigrationCompleteSpy wasNot Called } + Assertions.assertFalse(didMigration) + } + + @Test + fun smallMigration() { + val onMigrationComplete: () -> Unit = {} + val onMigrationCompleteSpy = spyk(onMigrationComplete) + val didMigration = Migrator.migrate( + old = 1, + new = 2, + migrations = listOf(Migration.of(Migration.ALWAYS) { true }, Migration.of(2f) { true }), + onMigrationComplete = onMigrationCompleteSpy + ) + verify { onMigrationCompleteSpy() } + Assertions.assertTrue(didMigration) + } + + @Test + fun largeMigration() { + val onMigrationComplete: () -> Unit = {} + val onMigrationCompleteSpy = spyk(onMigrationComplete) + val input = listOf( + Migration.of(Migration.ALWAYS) { true }, + Migration.of(2f) { true }, + Migration.of(3f) { true }, + Migration.of(4f) { true }, + Migration.of(5f) { true }, + Migration.of(6f) { true }, + Migration.of(7f) { true }, + Migration.of(8f) { true }, + Migration.of(9f) { true }, + Migration.of(10f) { true }, + ) + val didMigration = Migrator.migrate( + old = 1, + new = 10, + migrations = input, + onMigrationComplete = onMigrationCompleteSpy + ) + verify { onMigrationCompleteSpy() } + Assertions.assertTrue(didMigration) + } + + @Test + fun withinRangeMigration() { + val onMigrationComplete: () -> Unit = {} + val onMigrationCompleteSpy = spyk(onMigrationComplete) + val didMigration = Migrator.migrate( + old = 1, + new = 2, + migrations = listOf( + Migration.of(Migration.ALWAYS) { true }, + Migration.of(2f) { true }, + Migration.of(3f) { false } + ), + onMigrationComplete = onMigrationCompleteSpy + ) + verify { onMigrationCompleteSpy() } + Assertions.assertTrue(didMigration) + } +}