Rewrite Migrations (#577)

* Rewrite Migrations

* Fix Detekt errors

* Do migrations synchronous

* Filter and sort migrations

* Review changes

* Review changes 2

* Fix Detekt errors

Co-authored-by: Andreas <6576096+ghostbear@users.noreply.github.com>
This commit is contained in:
Secozzi 2024-07-03 22:23:57 +02:00
parent 92f16c913e
commit 486db1fd53
No known key found for this signature in database
GPG key ID: 71E9C97D8DDC2662
48 changed files with 1578 additions and 721 deletions

View file

@ -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<String, Any?>) -> Boolean,
newKey: (String) -> String,
) {
preferenceStore.getAll()
.filter(filterPredicate)
.forEach { (key, value) ->
when (value) {
is Int -> {
preferenceStore.getInt(newKey(key)).set(value)
preferenceStore.getInt(key).delete()
}
is Long -> {
preferenceStore.getLong(newKey(key)).set(value)
preferenceStore.getLong(key).delete()
}
is Float -> {
preferenceStore.getFloat(newKey(key)).set(value)
preferenceStore.getFloat(key).delete()
}
is String -> {
preferenceStore.getString(newKey(key)).set(value)
preferenceStore.getString(key).delete()
}
is Boolean -> {
preferenceStore.getBoolean(newKey(key)).set(value)
preferenceStore.getBoolean(key).delete()
}
is Set<*> -> (value as? Set<String>)?.let {
preferenceStore.getStringSet(newKey(key)).set(value)
preferenceStore.getStringSet(key).delete()
}
}
}
}

View file

@ -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<PreferenceStore>()
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.
*

View file

@ -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)
}
}
}
}

View file

@ -0,0 +1,10 @@
package mihon.core.migration
import uy.kohesive.injekt.Injekt
class MigrationContext {
inline fun <reified T> get(): T? {
return Injekt.getInstanceOrNull(T::class.java)
}
}

View file

@ -0,0 +1,41 @@
package mihon.core.migration
import tachiyomi.core.common.preference.PreferenceStore
@Suppress("UNCHECKED_CAST")
fun replacePreferences(
preferenceStore: PreferenceStore,
filterPredicate: (Map.Entry<String, Any?>) -> Boolean,
newKey: (String) -> String,
) {
preferenceStore.getAll()
.filter(filterPredicate)
.forEach { (key, value) ->
when (value) {
is Int -> {
preferenceStore.getInt(newKey(key)).set(value)
preferenceStore.getInt(key).delete()
}
is Long -> {
preferenceStore.getLong(newKey(key)).set(value)
preferenceStore.getLong(key).delete()
}
is Float -> {
preferenceStore.getFloat(newKey(key)).set(value)
preferenceStore.getFloat(key).delete()
}
is String -> {
preferenceStore.getString(newKey(key)).set(value)
preferenceStore.getString(key).delete()
}
is Boolean -> {
preferenceStore.getBoolean(newKey(key)).set(value)
preferenceStore.getBoolean(key).delete()
}
is Set<*> -> (value as? Set<String>)?.let {
preferenceStore.getStringSet(newKey(key)).set(value)
preferenceStore.getStringSet(key).delete()
}
}
}
}

View file

@ -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<Migration>,
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<Migration>, 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 }
}
}

View file

@ -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<SourcePreferences>() ?: return false
if (sourcePreferences.enabledLanguages().isSet()) {
sourcePreferences.enabledLanguages() += "all"
}
return true
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val networkPreferences = migrationContext.get<NetworkPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val extCache = context.externalCacheDir
if (extCache != null) {
val chapterCache = File(extCache, "chapter_disk_cache")
if (chapterCache.exists()) {
chapterCache.deleteRecursively()
}
}
return true
}
}

View file

@ -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<LibraryPreferences>() ?: return false
val pref = libraryPreferences.autoUpdateDeviceRestrictions()
if (pref.isSet() && "battery_not_low" in pref.get()) {
pref.getAndSet { it - "battery_not_low" }
}
return true
}
}

View file

@ -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<App>() ?: return false
val backupPreferences = migrationContext.get<BackupPreferences>() ?: return false
if (backupPreferences.backupInterval().get() == 0) {
backupPreferences.backupInterval().set(12)
BackupCreateJob.setupTask(context)
}
return true
}
}

View file

@ -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<App>() ?: return false
val preferenceStore = migrationContext.get<PreferenceStore>() ?: 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
}
}

View file

@ -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<SourcePreferences>() ?: return false
sourcePreferences.mangaExtensionRepos().getAndSet {
it.map { repo -> "https://raw.githubusercontent.com/$repo/repo" }.toSet()
}
return true
}
}

View file

@ -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<TrackerManager>() ?: return false
if (trackerManager.myAnimeList.isLoggedIn) {
trackerManager.myAnimeList.logout()
}
return true
}
}

View file

@ -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<App>() ?: return false
File(context.cacheDir, "chapter_disk_cache").deleteRecursively()
return true
}
}

View file

@ -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<TrackerManager>() ?: return false
if (trackerManager.myAnimeList.isLoggedIn) {
trackerManager.myAnimeList.logout()
}
return true
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val securityPreferences = migrationContext.get<SecurityPreferences>() ?: return false
val basePreferences = migrationContext.get<BasePreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}
}

View file

@ -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<App>() ?: return false
val trackerManager = migrationContext.get<TrackerManager>() ?: return false
val preferenceStore = migrationContext.get<PreferenceStore>() ?: 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
}
}

View file

@ -0,0 +1,47 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
val migrations: List<Migration>
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(),
)

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val playerPreferences = migrationContext.get<PlayerPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val preferenceStore = migrationContext.get<PreferenceStore>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val preferenceStore = migrationContext.get<PreferenceStore>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit {
remove(Preference.appStateKey("trusted_signatures"))
}
return true
}
}

View file

@ -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<App>() ?: return false
val playerPreferences = migrationContext.get<PlayerPreferences>() ?: 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
}
}

View file

@ -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<PreferenceStore>() ?: return false
replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
newKey = { Preference.privateKey(it) },
)
return true
}
}

View file

@ -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<PreferenceStore>() ?: return false
val uiPreferences = migrationContext.get<UiPreferences>() ?: return false
val pref = preferenceStore.getInt("relative_time", 7)
if (pref.get() == 0) {
uiPreferences.relativeTime().set(false)
}
return true
}
}

View file

@ -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<App>() ?: return false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
context.workManager.cancelAllWorkByTag("UpdateChecker")
context.workManager.cancelAllWorkByTag("ExtensionUpdate")
prefs.edit {
remove("automatic_ext_updates")
}
return true
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val readerPreferences = migrationContext.get<ReaderPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.contains("pref_rotation_type_key")) {
prefs.edit {
putInt("pref_rotation_type_key", 1)
}
}
return true
}
}

View file

@ -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<App>() ?: return false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: 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
}
}

View file

@ -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<App>() ?: return false
AnimeLibraryUpdateJob.setupTask(context)
return true
}
}

View file

@ -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<App>() ?: return false
MangaLibraryUpdateJob.setupTask(context)
AnimeLibraryUpdateJob.setupTask(context)
return true
}
}

View file

@ -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<App>() ?: return false
BackupCreateJob.setupTask(context)
return true
}
}

View file

@ -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<App>() ?: return false
MangaLibraryUpdateJob.setupTask(context)
return true
}
}

View file

@ -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<App>() ?: return false
val uiPreferences = migrationContext.get<UiPreferences>() ?: 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
}
}

View file

@ -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<SourcePreferences>() ?: return@withIOContext false
val animeExtensionRepositoryRepository =
migrationContext.get<AnimeExtensionRepoRepository>() ?: 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<MangaExtensionRepoRepository>() ?: 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
}
}

View file

@ -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<App>() ?: return false
MangaLibraryUpdateJob.cancelAllWorks(context)
AnimeLibraryUpdateJob.cancelAllWorks(context)
MangaLibraryUpdateJob.setupTask(context)
AnimeLibraryUpdateJob.setupTask(context)
return true
}
}

View file

@ -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)
}
}