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 a3d5011bf..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ /dev/null @@ -1,696 +0,0 @@ -package eu.kanade.tachiyomi - -import android.content.Context -import androidx.core.content.edit -import androidx.preference.PreferenceManager -import eu.kanade.domain.base.BasePreferences -import eu.kanade.domain.source.service.SourcePreferences -import eu.kanade.domain.ui.UiPreferences -import eu.kanade.domain.ui.model.NavStyle -import eu.kanade.domain.ui.model.StartScreen -import eu.kanade.tachiyomi.core.security.SecurityPreferences -import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob -import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob -import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob -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.ui.player.settings.PlayerPreferences -import eu.kanade.tachiyomi.ui.player.viewer.AspectState -import eu.kanade.tachiyomi.ui.player.viewer.HwDecState -import eu.kanade.tachiyomi.ui.player.viewer.InvertedPlayback -import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding -import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation -import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences -import eu.kanade.tachiyomi.util.system.DeviceUtil -import eu.kanade.tachiyomi.util.system.workManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import logcat.LogPriority -import mihon.domain.extensionrepo.anime.repository.AnimeExtensionRepoRepository -import mihon.domain.extensionrepo.exception.SaveExtensionRepoException -import mihon.domain.extensionrepo.manga.repository.MangaExtensionRepoRepository -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.preference.plusAssign -import tachiyomi.core.common.util.lang.launchIO -import tachiyomi.core.common.util.system.logcat -import tachiyomi.domain.backup.service.BackupPreferences -import tachiyomi.domain.library.service.LibraryPreferences -import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED -import java.io.File - -object Migrations { - - /** - * Performs a migration when the application is updated. - * - * @return true if a migration is performed, false otherwise. - */ - @Suppress( - "SameReturnValue", - "MagicNumber", - "CyclomaticComplexMethod", - "LongMethod", - "LongParameterList", - "NestedBlockDepth", - "ReturnCount", - ) - fun upgrade( - context: Context, - preferenceStore: PreferenceStore, - basePreferences: BasePreferences, - uiPreferences: UiPreferences, - networkPreferences: NetworkPreferences, - sourcePreferences: SourcePreferences, - securityPreferences: SecurityPreferences, - libraryPreferences: LibraryPreferences, - readerPreferences: ReaderPreferences, - playerPreferences: PlayerPreferences, - backupPreferences: BackupPreferences, - trackerManager: TrackerManager, - animeExtensionRepoRepository: AnimeExtensionRepoRepository, - mangaExtensionRepoRepository: MangaExtensionRepoRepository, - ): 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 - MangaLibraryUpdateJob.setupTask(context) - AnimeLibraryUpdateJob.setupTask(context) - BackupCreateJob.setupTask(context) - - // Fresh install - if (oldVersion == 0) { - return false - } - - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - - if (oldVersion < 15) { - // Delete internal chapter cache dir. - File(context.cacheDir, "chapter_disk_cache").deleteRecursively() - } - if (oldVersion < 19) { - // Move covers to external files dir. - val oldDir = File(context.externalCacheDir, "cover_disk_cache") - if (oldDir.exists()) { - val destDir = context.getExternalFilesDir("covers") - if (destDir != null) { - oldDir.listFiles()?.forEach { - it.renameTo(File(destDir, it.name)) - } - } - } - } - if (oldVersion < 26) { - // Delete external chapter cache dir. - val extCache = context.externalCacheDir - if (extCache != null) { - val chapterCache = File(extCache, "chapter_disk_cache") - if (chapterCache.exists()) { - chapterCache.deleteRecursively() - } - } - } - if (oldVersion < 44) { - // Reset sorting preference if using removed sort by source - val oldMangaSortingMode = prefs.getInt( - libraryPreferences.mangaSortingMode().key(), - 0, - ) - - if (oldMangaSortingMode == 5) { // SOURCE = 5 - prefs.edit { - putInt(libraryPreferences.mangaSortingMode().key(), 0) // ALPHABETICAL = 0 - } - } - - val oldAnimeSortingMode = prefs.getInt( - libraryPreferences.animeSortingMode().key(), - 0, - ) - - if (oldAnimeSortingMode == 5) { // SOURCE = 5 - prefs.edit { - putInt(libraryPreferences.animeSortingMode().key(), 0) // ALPHABETICAL = 0 - } - } - } - if (oldVersion < 52) { - // Migrate library filters to tri-state versions - fun convertBooleanPrefToTriState(key: String): Int { - val oldPrefValue = prefs.getBoolean(key, false) - return if (oldPrefValue) { - 1 - } else { - 0 - } - } - prefs.edit { - putInt( - libraryPreferences.filterDownloadedManga().key(), - convertBooleanPrefToTriState("pref_filter_downloaded_key"), - ) - remove("pref_filter_downloaded_key") - - putInt( - libraryPreferences.filterUnread().key(), - convertBooleanPrefToTriState("pref_filter_unread_key"), - ) - remove("pref_filter_unread_key") - - putInt( - libraryPreferences.filterCompletedManga().key(), - convertBooleanPrefToTriState("pref_filter_completed_key"), - ) - remove("pref_filter_completed_key") - } - } - if (oldVersion < 54) { - // Force MAL log out due to login flow change - // v52: switched from scraping to WebView - // v53: switched from WebView to OAuth - if (trackerManager.myAnimeList.isLoggedIn) { - trackerManager.myAnimeList.logout() - } - } - if (oldVersion < 57) { - // 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 < 59) { - // Reset rotation to Free after replacing Lock - if (prefs.contains("pref_rotation_type_key")) { - prefs.edit { - putInt("pref_rotation_type_key", 1) - } - } - } - if (oldVersion < 60) { - // 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") - } - } - if (oldVersion < 61) { - // Handle removed every 1 or 2 hour library updates - val updateInterval = libraryPreferences.autoUpdateInterval().get() - if (updateInterval == 1 || updateInterval == 2) { - libraryPreferences.autoUpdateInterval().set(3) - MangaLibraryUpdateJob.setupTask(context, 3) - AnimeLibraryUpdateJob.setupTask(context, 3) - } - } - if (oldVersion < 64) { - // Set up background tasks - MangaLibraryUpdateJob.setupTask(context) - AnimeLibraryUpdateJob.setupTask(context) - } - if (oldVersion < 64) { - val oldMangaSortingMode = prefs.getInt( - libraryPreferences.mangaSortingMode().key(), - 0, - ) - val oldAnimeSortingMode = prefs.getInt( - libraryPreferences.animeSortingMode().key(), - 0, - ) - val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true) - - val newMangaSortingMode = when (oldMangaSortingMode) { - 0 -> "ALPHABETICAL" - 1 -> "LAST_READ" - 2 -> "LAST_CHECKED" - 3 -> "UNREAD" - 4 -> "TOTAL_CHAPTERS" - 6 -> "LATEST_CHAPTER" - 8 -> "DATE_FETCHED" - 7 -> "DATE_ADDED" - else -> "ALPHABETICAL" - } - - val newAnimeSortingMode = when (oldAnimeSortingMode) { - 0 -> "ALPHABETICAL" - 1 -> "LAST_SEEN" - 2 -> "LAST_CHECKED" - 3 -> "UNSEEN" - 4 -> "TOTAL_EPISODES" - 6 -> "LATEST_EPISODE" - 8 -> "DATE_FETCHED" - 7 -> "DATE_ADDED" - else -> "ALPHABETICAL" - } - - val newSortingDirection = when (oldSortingDirection) { - true -> "ASCENDING" - else -> "DESCENDING" - } - - prefs.edit(commit = true) { - remove(libraryPreferences.mangaSortingMode().key()) - remove(libraryPreferences.animeSortingMode().key()) - remove("library_sorting_ascending") - } - - prefs.edit { - putString(libraryPreferences.mangaSortingMode().key(), newMangaSortingMode) - putString(libraryPreferences.animeSortingMode().key(), newAnimeSortingMode) - putString("library_sorting_ascending", newSortingDirection) - } - } - if (oldVersion < 70) { - if (sourcePreferences.enabledLanguages().isSet()) { - sourcePreferences.enabledLanguages() += "all" - } - } - if (oldVersion < 71) { - // 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) - MangaLibraryUpdateJob.setupTask(context, 12) - AnimeLibraryUpdateJob.setupTask(context, 12) - } - } - if (oldVersion < 72) { - val oldUpdateOngoingOnly = prefs.getBoolean( - "pref_update_only_non_completed_key", - true, - ) - if (!oldUpdateOngoingOnly) { - libraryPreferences.autoUpdateItemRestrictions() -= ENTRY_NON_COMPLETED - } - } - if (oldVersion < 75) { - 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 < 77) { - val oldReaderTap = prefs.getBoolean("reader_tap", false) - if (!oldReaderTap) { - readerPreferences.navigationModePager().set(5) - readerPreferences.navigationModeWebtoon().set(5) - } - } - if (oldVersion < 81) { - // Handle renamed enum values - prefs.edit { - val newMangaSortingMode = when ( - val oldSortingMode = prefs.getString( - libraryPreferences.mangaSortingMode().key(), - "ALPHABETICAL", - ) - ) { - "LAST_CHECKED" -> "LAST_MANGA_UPDATE" - "UNREAD" -> "UNREAD_COUNT" - "DATE_FETCHED" -> "CHAPTER_FETCH_DATE" - else -> oldSortingMode - } - val newAnimeSortingMode = when ( - val oldSortingMode = prefs.getString( - libraryPreferences.animeSortingMode().key(), - "ALPHABETICAL", - ) - ) { - "LAST_CHECKED" -> "LAST_MANGA_UPDATE" - "UNREAD" -> "UNREAD_COUNT" - "DATE_FETCHED" -> "CHAPTER_FETCH_DATE" - else -> oldSortingMode - } - putString(libraryPreferences.mangaSortingMode().key(), newMangaSortingMode) - putString(libraryPreferences.animeSortingMode().key(), newAnimeSortingMode) - } - } - if (oldVersion < 82) { - prefs.edit { - val mangasort = prefs.getString( - libraryPreferences.mangaSortingMode().key(), - null, - ) ?: return@edit - val animesort = prefs.getString( - libraryPreferences.animeSortingMode().key(), - null, - ) ?: return@edit - val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!! - putString(libraryPreferences.mangaSortingMode().key(), "$mangasort,$direction") - putString(libraryPreferences.animeSortingMode().key(), "$animesort,$direction") - remove("library_sorting_ascending") - } - } - if (oldVersion < 84) { - if (backupPreferences.backupInterval().get() == 0) { - backupPreferences.backupInterval().set(12) - BackupCreateJob.setupTask(context) - } - } - if (oldVersion < 85) { - val preferences = listOf( - libraryPreferences.filterChapterByRead(), - libraryPreferences.filterChapterByDownloaded(), - libraryPreferences.filterChapterByBookmarked(), - libraryPreferences.sortChapterBySourceOrNumber(), - libraryPreferences.displayChapterByNameOrNumber(), - libraryPreferences.sortChapterByAscendingOrDescending(), - libraryPreferences.filterEpisodeBySeen(), - libraryPreferences.filterEpisodeByDownloaded(), - libraryPreferences.filterEpisodeByBookmarked(), - libraryPreferences.sortEpisodeBySourceOrNumber(), - libraryPreferences.displayEpisodeByNameOrNumber(), - libraryPreferences.sortEpisodeByAscendingOrDescending(), - ) - - 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 < 86) { - if (uiPreferences.themeMode().isSet()) { - prefs.edit { - val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit - putString(uiPreferences.themeMode().key(), themeMode.uppercase()) - } - } - } - if (oldVersion < 92) { - if (playerPreferences.progressPreference().isSet()) { - prefs.edit { - val progressString = try { - prefs.getString(playerPreferences.progressPreference().key(), null) - } catch (e: ClassCastException) { - null - } ?: return@edit - val newProgress = progressString.toFloatOrNull() ?: return@edit - putFloat(playerPreferences.progressPreference().key(), newProgress) - } - } - } - if (oldVersion < 93) { - listOf( - playerPreferences.defaultPlayerOrientationType(), - playerPreferences.defaultPlayerOrientationLandscape(), - playerPreferences.defaultPlayerOrientationPortrait(), - playerPreferences.skipLengthPreference(), - ).forEach { pref -> - if (pref.isSet()) { - prefs.edit { - val oldString = try { - prefs.getString(pref.key(), null) - } catch (e: ClassCastException) { - null - } ?: return@edit - val newInt = oldString.toIntOrNull() ?: return@edit - putInt(pref.key(), newInt) - } - 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 < 96) { - MangaLibraryUpdateJob.cancelAllWorks(context) - AnimeLibraryUpdateJob.cancelAllWorks(context) - MangaLibraryUpdateJob.setupTask(context) - AnimeLibraryUpdateJob.setupTask(context) - } - if (oldVersion < 97) { - // Removed background jobs - context.workManager.cancelAllWorkByTag("UpdateChecker") - context.workManager.cancelAllWorkByTag("ExtensionUpdate") - prefs.edit { - remove("automatic_ext_updates") - } - } - if (oldVersion < 99) { - val prefKeys = listOf( - "pref_filter_library_downloaded", - "pref_filter_library_unread", - "pref_filter_library_unseen", - "pref_filter_library_started", - "pref_filter_library_bookmarked", - "pref_filter_library_completed", - ) + 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 < 105) { - val pref = libraryPreferences.autoUpdateDeviceRestrictions() - if (pref.isSet() && "battery_not_low" in pref.get()) { - pref.getAndSet { it - "battery_not_low" } - } - } - if (oldVersion < 106) { - val pref = preferenceStore.getInt("relative_time", 7) - if (pref.get() == 0) { - uiPreferences.relativeTime().set(false) - } - } - if (oldVersion < 113) { - 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", - "storage_dir", - ) - replacePreferences( - preferenceStore = preferenceStore, - filterPredicate = { it.key in prefsToReplace }, - newKey = { Preference.appStateKey(it) }, - ) - - // Deleting old download cache index files, but might as well clear it all out - context.cacheDir.deleteRecursively() - } - if (oldVersion < 114) { - sourcePreferences.mangaExtensionRepos().getAndSet { - it.map { repo -> "https://raw.githubusercontent.com/$repo/repo" }.toSet() - } - } - if (oldVersion < 116) { - replacePreferences( - preferenceStore = preferenceStore, - filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }, - newKey = { Preference.privateKey(it) }, - ) - } - if (oldVersion < 117) { - prefs.edit { - remove(Preference.appStateKey("trusted_signatures")) - } - } - - if (oldVersion < 120) { - val bottomNavStyle = preferenceStore.getInt("bottom_nav_style", 0) - - val isDefaultTabManga = preferenceStore.getBoolean("default_home_tab_library", false) - prefs.edit { - remove("bottom_nav_style") - remove("default_home_tab_library") - - val startScreen = if (isDefaultTabManga.get()) StartScreen.MANGA else StartScreen.ANIME - val navStyle = when (bottomNavStyle.get()) { - 0 -> NavStyle.MOVE_HISTORY_TO_MORE - 1 -> NavStyle.MOVE_UPDATES_TO_MORE - else -> NavStyle.MOVE_MANGA_TO_MORE - } - - preferenceStore.getEnum("start_screen", StartScreen.ANIME).set(startScreen) - preferenceStore.getEnum("bottom_rail_nav_style", NavStyle.MOVE_HISTORY_TO_MORE).set(navStyle) - } - } - - if (oldVersion < 121) { - if (trackerManager.myAnimeList.isLoggedIn) { - trackerManager.myAnimeList.logout() - } - } - - if (oldVersion < 123) { - val invertedPosition = preferenceStore.getBoolean("pref_invert_playback_txt", false) - val invertedDuration = preferenceStore.getBoolean("pref_invert_duration_txt", false) - val hwDec = preferenceStore.getString("pref_hwdec", HwDecState.defaultHwDec.mpvValue) - val deband = preferenceStore.getInt("pref_deband", 0) - val playerViewMode = preferenceStore.getInt("pref_player_view_mode", 1) - val gpuNext = preferenceStore.getBoolean("gpu_next", false) - - prefs.edit { - remove("pref_invert_playback_txt") - remove("pref_invert_duration_txt") - remove("pref_hwdec") - remove("pref_deband") - remove("pref_player_view_mode") - remove("gpu_next") - - val invertedPlayback = when { - invertedPosition.get() -> InvertedPlayback.POSITION - invertedDuration.get() -> InvertedPlayback.DURATION - else -> InvertedPlayback.NONE - } - val hardwareDecoding = HwDecState.entries.first { it.mpvValue == hwDec.get() } - val videoDebanding = VideoDebanding.entries.first { it.ordinal == deband.get() } - val aspectState = AspectState.entries.first { it.ordinal == playerViewMode.get() } - - preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE).set(invertedPlayback) - preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec).set(hardwareDecoding) - preferenceStore.getEnum("pref_video_debanding", VideoDebanding.DISABLED).set(videoDebanding) - preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT).set(aspectState) - preferenceStore.getBoolean("pref_gpu_next", false).set(gpuNext.get()) - } - } - - val coroutineScope = CoroutineScope(Dispatchers.IO) - - if (oldVersion < 125) { - coroutineScope.launchIO { - for ((index, source) in sourcePreferences.animeExtensionRepos().get().withIndex()) { - try { - animeExtensionRepoRepository.upsertRepo( - source, - "Repo #${index + 1}", - null, - source, - "NOFINGERPRINT-${index + 1}", - ) - } catch (e: SaveExtensionRepoException) { - logcat(LogPriority.ERROR, e) { - "Error Migrating Anime Extension Repo with baseUrl: $source" - } - } - } - sourcePreferences.animeExtensionRepos().delete() - - for ((index, source) in sourcePreferences.mangaExtensionRepos().get().withIndex()) { - try { - mangaExtensionRepoRepository.upsertRepo( - source, - "Repo #${index + 1}", - null, - source, - "NOFINGERPRINT-${index + 1}", - ) - } catch (e: SaveExtensionRepoException) { - logcat(LogPriority.ERROR, e) { - "Error Migrating Manga Extension Repo with baseUrl: $source" - } - } - } - sourcePreferences.mangaExtensionRepos().delete() - } - } - return true - } - return false - } -} - -@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/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 84f8b0220..8d8ba1bf9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -55,8 +55,6 @@ import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior import cafe.adriel.voyager.navigator.currentOrThrow import com.google.accompanist.systemuicontroller.rememberSystemUiController 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 @@ -67,7 +65,6 @@ import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen import eu.kanade.presentation.util.AssistContentScreen import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.core.common.Constants import eu.kanade.tachiyomi.data.cache.ChapterCache @@ -107,7 +104,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.i18n.stringResource +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.logcat @@ -123,9 +124,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() private val animeDownloadCache: AnimeDownloadCache by injectLazy() @@ -149,26 +148,7 @@ class MainActivity : BaseActivity() { super.onCreate(savedInstanceState) - val didMigration = if (isLaunch) { - Migrations.upgrade( - context = applicationContext, - basePreferences = preferences, - uiPreferences = uiPreferences, - preferenceStore = Injekt.get(), - networkPreferences = Injekt.get(), - sourcePreferences = sourcePreferences, - securityPreferences = Injekt.get(), - libraryPreferences = libraryPreferences, - readerPreferences = Injekt.get(), - playerPreferences = Injekt.get(), - backupPreferences = Injekt.get(), - trackerManager = Injekt.get(), - animeExtensionRepoRepository = Injekt.get(), - mangaExtensionRepoRepository = Injekt.get(), - ) - } else { - false - } + val didMigration = migrate() // Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079 if (!isTaskRoot) { @@ -403,6 +383,21 @@ class MainActivity : BaseActivity() { } } + private fun migrate(): Boolean { + val preferenceStore = Injekt.get() + val preference = preferenceStore.getInt(Preference.appStateKey("last_version_code"), 0) + 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/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/MigrationUtils.kt b/app/src/main/java/mihon/core/migration/MigrationUtils.kt new file mode 100644 index 000000000..85007937b --- /dev/null +++ b/app/src/main/java/mihon/core/migration/MigrationUtils.kt @@ -0,0 +1,41 @@ +package mihon.core.migration + +import tachiyomi.core.common.preference.PreferenceStore + +@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/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/AddAllLangMigration.kt b/app/src/main/java/mihon/core/migration/migrations/AddAllLangMigration.kt new file mode 100644 index 000000000..bd2ffe3de --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/AddAllLangMigration.kt @@ -0,0 +1,21 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.source.service.SourcePreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.plusAssign + +class AddAllLangMigration : Migration { + override val version = 70f + + // Migration to add "all" to enabled langauges + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val sourcePreferences = migrationContext.get() ?: return false + + if (sourcePreferences.enabledLanguages().isSet()) { + sourcePreferences.enabledLanguages() += "all" + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/CombineUpdateRestrictionMigration.kt b/app/src/main/java/mihon/core/migration/migrations/CombineUpdateRestrictionMigration.kt new file mode 100644 index 000000000..58ac0d06e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/CombineUpdateRestrictionMigration.kt @@ -0,0 +1,30 @@ +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.domain.library.service.LibraryPreferences +import tachiyomi.domain.library.service.LibraryPreferences.Companion.ENTRY_NON_COMPLETED + +class CombineUpdateRestrictionMigration : Migration { + override val version = 72f + + // Combine global update item restrictions + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val oldUpdateOngoingOnly = prefs.getBoolean( + "pref_update_only_non_completed_key", + true, + ) + if (!oldUpdateOngoingOnly) { + libraryPreferences.autoUpdateItemRestrictions() -= ENTRY_NON_COMPLETED + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/CoverToExternalFileMigration.kt b/app/src/main/java/mihon/core/migration/migrations/CoverToExternalFileMigration.kt new file mode 100644 index 000000000..5113c911f --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/CoverToExternalFileMigration.kt @@ -0,0 +1,27 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import java.io.File + +class CoverToExternalFileMigration : Migration { + override val version = 19f + + // Move covers to external files dir. + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + val oldDir = File(context.externalCacheDir, "cover_disk_cache") + if (oldDir.exists()) { + val destDir = context.getExternalFilesDir("covers") + if (destDir != null) { + oldDir.listFiles()?.forEach { + it.renameTo(File(destDir, it.name)) + } + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DOHMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DOHMigration.kt new file mode 100644 index 000000000..4bc603f80 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DOHMigration.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 + +class DOHMigration : Migration { + override val version = 57f + + // Migrate DNS over HTTPS setting + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val networkPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val wasDohEnabled = prefs.getBoolean("enable_doh", false) + if (wasDohEnabled) { + prefs.edit { + putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE) + remove("enable_doh") + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DeleteExternalChapterCacheDirMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DeleteExternalChapterCacheDirMigration.kt new file mode 100644 index 000000000..bf4ab7cb4 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DeleteExternalChapterCacheDirMigration.kt @@ -0,0 +1,25 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import java.io.File + +class DeleteExternalChapterCacheDirMigration : Migration { + override val version = 26f + + // Delete external chapter cache dir. + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + val extCache = context.externalCacheDir + if (extCache != null) { + val chapterCache = File(extCache, "chapter_disk_cache") + if (chapterCache.exists()) { + chapterCache.deleteRecursively() + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/DontRunJobsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/DontRunJobsMigration.kt new file mode 100644 index 000000000..f75e54d55 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/DontRunJobsMigration.kt @@ -0,0 +1,22 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.domain.library.service.LibraryPreferences + +class DontRunJobsMigration : Migration { + override val version = 105f + + // Don't run automatic backup or library update jobs if battery is low + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val libraryPreferences = migrationContext.get() ?: return false + + val pref = libraryPreferences.autoUpdateDeviceRestrictions() + if (pref.isSet() && "battery_not_low" in pref.get()) { + pref.getAndSet { it - "battery_not_low" } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/EnableAutoBackupMigration.kt b/app/src/main/java/mihon/core/migration/migrations/EnableAutoBackupMigration.kt new file mode 100644 index 000000000..3249a2989 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/EnableAutoBackupMigration.kt @@ -0,0 +1,24 @@ +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 +import tachiyomi.domain.backup.service.BackupPreferences + +class EnableAutoBackupMigration : Migration { + override val version = 84f + + // Always attempt automatic backup creation + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val backupPreferences = migrationContext.get() ?: return false + + if (backupPreferences.backupInterval().get() == 0) { + backupPreferences.backupInterval().set(12) + BackupCreateJob.setupTask(context) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/EnumsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/EnumsMigration.kt new file mode 100644 index 000000000..dde979b2e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/EnumsMigration.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 eu.kanade.tachiyomi.ui.player.viewer.AspectState +import eu.kanade.tachiyomi.ui.player.viewer.HwDecState +import eu.kanade.tachiyomi.ui.player.viewer.InvertedPlayback +import eu.kanade.tachiyomi.ui.player.viewer.VideoDebanding +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.getEnum + +class EnumsMigration : Migration { + override val version = 123f + + // refactor(player): Implement more enums + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val preferenceStore = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val invertedPosition = preferenceStore.getBoolean("pref_invert_playback_txt", false) + val invertedDuration = preferenceStore.getBoolean("pref_invert_duration_txt", false) + val hwDec = preferenceStore.getString("pref_hwdec", HwDecState.defaultHwDec.mpvValue) + val deband = preferenceStore.getInt("pref_deband", 0) + val playerViewMode = preferenceStore.getInt("pref_player_view_mode", 1) + val gpuNext = preferenceStore.getBoolean("gpu_next", false) + + prefs.edit { + remove("pref_invert_playback_txt") + remove("pref_invert_duration_txt") + remove("pref_hwdec") + remove("pref_deband") + remove("pref_player_view_mode") + remove("gpu_next") + + val invertedPlayback = when { + invertedPosition.get() -> InvertedPlayback.POSITION + invertedDuration.get() -> InvertedPlayback.DURATION + else -> InvertedPlayback.NONE + } + val hardwareDecoding = HwDecState.entries.first { it.mpvValue == hwDec.get() } + val videoDebanding = VideoDebanding.entries.first { it.ordinal == deband.get() } + val aspectState = AspectState.entries.first { it.ordinal == playerViewMode.get() } + + preferenceStore.getEnum("pref_inverted_playback", InvertedPlayback.NONE).set(invertedPlayback) + preferenceStore.getEnum("pref_hardware_decoding", HwDecState.defaultHwDec).set(hardwareDecoding) + preferenceStore.getEnum("pref_video_debanding", VideoDebanding.DISABLED).set(videoDebanding) + preferenceStore.getEnum("pref_player_aspect_state", AspectState.FIT).set(aspectState) + preferenceStore.getBoolean("pref_gpu_next", false).set(gpuNext.get()) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ExternalRepoMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ExternalRepoMigration.kt new file mode 100644 index 000000000..8dcbaa125 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ExternalRepoMigration.kt @@ -0,0 +1,21 @@ +package mihon.core.migration.migrations + +import eu.kanade.domain.source.service.SourcePreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.getAndSet + +class ExternalRepoMigration : Migration { + override val version = 114f + + // Clean up external repos + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val sourcePreferences = migrationContext.get() ?: return false + + sourcePreferences.mangaExtensionRepos().getAndSet { + it.map { repo -> "https://raw.githubusercontent.com/$repo/repo" }.toSet() + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ForceMALLogOutMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ForceMALLogOutMigration.kt new file mode 100644 index 000000000..5b6171b67 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ForceMALLogOutMigration.kt @@ -0,0 +1,22 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.data.track.TrackerManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class ForceMALLogOutMigration : Migration { + override val version = 54f + + // Force MAL log out due to login flow change + // v52: switched from scraping to WebView + // v53: switched from WebView to OAuth + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val trackerManager = migrationContext.get() ?: return false + + if (trackerManager.myAnimeList.isLoggedIn) { + trackerManager.myAnimeList.logout() + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/InternalChapterCacheDirMigration.kt b/app/src/main/java/mihon/core/migration/migrations/InternalChapterCacheDirMigration.kt new file mode 100644 index 000000000..6fbf39d0e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/InternalChapterCacheDirMigration.kt @@ -0,0 +1,19 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import java.io.File + +class InternalChapterCacheDirMigration : Migration { + override val version = 15f + + // Delete internal chapter cache dir. + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + File(context.cacheDir, "chapter_disk_cache").deleteRecursively() + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/LogOutMALMigration.kt b/app/src/main/java/mihon/core/migration/migrations/LogOutMALMigration.kt new file mode 100644 index 000000000..8c7f7bd46 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/LogOutMALMigration.kt @@ -0,0 +1,19 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.data.track.TrackerManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class LogOutMALMigration : Migration { + override val version = 121f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val trackerManager = migrationContext.get() ?: return false + + if (trackerManager.myAnimeList.isLoggedIn) { + trackerManager.myAnimeList.logout() + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MergeSortTypeDirectionMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MergeSortTypeDirectionMigration.kt new file mode 100644 index 000000000..a0d015e8e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MergeSortTypeDirectionMigration.kt @@ -0,0 +1,36 @@ +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.domain.library.service.LibraryPreferences + +class MergeSortTypeDirectionMigration : Migration { + override val version = 82f + + // Merge Sort Type and Direction into one class + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + prefs.edit { + val mangasort = prefs.getString( + libraryPreferences.mangaSortingMode().key(), + null, + ) ?: return@edit + val animesort = prefs.getString( + libraryPreferences.animeSortingMode().key(), + null, + ) ?: return@edit + val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!! + putString(libraryPreferences.mangaSortingMode().key(), "$mangasort,$direction") + putString(libraryPreferences.animeSortingMode().key(), "$animesort,$direction") + remove("library_sorting_ascending") + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MigrateRotationViewerValuesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MigrateRotationViewerValuesMigration.kt new file mode 100644 index 000000000..877624cae --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MigrateRotationViewerValuesMigration.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 + +class MigrateRotationViewerValuesMigration : Migration { + override val version = 60f + + // Migrate Rotation and Viewer values to default values for viewer_flags + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + 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 true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MigrateSecureScreenMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MigrateSecureScreenMigration.kt new file mode 100644 index 000000000..43985a29e --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MigrateSecureScreenMigration.kt @@ -0,0 +1,37 @@ +package mihon.core.migration.migrations + +import androidx.preference.PreferenceManager +import eu.kanade.domain.base.BasePreferences +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.core.security.SecurityPreferences +import eu.kanade.tachiyomi.util.system.DeviceUtil +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class MigrateSecureScreenMigration : Migration { + override val version = 75f + + // Allow disabling secure screen when incognito mode is on + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val securityPreferences = migrationContext.get() ?: return false + val basePreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + 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, + ) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MigrateSortingModeMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MigrateSortingModeMigration.kt new file mode 100644 index 000000000..41dcf2468 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MigrateSortingModeMigration.kt @@ -0,0 +1,72 @@ +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.domain.library.service.LibraryPreferences + +class MigrateSortingModeMigration : Migration { + override val version = 64f + + // Switch to sort per category + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val oldMangaSortingMode = prefs.getInt( + libraryPreferences.mangaSortingMode().key(), + 0, + ) + val oldAnimeSortingMode = prefs.getInt( + libraryPreferences.animeSortingMode().key(), + 0, + ) + val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true) + + val newMangaSortingMode = when (oldMangaSortingMode) { + 0 -> "ALPHABETICAL" + 1 -> "LAST_READ" + 2 -> "LAST_CHECKED" + 3 -> "UNREAD" + 4 -> "TOTAL_CHAPTERS" + 6 -> "LATEST_CHAPTER" + 8 -> "DATE_FETCHED" + 7 -> "DATE_ADDED" + else -> "ALPHABETICAL" + } + + val newAnimeSortingMode = when (oldAnimeSortingMode) { + 0 -> "ALPHABETICAL" + 1 -> "LAST_SEEN" + 2 -> "LAST_CHECKED" + 3 -> "UNSEEN" + 4 -> "TOTAL_EPISODES" + 6 -> "LATEST_EPISODE" + 8 -> "DATE_FETCHED" + 7 -> "DATE_ADDED" + else -> "ALPHABETICAL" + } + + val newSortingDirection = when (oldSortingDirection) { + true -> "ASCENDING" + else -> "DESCENDING" + } + + prefs.edit(commit = true) { + remove(libraryPreferences.mangaSortingMode().key()) + remove(libraryPreferences.animeSortingMode().key()) + remove("library_sorting_ascending") + } + + prefs.edit { + putString(libraryPreferences.mangaSortingMode().key(), newMangaSortingMode) + putString(libraryPreferences.animeSortingMode().key(), newAnimeSortingMode) + putString("library_sorting_ascending", newSortingDirection) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MigrateToTriStateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MigrateToTriStateMigration.kt new file mode 100644 index 000000000..b3f747879 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MigrateToTriStateMigration.kt @@ -0,0 +1,51 @@ +package mihon.core.migration.migrations + +import android.content.SharedPreferences +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.domain.library.service.LibraryPreferences + +class MigrateToTriStateMigration : Migration { + override val version = 52f + + // Migrate library filters to tri-state versions + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + prefs.edit { + putInt( + libraryPreferences.filterDownloadedManga().key(), + convertBooleanPrefToTriState(prefs, "pref_filter_downloaded_key"), + ) + remove("pref_filter_downloaded_key") + + putInt( + libraryPreferences.filterUnread().key(), + convertBooleanPrefToTriState(prefs, "pref_filter_unread_key"), + ) + remove("pref_filter_unread_key") + + putInt( + libraryPreferences.filterCompletedManga().key(), + convertBooleanPrefToTriState(prefs, "pref_filter_completed_key"), + ) + remove("pref_filter_completed_key") + } + + return true + } + + private fun convertBooleanPrefToTriState(prefs: SharedPreferences, key: String): Int { + val oldPrefValue = prefs.getBoolean(key, false) + return if (oldPrefValue) { + 1 + } else { + 0 + } + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MigrateTriStateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MigrateTriStateMigration.kt new file mode 100644 index 000000000..60cd238d5 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MigrateTriStateMigration.kt @@ -0,0 +1,51 @@ +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 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 + +class MigrateTriStateMigration : Migration { + override val version = 99f + + // Migrate TriState usages to TriStateFilter enum + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val trackerManager = migrationContext.get() ?: return false + val preferenceStore = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val prefKeys = listOf( + "pref_filter_library_downloaded", + "pref_filter_library_unread", + "pref_filter_library_unseen", + "pref_filter_library_started", + "pref_filter_library_bookmarked", + "pref_filter_library_completed", + ) + 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, + ) + } + } + + return true + } +} 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..00b852344 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/Migrations.kt @@ -0,0 +1,47 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration + +val migrations: List + get() = listOf( + SetupBackupCreateMigration(), + SetupAnimeLibraryUpdateMigration(), + SetupMangaLibraryUpdateMigration(), + InternalChapterCacheDirMigration(), + CoverToExternalFileMigration(), + DeleteExternalChapterCacheDirMigration(), + ResetSortPreferenceRemovedMigration(), + MigrateToTriStateMigration(), + ForceMALLogOutMigration(), + DOHMigration(), + ResetRotationMigration(), + MigrateRotationViewerValuesMigration(), + RemoveOneTwoHourUpdateMigration(), + SetupBackgroundTasksMigration(), + MigrateSortingModeMigration(), + AddAllLangMigration(), + RemoveQuickUpdateMigration(), + CombineUpdateRestrictionMigration(), + MigrateSecureScreenMigration(), + RemoveReaderTapMigration(), + RenameEnumMigration(), + MergeSortTypeDirectionMigration(), + EnableAutoBackupMigration(), + MoveChapterPreferencesMigration(), + SplitPreferencesMigration(), + PlayerPreferenceMigration(), + MovePlayerPreferencesMigration(), + UseWorkManagerMigration(), + RemoveBackgroundJobsMigration(), + MigrateTriStateMigration(), + DontRunJobsMigration(), + RelativeTimestampMigration(), + NoAppStateMigration(), + ExternalRepoMigration(), + PrivatePreferenceMigration(), + PermaTrustExtensionsMigration(), + NavigationOptionsMigration(), + LogOutMALMigration(), + EnumsMigration(), + TrustExtensionRepositoryMigration(), + ) diff --git a/app/src/main/java/mihon/core/migration/migrations/MoveChapterPreferencesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MoveChapterPreferencesMigration.kt new file mode 100644 index 000000000..088b15b33 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MoveChapterPreferencesMigration.kt @@ -0,0 +1,46 @@ +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.domain.library.service.LibraryPreferences + +class MoveChapterPreferencesMigration : Migration { + override val version = 85f + + // Move chapter preferences from PreferencesHelper to LibraryPrefrences + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val preferences = listOf( + libraryPreferences.filterChapterByRead(), + libraryPreferences.filterChapterByDownloaded(), + libraryPreferences.filterChapterByBookmarked(), + libraryPreferences.sortChapterBySourceOrNumber(), + libraryPreferences.displayChapterByNameOrNumber(), + libraryPreferences.sortChapterByAscendingOrDescending(), + libraryPreferences.filterEpisodeBySeen(), + libraryPreferences.filterEpisodeByDownloaded(), + libraryPreferences.filterEpisodeByBookmarked(), + libraryPreferences.sortEpisodeBySourceOrNumber(), + libraryPreferences.displayEpisodeByNameOrNumber(), + libraryPreferences.sortEpisodeByAscendingOrDescending(), + ) + + 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 true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/MovePlayerPreferencesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/MovePlayerPreferencesMigration.kt new file mode 100644 index 000000000..ec6cccf65 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/MovePlayerPreferencesMigration.kt @@ -0,0 +1,50 @@ +package mihon.core.migration.migrations + +import android.content.Context +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class MovePlayerPreferencesMigration : Migration { + override val version = 93f + + // more migrations for player prefs + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val playerPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + listOf( + playerPreferences.defaultPlayerOrientationType(), + playerPreferences.defaultPlayerOrientationLandscape(), + playerPreferences.defaultPlayerOrientationPortrait(), + playerPreferences.skipLengthPreference(), + ).forEach { pref -> + if (pref.isSet()) { + prefs.edit { + val oldString = try { + prefs.getString(pref.key(), null) + } catch (e: ClassCastException) { + null + } ?: return@edit + val newInt = oldString.toIntOrNull() ?: return@edit + putInt(pref.key(), newInt) + } + 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 true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/NavigationOptionsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/NavigationOptionsMigration.kt new file mode 100644 index 000000000..bfaef758b --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/NavigationOptionsMigration.kt @@ -0,0 +1,42 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.domain.ui.model.NavStyle +import eu.kanade.domain.ui.model.StartScreen +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.getEnum + +class NavigationOptionsMigration : Migration { + override val version = 120f + + // Bring back navigation options + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val preferenceStore = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val bottomNavStyle = preferenceStore.getInt("bottom_nav_style", 0) + + val isDefaultTabManga = preferenceStore.getBoolean("default_home_tab_library", false) + prefs.edit { + remove("bottom_nav_style") + remove("default_home_tab_library") + + val startScreen = if (isDefaultTabManga.get()) StartScreen.MANGA else StartScreen.ANIME + val navStyle = when (bottomNavStyle.get()) { + 0 -> NavStyle.MOVE_HISTORY_TO_MORE + 1 -> NavStyle.MOVE_UPDATES_TO_MORE + else -> NavStyle.MOVE_MANGA_TO_MORE + } + + preferenceStore.getEnum("start_screen", StartScreen.ANIME).set(startScreen) + preferenceStore.getEnum("bottom_rail_nav_style", NavStyle.MOVE_HISTORY_TO_MORE).set(navStyle) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/NoAppStateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/NoAppStateMigration.kt new file mode 100644 index 000000000..191bbd84d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/NoAppStateMigration.kt @@ -0,0 +1,43 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import mihon.core.migration.replacePreferences +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore + +class NoAppStateMigration : Migration { + override val version = 113f + + // Don't include "app state" preferences in backups + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val preferenceStore = migrationContext.get() ?: return 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", + "storage_dir", + ) + replacePreferences( + preferenceStore = preferenceStore, + filterPredicate = { it.key in prefsToReplace }, + newKey = { Preference.appStateKey(it) }, + ) + + // Deleting old download cache index files, but might as well clear it all out + context.cacheDir.deleteRecursively() + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/PermaTrustExtensionsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/PermaTrustExtensionsMigration.kt new file mode 100644 index 000000000..7e4adef6d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/PermaTrustExtensionsMigration.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.preference.Preference + +class PermaTrustExtensionsMigration : Migration { + override val version = 117f + + // Allow permanently trusting unofficial extensions by version code + signature + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + prefs.edit { + remove(Preference.appStateKey("trusted_signatures")) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/PlayerPreferenceMigration.kt b/app/src/main/java/mihon/core/migration/migrations/PlayerPreferenceMigration.kt new file mode 100644 index 000000000..cc2995f37 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/PlayerPreferenceMigration.kt @@ -0,0 +1,33 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class PlayerPreferenceMigration : Migration { + override val version = 92f + + // add migration for player preference + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val playerPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + if (playerPreferences.progressPreference().isSet()) { + prefs.edit { + val progressString = try { + prefs.getString(playerPreferences.progressPreference().key(), null) + } catch (e: ClassCastException) { + null + } ?: return@edit + val newProgress = progressString.toFloatOrNull() ?: return@edit + putFloat(playerPreferences.progressPreference().key(), newProgress) + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/PrivatePreferenceMigration.kt b/app/src/main/java/mihon/core/migration/migrations/PrivatePreferenceMigration.kt new file mode 100644 index 000000000..64cea92e7 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/PrivatePreferenceMigration.kt @@ -0,0 +1,23 @@ +package mihon.core.migration.migrations + +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import mihon.core.migration.replacePreferences +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.PreferenceStore + +class PrivatePreferenceMigration : Migration { + override val version = 116f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val preferenceStore = migrationContext.get() ?: return false + + replacePreferences( + preferenceStore = preferenceStore, + filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") }, + newKey = { Preference.privateKey(it) }, + ) + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RelativeTimestampMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RelativeTimestampMigration.kt new file mode 100644 index 000000000..9b8a0a558 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RelativeTimestampMigration.kt @@ -0,0 +1,23 @@ +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 + +class RelativeTimestampMigration : Migration { + override val version = 106f + + // Bring back simplified relative timestamp setting + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val preferenceStore = migrationContext.get() ?: return false + val uiPreferences = migrationContext.get() ?: return false + + val pref = preferenceStore.getInt("relative_time", 7) + if (pref.get() == 0) { + uiPreferences.relativeTime().set(false) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveBackgroundJobsMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveBackgroundJobsMigration.kt new file mode 100644 index 000000000..d990cec00 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveBackgroundJobsMigration.kt @@ -0,0 +1,26 @@ +package mihon.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.util.system.workManager +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class RemoveBackgroundJobsMigration : Migration { + override val version = 97f + + // Removed background jobs + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + context.workManager.cancelAllWorkByTag("UpdateChecker") + context.workManager.cancelAllWorkByTag("ExtensionUpdate") + prefs.edit { + remove("automatic_ext_updates") + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveOneTwoHourUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveOneTwoHourUpdateMigration.kt new file mode 100644 index 000000000..0787920f5 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveOneTwoHourUpdateMigration.kt @@ -0,0 +1,27 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob +import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.domain.library.service.LibraryPreferences + +class RemoveOneTwoHourUpdateMigration : Migration { + override val version = 61f + + // Handle removed every 1 or 2 hour library updates + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + + val updateInterval = libraryPreferences.autoUpdateInterval().get() + if (updateInterval == 1 || updateInterval == 2) { + libraryPreferences.autoUpdateInterval().set(3) + MangaLibraryUpdateJob.setupTask(context, 3) + AnimeLibraryUpdateJob.setupTask(context, 3) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveQuickUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveQuickUpdateMigration.kt new file mode 100644 index 000000000..06f1b10dd --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveQuickUpdateMigration.kt @@ -0,0 +1,27 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob +import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext +import tachiyomi.domain.library.service.LibraryPreferences + +class RemoveQuickUpdateMigration : Migration { + override val version = 71f + + // Handle removed every 3, 4, 6, and 8 hour library updates + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + + val updateInterval = libraryPreferences.autoUpdateInterval().get() + if (updateInterval in listOf(3, 4, 6, 8)) { + libraryPreferences.autoUpdateInterval().set(12) + MangaLibraryUpdateJob.setupTask(context, 12) + AnimeLibraryUpdateJob.setupTask(context, 12) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RemoveReaderTapMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RemoveReaderTapMigration.kt new file mode 100644 index 000000000..0a1152928 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RemoveReaderTapMigration.kt @@ -0,0 +1,26 @@ +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 + +class RemoveReaderTapMigration : Migration { + override val version = 77f + + // Remove reader tapping option in favor of disabled nav layouts + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val readerPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val oldReaderTap = prefs.getBoolean("reader_tap", false) + if (!oldReaderTap) { + readerPreferences.navigationModePager().set(5) + readerPreferences.navigationModeWebtoon().set(5) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/RenameEnumMigration.kt b/app/src/main/java/mihon/core/migration/migrations/RenameEnumMigration.kt new file mode 100644 index 000000000..20fd95933 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/RenameEnumMigration.kt @@ -0,0 +1,48 @@ +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.domain.library.service.LibraryPreferences + +class RenameEnumMigration : Migration { + override val version = 81f + + // Handle renamed enum values + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + prefs.edit { + val newMangaSortingMode = when ( + val oldSortingMode = prefs.getString( + libraryPreferences.mangaSortingMode().key(), + "ALPHABETICAL", + ) + ) { + "LAST_CHECKED" -> "LAST_MANGA_UPDATE" + "UNREAD" -> "UNREAD_COUNT" + "DATE_FETCHED" -> "CHAPTER_FETCH_DATE" + else -> oldSortingMode + } + val newAnimeSortingMode = when ( + val oldSortingMode = prefs.getString( + libraryPreferences.animeSortingMode().key(), + "ALPHABETICAL", + ) + ) { + "LAST_CHECKED" -> "LAST_MANGA_UPDATE" + "UNREAD" -> "UNREAD_COUNT" + "DATE_FETCHED" -> "CHAPTER_FETCH_DATE" + else -> oldSortingMode + } + putString(libraryPreferences.mangaSortingMode().key(), newMangaSortingMode) + putString(libraryPreferences.animeSortingMode().key(), newAnimeSortingMode) + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ResetRotationMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ResetRotationMigration.kt new file mode 100644 index 000000000..6aaa8be61 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ResetRotationMigration.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 + +class ResetRotationMigration : Migration { + override val version = 59f + + // Reset rotation to Free after replacing Lock + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + if (prefs.contains("pref_rotation_type_key")) { + prefs.edit { + putInt("pref_rotation_type_key", 1) + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/ResetSortPreferenceRemovedMigration.kt b/app/src/main/java/mihon/core/migration/migrations/ResetSortPreferenceRemovedMigration.kt new file mode 100644 index 000000000..9036c075d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/ResetSortPreferenceRemovedMigration.kt @@ -0,0 +1,43 @@ +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.domain.library.service.LibraryPreferences + +class ResetSortPreferenceRemovedMigration : Migration { + override val version = 44f + + // Reset sorting preference if using removed sort by source + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val libraryPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val oldMangaSortingMode = prefs.getInt( + libraryPreferences.mangaSortingMode().key(), + 0, + ) + + if (oldMangaSortingMode == 5) { // SOURCE = 5 + prefs.edit { + putInt(libraryPreferences.mangaSortingMode().key(), 0) // ALPHABETICAL = 0 + } + } + + val oldAnimeSortingMode = prefs.getInt( + libraryPreferences.animeSortingMode().key(), + 0, + ) + + if (oldAnimeSortingMode == 5) { // SOURCE = 5 + prefs.edit { + putInt(libraryPreferences.animeSortingMode().key(), 0) // ALPHABETICAL = 0 + } + } + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupAnimeLibraryUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupAnimeLibraryUpdateMigration.kt new file mode 100644 index 000000000..73e5d4380 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupAnimeLibraryUpdateMigration.kt @@ -0,0 +1,18 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupAnimeLibraryUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + AnimeLibraryUpdateJob.setupTask(context) + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SetupBackgroundTasksMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupBackgroundTasksMigration.kt new file mode 100644 index 000000000..b81336958 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupBackgroundTasksMigration.kt @@ -0,0 +1,21 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob +import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupBackgroundTasksMigration : Migration { + override val version = 64f + + // Set up background tasks + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + MangaLibraryUpdateJob.setupTask(context) + AnimeLibraryUpdateJob.setupTask(context) + + return 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..8b637a326 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupBackupCreateMigration.kt @@ -0,0 +1,18 @@ +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/SetupMangaLibraryUpdateMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SetupMangaLibraryUpdateMigration.kt new file mode 100644 index 000000000..6df1c138c --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SetupMangaLibraryUpdateMigration.kt @@ -0,0 +1,18 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class SetupMangaLibraryUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + MangaLibraryUpdateJob.setupTask(context) + + return true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/SplitPreferencesMigration.kt b/app/src/main/java/mihon/core/migration/migrations/SplitPreferencesMigration.kt new file mode 100644 index 000000000..b416e9c3d --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/SplitPreferencesMigration.kt @@ -0,0 +1,28 @@ +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 + +class SplitPreferencesMigration : Migration { + override val version = 86f + + // Split the rest of the preferences in PreferencesHelper + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + val uiPreferences = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + if (uiPreferences.themeMode().isSet()) { + prefs.edit { + val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit + putString(uiPreferences.themeMode().key(), themeMode.uppercase()) + } + } + + 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..519f22ff8 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/TrustExtensionRepositoryMigration.kt @@ -0,0 +1,57 @@ +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.anime.repository.AnimeExtensionRepoRepository +import mihon.domain.extensionrepo.manga.repository.MangaExtensionRepoRepository +import tachiyomi.core.common.util.lang.withIOContext +import tachiyomi.core.common.util.system.logcat + +class TrustExtensionRepositoryMigration : Migration { + override val version: Float = 7f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val sourcePreferences = migrationContext.get() ?: return@withIOContext false + + val animeExtensionRepositoryRepository = + migrationContext.get() ?: return@withIOContext false + for ((index, source) in sourcePreferences.animeExtensionRepos().get().withIndex()) { + try { + animeExtensionRepositoryRepository.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.animeExtensionRepos().delete() + + val mangaExtensionRepositoryRepository = + migrationContext.get() ?: return@withIOContext false + for ((index, source) in sourcePreferences.mangaExtensionRepos().get().withIndex()) { + try { + mangaExtensionRepositoryRepository.upsertRepo( + source, + "Repo #${index + 1}", + null, + source, + "NOFINGERPRINT-${index + 1}", + ) + } catch (e: SaveExtensionRepoException) { + logcat(LogPriority.ERROR, e) { + "Error Migrating Manga Extension Repo with baseUrl: $source" + } + } + } + sourcePreferences.mangaExtensionRepos().delete() + + return@withIOContext true + } +} diff --git a/app/src/main/java/mihon/core/migration/migrations/UseWorkManagerMigration.kt b/app/src/main/java/mihon/core/migration/migrations/UseWorkManagerMigration.kt new file mode 100644 index 000000000..6033a7b65 --- /dev/null +++ b/app/src/main/java/mihon/core/migration/migrations/UseWorkManagerMigration.kt @@ -0,0 +1,23 @@ +package mihon.core.migration.migrations + +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.anime.AnimeLibraryUpdateJob +import eu.kanade.tachiyomi.data.library.manga.MangaLibraryUpdateJob +import mihon.core.migration.Migration +import mihon.core.migration.MigrationContext + +class UseWorkManagerMigration : Migration { + override val version = 96f + + // Fully utilize WorkManager for library updates + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context = migrationContext.get() ?: return false + + MangaLibraryUpdateJob.cancelAllWorks(context) + AnimeLibraryUpdateJob.cancelAllWorks(context) + MangaLibraryUpdateJob.setupTask(context) + AnimeLibraryUpdateJob.setupTask(context) + + return 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) + } +}