Rewrite Migrations (#577)

* Rewrite Migrations

* Fix Detekt errors

* Do migrations synchronous

* Filter and sort migrations

* Review changes

* Review changes 2

* Fix Detekt errors

(cherry picked from commit 666d6aa117756f0a9a57b31f91b7acb0ee5d7409)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
This commit is contained in:
Andreas
2024-03-25 18:26:19 +01:00
committed by Jobobby04
parent ceff887a10
commit 90d5104bdc
51 changed files with 1670 additions and 954 deletions
@@ -0,0 +1,53 @@
package mihon.core.migration
import kotlinx.coroutines.runBlocking
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler
object MigrateUtils {
fun updateSourceId(migrationContext: MigrationContext, newId: Long, oldId: Long) {
val handler = migrationContext.get<DatabaseHandler>() ?: return
runBlocking {
handler.await { ehQueries.migrateSource(newId, oldId) }
}
}
@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()
}
}
}
}
}
@@ -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)
}
}
}
}
@@ -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)
}
}
@@ -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 }
}
}
@@ -0,0 +1,19 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.backup.service.BackupPreferences
class AlwaysBackupMigration : Migration {
override val version: Float = 40f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val backupPreferences = migrationContext.get<BackupPreferences>() ?: return@withIOContext false
if (backupPreferences.backupInterval().get() == 0) {
backupPreferences.backupInterval().set(12)
}
return@withIOContext true
}
}
@@ -0,0 +1,24 @@
package mihon.core.migration.migrations
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.util.system.DeviceUtil
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class ChangeMiuiExtensionInstallerMigration : Migration {
override val version: Float = 27f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val basePreferences = migrationContext.get<BasePreferences>() ?: return@withIOContext false
if (
DeviceUtil.isMiui &&
basePreferences.extensionInstaller().get() == BasePreferences.ExtensionInstaller
.PACKAGEINSTALLER
) {
basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.LEGACY)
}
return@withIOContext true
}
}
@@ -0,0 +1,27 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class ChangeThemeModeToUppercaseMigration : Migration {
override val version: Float = 42f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val uiPreferences = migrationContext.get<UiPreferences>() ?: return@withIOContext false
if (uiPreferences.themeMode().isSet()) {
prefs.edit {
val themeMode = prefs.getString(uiPreferences.themeMode().key(), null) ?: return@edit
putString(uiPreferences.themeMode().key(), themeMode.uppercase())
}
}
return@withIOContext true
}
}
@@ -0,0 +1,26 @@
package mihon.core.migration.migrations
import android.content.Context
import androidx.core.content.edit
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class ChangeTrackingQueueTypeMigration : Migration {
override val version: Float = 44f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val trackingQueuePref = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
trackingQueuePref.all.forEach {
val (_, lastChapterRead) = it.value.toString().split(":")
trackingQueuePref.edit {
remove(it.key)
putFloat(it.key, lastChapterRead.toFloat())
}
}
return@withIOContext true
}
}
@@ -0,0 +1,33 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.cache.PagePreviewCache
import logcat.LogPriority
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
import java.io.File
class ClearBrokenPagePreviewCacheMigration : Migration {
override val version: Float = 58f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val pagePreviewCache = migrationContext.get<PagePreviewCache>() ?: return@withIOContext false
pagePreviewCache.clear()
File(context.cacheDir, PagePreviewCache.PARAMETER_CACHE_DIRECTORY).listFiles()?.forEach {
if (it.name == "journal" || it.name.startsWith("journal.")) {
return@forEach
}
try {
it.delete()
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Failed to remove file from cache" }
}
}
return@withIOContext true
}
}
@@ -0,0 +1,28 @@
package mihon.core.migration.migrations
import eu.kanade.domain.manga.interactor.UpdateManga
import exh.source.HBROWSE_SOURCE_ID
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.manga.interactor.GetMangaBySource
import tachiyomi.domain.manga.model.MangaUpdate
class DelegateHBrowseMigration : Migration {
override val version: Float = 4f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val getMangaBySource = migrationContext.get<GetMangaBySource>() ?: return@withIOContext false
val updateManga = migrationContext.get<UpdateManga>() ?: return@withIOContext false
MigrateUtils.updateSourceId(migrationContext, HBROWSE_SOURCE_ID, 6912)
// Migrate BHrowse URLs
val hBrowseManga = getMangaBySource.await(HBROWSE_SOURCE_ID)
val mangaUpdates = hBrowseManga.map {
MangaUpdate(it.id, url = it.url + "/c00001/")
}
updateManga.awaitAll(mangaUpdates)
return@withIOContext true
}
}
@@ -0,0 +1,17 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.source.online.all.NHentai
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class DelegateNHentaiMigration : Migration {
override val version: Float = 6f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
MigrateUtils.updateSourceId(migrationContext, NHentai.otherId, 6907)
return@withIOContext true
}
}
@@ -0,0 +1,36 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import exh.log.xLogE
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import java.io.File
class DeleteOldEhFavoritesDatabaseMigration : Migration {
override val version: Float = 24f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
try {
sequenceOf(
"fav-sync",
"fav-sync.management",
"fav-sync.lock",
"fav-sync.note",
).map {
File(context.filesDir, it)
}.filter(File::exists).forEach {
if (it.isDirectory) {
it.deleteRecursively()
} else {
it.delete()
}
}
} catch (e: Exception) {
xLogE("Failed to delete old favorites database", e)
}
return@withIOContext true
}
}
@@ -0,0 +1,17 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.data.DatabaseHandler
class DeleteOldMangaDexTracksMigration : Migration {
override val version: Float = 17f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val handler = migrationContext.get<DatabaseHandler>() ?: return@withIOContext false
// Delete old mangadex trackers
handler.await { ehQueries.deleteBySyncId(6) }
return@withIOContext true
}
}
@@ -0,0 +1,17 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.data.track.TrackerManager
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class LogoutFromMALMigration : Migration {
override val version: Float = 12f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
// Force MAL log out due to login flow change
migrationContext.get<TrackerManager>()?.myAnimeList?.logout()
return@withIOContext true
}
}
@@ -0,0 +1,17 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.data.track.TrackerManager
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class LogoutFromMangaDexMigration : Migration {
override val version: Float = 45f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
// Force MangaDex log out due to login flow change
migrationContext.get<TrackerManager>()?.mdList?.logout()
return@withIOContext true
}
}
@@ -0,0 +1,191 @@
package mihon.core.migration.migrations
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.source.Source
import exh.source.MERGED_SOURCE_ID
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.chapter.ChapterMapper
import tachiyomi.domain.chapter.interactor.DeleteChapters
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaBySource
import tachiyomi.domain.manga.interactor.InsertMergedReference
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.model.MergedMangaReference
import tachiyomi.domain.source.service.SourceManager
class MergedMangaRewriteMigration : Migration {
override val version: Float = 7f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val handler = migrationContext.get<DatabaseHandler>() ?: return@withIOContext false
val getMangaBySource = migrationContext.get<GetMangaBySource>() ?: return@withIOContext false
val getManga = migrationContext.get<GetManga>() ?: return@withIOContext false
val updateManga = migrationContext.get<UpdateManga>() ?: return@withIOContext false
val insertMergedReference = migrationContext.get<InsertMergedReference>() ?: return@withIOContext false
val sourceManager = migrationContext.get<SourceManager>() ?: return@withIOContext false
val deleteChapters = migrationContext.get<DeleteChapters>() ?: return@withIOContext false
val updateChapter = migrationContext.get<UpdateChapter>() ?: return@withIOContext false
val mergedMangas = getMangaBySource.await(MERGED_SOURCE_ID)
if (mergedMangas.isNotEmpty()) {
val mangaConfigs = mergedMangas.mapNotNull { mergedManga ->
readMangaConfig(mergedManga)?.let { mergedManga to it }
}
if (mangaConfigs.isNotEmpty()) {
val mangaToUpdate = mutableListOf<MangaUpdate>()
val mergedMangaReferences = mutableListOf<MergedMangaReference>()
mangaConfigs.onEach { mergedManga ->
val newFirst = mergedManga.second.children.firstOrNull()?.url?.let {
if (getManga.await(it, MERGED_SOURCE_ID) != null) return@onEach
mangaToUpdate += MangaUpdate(id = mergedManga.first.id, url = it)
mergedManga.first.copy(url = it)
} ?: mergedManga.first
mergedMangaReferences += MergedMangaReference(
id = -1,
isInfoManga = false,
getChapterUpdates = false,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = false,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = newFirst.id,
mangaUrl = newFirst.url,
mangaSourceId = MERGED_SOURCE_ID,
)
mergedManga.second.children.distinct().forEachIndexed { index, mangaSource ->
val load = mangaSource.load(getManga, sourceManager) ?: return@forEachIndexed
mergedMangaReferences += MergedMangaReference(
id = -1,
isInfoManga = index == 0,
getChapterUpdates = true,
chapterSortMode = 0,
chapterPriority = 0,
downloadChapters = true,
mergeId = newFirst.id,
mergeUrl = newFirst.url,
mangaId = load.manga.id,
mangaUrl = load.manga.url,
mangaSourceId = load.source.id,
)
}
}
updateManga.awaitAll(mangaToUpdate)
insertMergedReference.awaitAll(mergedMangaReferences)
val loadedMangaList = mangaConfigs
.map { it.second.children }
.flatten()
.mapNotNull { it.load(getManga, sourceManager) }
.distinct()
val chapters =
handler.awaitList {
ehQueries.getChaptersByMangaIds(
mergedMangas.map { it.id },
ChapterMapper::mapChapter,
)
}
val mergedMangaChapters =
handler.awaitList {
ehQueries.getChaptersByMangaIds(
loadedMangaList.map { it.manga.id },
ChapterMapper::mapChapter,
)
}
val mergedMangaChaptersMatched = mergedMangaChapters.mapNotNull { chapter ->
loadedMangaList.firstOrNull {
it.manga.id == chapter.id
}?.let { it to chapter }
}
val parsedChapters = chapters.filter {
it.read || it.lastPageRead != 0L
}.mapNotNull { chapter -> readUrlConfig(chapter.url)?.let { chapter to it } }
val chaptersToUpdate = mutableListOf<ChapterUpdate>()
parsedChapters.forEach { parsedChapter ->
mergedMangaChaptersMatched.firstOrNull {
it.second.url == parsedChapter.second.url &&
it.first.source.id == parsedChapter.second.source &&
it.first.manga.url == parsedChapter.second.mangaUrl
}?.let {
chaptersToUpdate += ChapterUpdate(
it.second.id,
read = parsedChapter.first.read,
lastPageRead = parsedChapter.first.lastPageRead,
)
}
}
deleteChapters.await(mergedMangaChapters.map { it.id })
updateChapter.awaitAll(chaptersToUpdate)
}
}
return@withIOContext true
}
@Serializable
private data class UrlConfig(
@SerialName("s")
val source: Long,
@SerialName("u")
val url: String,
@SerialName("m")
val mangaUrl: String,
)
@Serializable
private data class MangaConfig(
@SerialName("c")
val children: List<MangaSource>,
) {
companion object {
fun readFromUrl(url: String): MangaConfig? {
return try {
Json.decodeFromString(url)
} catch (e: Exception) {
null
}
}
}
}
private fun readMangaConfig(manga: Manga): MangaConfig? {
return MangaConfig.readFromUrl(manga.url)
}
@Serializable
private data class MangaSource(
@SerialName("s")
val source: Long,
@SerialName("u")
val url: String,
) {
suspend fun load(getManga: GetManga, sourceManager: SourceManager): LoadedMangaSource? {
val manga = getManga.await(url, source) ?: return null
val source = sourceManager.getOrStub(source)
return LoadedMangaSource(source, manga)
}
}
private fun readUrlConfig(url: String): UrlConfig? {
return try {
Json.decodeFromString(url)
} catch (e: Exception) {
null
}
}
private data class LoadedMangaSource(val source: Source, val manga: Manga)
}
@@ -0,0 +1,48 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
val migrations: List<Migration>
get() = listOf(
SetupBackupCreateMigration(),
SetupLibraryUpdateMigration(),
SetupEHentaiUpdateMigration(),
SetupSyncDataMigration(),
DelegateHBrowseMigration(),
DelegateNHentaiMigration(),
MergedMangaRewriteMigration(),
LogoutFromMALMigration(),
MoveDOHSettingMigration(),
ResetRotationSettingMigration(),
ResetReaderSettingsMigration(),
DeleteOldMangaDexTracksMigration(),
RemoveOldReaderThemeMigration(),
RemoveShorterLibraryUpdatesMigration(),
MoveLibrarySortingSettingsMigration(),
RemoveShortLibraryUpdatesMigration(),
MoveLibraryNonCompleteSettingMigration(),
DeleteOldEhFavoritesDatabaseMigration(),
MoveSecureScreenSettingMigration(),
ChangeMiuiExtensionInstallerMigration(),
MoveCoverOnlyGridSettingMigration(),
MoveCatalogueCoverOnlyGridSettingMigration(),
MoveLatestToFeedMigration(),
MoveReaderTapSettingMigration(),
MoveSortingModeSettingsMigration(),
MoveSortingModeSettingMigration(),
AlwaysBackupMigration(),
ResetFilterAndSortSettingsMigration(),
ChangeThemeModeToUppercaseMigration(),
MoveReadingButtonSettingMigration(),
ChangeTrackingQueueTypeMigration(),
LogoutFromMangaDexMigration(),
RemoveUpdateCheckerJobsMigration(),
RemoveBatteryNotLowRestrictionMigration(),
MoveRelativeTimeSettingMigration(),
ClearBrokenPagePreviewCacheMigration(),
MoveSettingsToPrivateOrAppStateMigration(),
MoveExtensionRepoSettingsMigration(),
MoveCacheToDiskSettingMigration(),
MoveEncryptionSettingsToAppStateMigration(),
TrustExtensionRepositoryMigration(),
)
@@ -0,0 +1,24 @@
package mihon.core.migration.migrations
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveCacheToDiskSettingMigration : Migration {
override val version: Float = 66f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val readerPreferences = migrationContext.get<ReaderPreferences>() ?: return@withIOContext false
val cacheImagesToDisk = prefs.getBoolean("cache_archive_manga_on_disk", false)
if (cacheImagesToDisk) {
readerPreferences.archiveReaderMode().set(ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK)
}
return@withIOContext true
}
}
@@ -0,0 +1,24 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveCatalogueCoverOnlyGridSettingMigration : Migration {
override val version: Float = 29f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.getString("pref_display_mode_catalogue", null) == "NO_TITLE_GRID") {
prefs.edit(commit = true) {
putString("pref_display_mode_catalogue", "COMPACT_GRID")
}
}
return@withIOContext true
}
}
@@ -0,0 +1,24 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveCoverOnlyGridSettingMigration : Migration {
override val version: Float = 28f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
if (prefs.getString("pref_display_mode_library", null) == "NO_TITLE_GRID") {
prefs.edit(commit = true) {
putString("pref_display_mode_library", "COVER_ONLY_GRID")
}
}
return@withIOContext true
}
}
@@ -0,0 +1,30 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveDOHSettingMigration : Migration {
override val version: Float = 14f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val networkPreferences = migrationContext.get<NetworkPreferences>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Migrate DNS over HTTPS setting
val wasDohEnabled = prefs.getBoolean("enable_doh", false)
if (wasDohEnabled) {
prefs.edit {
putInt(networkPreferences.dohProvider().key(), PREF_DOH_CLOUDFLARE)
remove("enable_doh")
}
}
return@withIOContext true
}
}
@@ -0,0 +1,45 @@
package mihon.core.migration.migrations
import android.widget.Toast
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.util.system.toast
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
class MoveEncryptionSettingsToAppStateMigration : Migration {
override val version: Float = 66f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
if (prefs.getBoolean(Preference.privateKey("encrypt_database"), false)) {
withUIContext {
context.toast(
"Restart the app to load your encrypted library",
Toast.LENGTH_LONG
)
}
}
val appStatePrefsToReplace = listOf(
"__PRIVATE_sql_password",
"__PRIVATE_encrypt_database",
"__PRIVATE_cbz_password",
)
MigrateUtils.replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key in appStatePrefsToReplace },
newKey = { Preference.appStateKey(it.replace("__PRIVATE_", "").trim()) },
)
return@withIOContext true
}
}
@@ -0,0 +1,37 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.App
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.withIOContext
class MoveExtensionRepoSettingsMigration : Migration {
override val version: Float = 60f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
val sourcePreferences = migrationContext.get<SourcePreferences>() ?: return@withIOContext false
sourcePreferences.extensionRepos().getAndSet {
it.map { "https://raw.githubusercontent.com/$it/repo" }.toSet()
}
MigrateUtils.replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") },
newKey = { Preference.privateKey(it) },
)
prefs.edit {
remove(Preference.appStateKey("trusted_signatures"))
}
return@withIOContext true
}
}
@@ -0,0 +1,63 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import exh.util.nullIfBlank
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.source.interactor.InsertFeedSavedSearch
import tachiyomi.domain.source.interactor.InsertSavedSearch
import tachiyomi.domain.source.model.FeedSavedSearch
import tachiyomi.domain.source.model.SavedSearch
class MoveLatestToFeedMigration : Migration {
override val version: Float = 31f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val insertSavedSearch = migrationContext.get<InsertSavedSearch>() ?: return@withIOContext false
val insertFeedSavedSearch = migrationContext.get<InsertFeedSavedSearch>() ?: return@withIOContext false
val savedSearch = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull {
runCatching {
val content = Json.decodeFromString<JsonObject>(it.substringAfter(':'))
SavedSearch(
id = -1,
source = it.substringBefore(':').toLongOrNull()
?: return@runCatching null,
name = content["name"]!!.jsonPrimitive.content,
query = content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
filtersJson = Json.encodeToString(content["filters"]!!.jsonArray),
)
}.getOrNull()
}
if (!savedSearch.isNullOrEmpty()) {
insertSavedSearch.awaitAll(savedSearch)
}
val feedSavedSearch = prefs.getStringSet("latest_tab_sources", emptySet())?.map {
FeedSavedSearch(
id = -1,
source = it.toLong(),
savedSearch = null,
global = true,
)
}
if (!feedSavedSearch.isNullOrEmpty()) {
insertFeedSavedSearch.awaitAll(feedSavedSearch)
}
prefs.edit(commit = true) {
remove("eh_saved_searches")
remove("latest_tab_sources")
}
return@withIOContext true
}
}
@@ -0,0 +1,25 @@
package mihon.core.migration.migrations
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.minusAssign
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class MoveLibraryNonCompleteSettingMigration : Migration {
override val version: Float = 23f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
if (!oldUpdateOngoingOnly) {
libraryPreferences.autoUpdateMangaRestrictions() -= LibraryPreferences.MANGA_NON_COMPLETED
}
return@withIOContext true
}
}
@@ -0,0 +1,57 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
class MoveLibrarySortingSettingsMigration : Migration {
override val version: Float = 20f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
try {
val oldSortingMode = prefs.getInt(libraryPreferences.sortingMode().key(), 0 /* ALPHABETICAL */)
val oldSortingDirection = prefs.getBoolean("library_sorting_ascending", true)
val newSortingMode = when (oldSortingMode) {
0 -> "ALPHABETICAL"
1 -> "LAST_READ"
2 -> "LAST_MANGA_UPDATE"
3 -> "UNREAD_COUNT"
4 -> "TOTAL_CHAPTERS"
6 -> "LATEST_CHAPTER"
7 -> "DRAG_AND_DROP"
8 -> "DATE_ADDED"
9 -> "TAG_LIST"
10 -> "CHAPTER_FETCH_DATE"
else -> "ALPHABETICAL"
}
val newSortingDirection = when (oldSortingDirection) {
true -> "ASCENDING"
else -> "DESCENDING"
}
prefs.edit(commit = true) {
remove(libraryPreferences.sortingMode().key())
remove("library_sorting_ascending")
}
prefs.edit {
putString(libraryPreferences.sortingMode().key(), newSortingMode)
putString("library_sorting_ascending", newSortingDirection)
}
} catch (e: Exception) {
logcat(throwable = e) { "Already done migration" }
}
return@withIOContext true
}
}
@@ -0,0 +1,25 @@
package mihon.core.migration.migrations
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveReaderTapSettingMigration : Migration {
override val version: Float = 32f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val readerPreferences = migrationContext.get<ReaderPreferences>() ?: return@withIOContext false
val oldReaderTap = prefs.getBoolean("reader_tap", false)
if (!oldReaderTap) {
readerPreferences.navigationModePager().set(5)
readerPreferences.navigationModeWebtoon().set(5)
}
return@withIOContext true
}
}
@@ -0,0 +1,23 @@
package mihon.core.migration.migrations
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class MoveReadingButtonSettingMigration : Migration {
override val version: Float = 43f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
if (prefs.getBoolean("start_reading_button", false)) {
libraryPreferences.showContinueReadingButton().set(true)
}
return@withIOContext true
}
}
@@ -0,0 +1,22 @@
package mihon.core.migration.migrations
import eu.kanade.domain.ui.UiPreferences
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.lang.withIOContext
class MoveRelativeTimeSettingMigration : Migration {
override val version: Float = 57f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
val uiPreferences = migrationContext.get<UiPreferences>() ?: return@withIOContext false
val pref = preferenceStore.getInt("relative_time", 7)
if (pref.get() == 0) {
uiPreferences.relativeTime().set(false)
}
return@withIOContext true
}
}
@@ -0,0 +1,24 @@
package mihon.core.migration.migrations
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class MoveSecureScreenSettingMigration : Migration {
override val version: Float = 27f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val securityPreferences = migrationContext.get<SecurityPreferences>() ?: return@withIOContext false
val oldSecureScreen = prefs.getBoolean("secure_screen", false)
if (oldSecureScreen) {
securityPreferences.secureScreen().set(SecurityPreferences.SecureScreenMode.ALWAYS)
}
return@withIOContext true
}
}
@@ -0,0 +1,67 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import mihon.core.migration.MigrateUtils
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.util.lang.withIOContext
class MoveSettingsToPrivateOrAppStateMigration : Migration {
override val version: Float = 59f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
val prefsToReplace = listOf(
"pref_download_only",
"incognito_mode",
"last_catalogue_source",
"trusted_signatures",
"last_app_closed",
"library_update_last_timestamp",
"library_unseen_updates_count",
"last_used_category",
"last_app_check",
"last_ext_check",
"last_version_code",
"skip_pre_migration",
"eh_auto_update_stats",
"storage_dir",
)
MigrateUtils.replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key in prefsToReplace },
newKey = { Preference.appStateKey(it) },
)
val privatePrefsToReplace = listOf(
"sql_password",
"encrypt_database",
"cbz_password",
"password_protect_downloads",
"eh_ipb_member_id",
"enable_exhentai",
"eh_ipb_member_id",
"eh_ipb_pass_hash",
"eh_igneous",
"eh_ehSettingsProfile",
"eh_exhSettingsProfile",
"eh_settingsKey",
"eh_sessionCookie",
"eh_hathPerksCookie",
)
MigrateUtils.replacePreferences(
preferenceStore = preferenceStore,
filterPredicate = { it.key in privatePrefsToReplace },
newKey = { Preference.privateKey(it) },
)
// Deleting old download cache index files, but might as well clear it all out
context.cacheDir.deleteRecursively()
return@withIOContext true
}
}
@@ -0,0 +1,27 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class MoveSortingModeSettingMigration : Migration {
override val version: Float = 39f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.edit {
val sort = prefs.getString(libraryPreferences.sortingMode().key(), null) ?: return@edit
val direction = prefs.getString("library_sorting_ascending", "ASCENDING")!!
putString(libraryPreferences.sortingMode().key(), "$sort,$direction")
remove("library_sorting_ascending")
}
return@withIOContext true
}
}
@@ -0,0 +1,49 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.data.DatabaseHandler
import tachiyomi.data.category.CategoryMapper
import tachiyomi.domain.library.service.LibraryPreferences
class MoveSortingModeSettingsMigration : Migration {
override val version: Float = 38f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val handler = migrationContext.get<DatabaseHandler>() ?: return@withIOContext false
// Handle renamed enum values
val newSortingMode = when (
val oldSortingMode = prefs.getString(libraryPreferences.sortingMode().key(), "ALPHABETICAL")
) {
"LAST_CHECKED" -> "LAST_MANGA_UPDATE"
"UNREAD" -> "UNREAD_COUNT"
"DATE_FETCHED" -> "CHAPTER_FETCH_DATE"
"DRAG_AND_DROP" -> "ALPHABETICAL"
else -> oldSortingMode
}
prefs.edit {
putString(libraryPreferences.sortingMode().key(), newSortingMode)
}
handler.await(true) {
categoriesQueries.getCategories(CategoryMapper::mapCategory).executeAsList()
.filter { (it.flags and 0b00111100L) == 0b00100000L }
.forEach {
categoriesQueries.update(
categoryId = it.id,
flags = it.flags and 0b00111100L.inv(),
name = null,
order = null,
)
}
}
return@withIOContext true
}
}
@@ -0,0 +1,21 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.getAndSet
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class RemoveBatteryNotLowRestrictionMigration : Migration {
override val version: Float = 56f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val pref = libraryPreferences.autoUpdateDeviceRestrictions()
if (pref.isSet() && "battery_not_low" in pref.get()) {
pref.getAndSet { it - "battery_not_low" }
}
return@withIOContext true
}
}
@@ -0,0 +1,20 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class RemoveOldReaderThemeMigration : Migration {
override val version: Float = 18f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val readerPreferences = migrationContext.get<ReaderPreferences>() ?: return@withIOContext false
val readerTheme = readerPreferences.readerTheme().get()
if (readerTheme == 4) {
readerPreferences.readerTheme().set(3)
}
return@withIOContext true
}
}
@@ -0,0 +1,20 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class RemoveShortLibraryUpdatesMigration : Migration {
override val version: Float = 22f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval in listOf(3, 4, 6, 8)) {
libraryPreferences.autoUpdateInterval().set(12)
}
return@withIOContext true
}
}
@@ -0,0 +1,20 @@
package mihon.core.migration.migrations
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class RemoveShorterLibraryUpdatesMigration : Migration {
override val version: Float = 18f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val updateInterval = libraryPreferences.autoUpdateInterval().get()
if (updateInterval == 1 || updateInterval == 2) {
libraryPreferences.autoUpdateInterval().set(3)
}
return@withIOContext true
}
}
@@ -0,0 +1,55 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.util.system.workManager
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.preference.PreferenceStore
import tachiyomi.core.common.preference.TriState
import tachiyomi.core.common.preference.getEnum
import tachiyomi.core.common.util.lang.withIOContext
class RemoveUpdateCheckerJobsMigration : Migration {
override val version: Float = 52f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val preferenceStore = migrationContext.get<PreferenceStore>() ?: return@withIOContext false
val trackerManager = migrationContext.get<TrackerManager>() ?: return@withIOContext false
// Removed background jobs
context.workManager.cancelAllWorkByTag("UpdateChecker")
context.workManager.cancelAllWorkByTag("ExtensionUpdate")
prefs.edit {
remove("automatic_ext_updates")
}
val prefKeys = listOf(
"pref_filter_library_downloaded",
"pref_filter_library_unread",
"pref_filter_library_started",
"pref_filter_library_bookmarked",
"pref_filter_library_completed",
"pref_filter_library_lewd",
) + trackerManager.trackers.map { "pref_filter_library_tracked_${it.id}" }
prefKeys.forEach { key ->
val pref = prefs.getInt(key, 0)
prefs.edit {
remove(key)
val newValue = when (pref) {
1 -> TriState.ENABLED_IS
2 -> TriState.ENABLED_NOT
else -> TriState.DISABLED
}
preferenceStore.getEnum("${key}_v2", TriState.DISABLED).set(newValue)
}
}
return@withIOContext true
}
}
@@ -0,0 +1,39 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.domain.library.service.LibraryPreferences
class ResetFilterAndSortSettingsMigration : Migration {
override val version: Float = 41f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val libraryPreferences = migrationContext.get<LibraryPreferences>() ?: return@withIOContext false
val preferences = listOf(
libraryPreferences.filterChapterByRead(),
libraryPreferences.filterChapterByDownloaded(),
libraryPreferences.filterChapterByBookmarked(),
libraryPreferences.sortChapterBySourceOrNumber(),
libraryPreferences.displayChapterByNameOrNumber(),
libraryPreferences.sortChapterByAscendingOrDescending(),
)
prefs.edit {
preferences.forEach { preference ->
val key = preference.key()
val value = prefs.getInt(key, Int.MIN_VALUE)
if (value == Int.MIN_VALUE) return@forEach
remove(key)
putLong(key, value.toLong())
}
}
return@withIOContext true
}
}
@@ -0,0 +1,39 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class ResetReaderSettingsMigration : Migration {
override val version: Float = 17f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Migrate Rotation and Viewer values to default values for viewer_flags
val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) {
1 -> ReaderOrientation.FREE.flagValue
2 -> ReaderOrientation.PORTRAIT.flagValue
3 -> ReaderOrientation.LANDSCAPE.flagValue
4 -> ReaderOrientation.LOCKED_PORTRAIT.flagValue
5 -> ReaderOrientation.LOCKED_LANDSCAPE.flagValue
else -> ReaderOrientation.FREE.flagValue
}
// Reading mode flag and prefValue is the same value
val newReadingMode = prefs.getInt("pref_default_viewer_key", 1)
prefs.edit {
putInt("pref_default_orientation_type_key", newOrientation)
remove("pref_rotation_type_key")
putInt("pref_default_reading_mode_key", newReadingMode)
remove("pref_default_viewer_key")
}
return@withIOContext true
}
}
@@ -0,0 +1,25 @@
package mihon.core.migration.migrations
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.App
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import tachiyomi.core.common.util.lang.withIOContext
class ResetRotationSettingMigration : Migration {
override val version: Float = 16f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val context = migrationContext.get<App>() ?: return@withIOContext false
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
// Reset rotation to Free after replacing Lock
if (prefs.contains("pref_rotation_type_key")) {
prefs.edit {
putInt("pref_rotation_type_key", 1)
}
}
return@withIOContext true
}
}
@@ -0,0 +1,16 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
class SetupBackupCreateMigration : Migration {
override val version: Float = Migration.ALWAYS
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
val context = migrationContext.get<App>() ?: return false
BackupCreateJob.setupTask(context)
return true
}
}
@@ -0,0 +1,16 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import exh.eh.EHentaiUpdateWorker
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
class SetupEHentaiUpdateMigration : Migration {
override val version: Float = Migration.ALWAYS
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
val context = migrationContext.get<App>() ?: return false
EHentaiUpdateWorker.scheduleBackground(context)
return true
}
}
@@ -0,0 +1,16 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
class SetupLibraryUpdateMigration : Migration {
override val version: Float = Migration.ALWAYS
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
val context = migrationContext.get<App>() ?: return false
LibraryUpdateJob.setupTask(context)
return true
}
}
@@ -0,0 +1,16 @@
package mihon.core.migration.migrations
import eu.kanade.tachiyomi.App
import eu.kanade.tachiyomi.data.sync.SyncDataJob
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
class SetupSyncDataMigration : Migration {
override val version: Float = Migration.ALWAYS
override suspend fun invoke(migrationContext: MigrationContext): Boolean {
val context = migrationContext.get<App>() ?: return false
SyncDataJob.setupTask(context)
return true
}
}
@@ -0,0 +1,35 @@
package mihon.core.migration.migrations
import eu.kanade.domain.source.service.SourcePreferences
import logcat.LogPriority
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
class TrustExtensionRepositoryMigration : Migration {
override val version: Float = 67f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val sourcePreferences = migrationContext.get<SourcePreferences>() ?: return@withIOContext false
val extensionRepositoryRepository =
migrationContext.get<ExtensionRepoRepository>() ?: return@withIOContext false
for ((index, source) in sourcePreferences.extensionRepos().get().withIndex()) {
try {
extensionRepositoryRepository.upsertRepo(
source,
"Repo #${index + 1}",
null,
source,
"NOFINGERPRINT-${index + 1}",
)
} catch (e: SaveExtensionRepoException) {
logcat(LogPriority.ERROR, e) { "Error Migrating Extension Repo with baseUrl: $source" }
}
}
sourcePreferences.extensionRepos().delete()
return@withIOContext true
}
}