diff --git a/app/src/main/java/dev/yokai/core/migration/Migration.kt b/app/src/main/java/dev/yokai/core/migration/Migration.kt new file mode 100644 index 0000000000..16832a4f9e --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/Migration.kt @@ -0,0 +1,22 @@ +package dev.yokai.core.migration + +interface Migration { + val version: Float + + suspend operator fun invoke(migrationContext: MigrationContext): Boolean + + val isAlways: Boolean + get() = version == ALWAYS + + companion object { + const val ALWAYS = -1f + + fun of(version: Float, action: suspend (MigrationContext) -> Boolean): Migration = object : Migration { + override val version: Float = version + + override suspend operator fun invoke(migrationContext: MigrationContext): Boolean { + return action(migrationContext) + } + } + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/MigrationCompletedListener.kt b/app/src/main/java/dev/yokai/core/migration/MigrationCompletedListener.kt new file mode 100644 index 0000000000..6d3a687682 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/MigrationCompletedListener.kt @@ -0,0 +1,3 @@ +package dev.yokai.core.migration + +typealias MigrationCompletedListener = () -> Unit diff --git a/app/src/main/java/dev/yokai/core/migration/MigrationContext.kt b/app/src/main/java/dev/yokai/core/migration/MigrationContext.kt new file mode 100644 index 0000000000..16c8d096b7 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/MigrationContext.kt @@ -0,0 +1,10 @@ +package dev.yokai.core.migration + +import uy.kohesive.injekt.Injekt + +class MigrationContext(val dryRun: Boolean) { + + inline fun get(): T? { + return Injekt.getInstanceOrNull(T::class.java) + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/MigrationJobFactory.kt b/app/src/main/java/dev/yokai/core/migration/MigrationJobFactory.kt new file mode 100644 index 0000000000..c83279f2ea --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/MigrationJobFactory.kt @@ -0,0 +1,29 @@ +package dev.yokai.core.migration + +import co.touchlab.kermit.Logger +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async + +class MigrationJobFactory( + private val migrationContext: MigrationContext, + private val scope: CoroutineScope +) { + + fun create(migrations: List): Deferred = with(scope) { + return migrations.sortedBy { it.version } + .fold(CompletableDeferred(true)) { acc: Deferred, migration: Migration -> + if (!migrationContext.dryRun) { + Logger.i { "Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" } + async { + val prev = acc.await() + migration(migrationContext) || prev + } + } else { + Logger.i { "(Dry-run) Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" } + CompletableDeferred(true) + } + } + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/MigrationStrategy.kt b/app/src/main/java/dev/yokai/core/migration/MigrationStrategy.kt new file mode 100644 index 0000000000..56e44bf8cc --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/MigrationStrategy.kt @@ -0,0 +1,55 @@ +package dev.yokai.core.migration + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.launch + +interface MigrationStrategy { + operator fun invoke(migrations: List): Deferred +} + +class DefaultMigrationStrategy( + private val migrationJobFactory: MigrationJobFactory, + private val migrationCompletedListener: MigrationCompletedListener, + private val scope: CoroutineScope +) : MigrationStrategy { + + override operator fun invoke(migrations: List): Deferred = with(scope) { + if (migrations.isEmpty()) { + return@with CompletableDeferred(false) + } + + val chain = migrationJobFactory.create(migrations) + + launch { + if (chain.await()) migrationCompletedListener() + }.start() + + chain + } +} + +class InitialMigrationStrategy(private val strategy: DefaultMigrationStrategy) : MigrationStrategy { + + override operator fun invoke(migrations: List): Deferred { + return strategy(migrations.filter { it.isAlways }) + } +} + +class NoopMigrationStrategy(val state: Boolean) : MigrationStrategy { + + override fun invoke(migrations: List): Deferred { + return CompletableDeferred(state) + } +} + +class VersionRangeMigrationStrategy( + private val versions: IntRange, + private val strategy: DefaultMigrationStrategy +) : MigrationStrategy { + + override operator fun invoke(migrations: List): Deferred { + return strategy(migrations.filter { it.isAlways || it.version.toInt() in versions }) + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/MigrationStrategyFactory.kt b/app/src/main/java/dev/yokai/core/migration/MigrationStrategyFactory.kt new file mode 100644 index 0000000000..2094ffebb0 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/MigrationStrategyFactory.kt @@ -0,0 +1,23 @@ +package dev.yokai.core.migration + +class MigrationStrategyFactory( + private val factory: MigrationJobFactory, + private val migrationCompletedListener: MigrationCompletedListener, +) { + + fun create(old: Int, new: Int): MigrationStrategy { + val versions = (old + 1)..new + val strategy = when { + old == 0 -> InitialMigrationStrategy( + strategy = DefaultMigrationStrategy(factory, migrationCompletedListener, Migrator.scope), + ) + + old >= new -> NoopMigrationStrategy(false) + else -> VersionRangeMigrationStrategy( + versions = versions, + strategy = DefaultMigrationStrategy(factory, migrationCompletedListener, Migrator.scope), + ) + } + return strategy + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/Migrator.kt b/app/src/main/java/dev/yokai/core/migration/Migrator.kt new file mode 100644 index 0000000000..52ecbe105f --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/Migrator.kt @@ -0,0 +1,40 @@ +package dev.yokai.core.migration + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking + +object Migrator { + private var result: Deferred? = null + val scope = CoroutineScope(Dispatchers.Main + Job()) + + fun initialize( + old: Int, + new: Int, + migrations: List, + dryRun: Boolean = false, + onMigrationComplete: () -> Unit + ) { + val migrationContext = MigrationContext(dryRun) + val migrationJobFactory = MigrationJobFactory(migrationContext, scope) + val migrationStrategyFactory = MigrationStrategyFactory(migrationJobFactory, onMigrationComplete) + val strategy = migrationStrategyFactory.create(old, new) + result = strategy(migrations) + } + + suspend fun await(): Boolean { + val result = result ?: CompletableDeferred(false) + return result.await() + } + + fun release() { + result = null + } + + fun awaitAndRelease(): Boolean = runBlocking { + await().also { release() } + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/ChapterCacheMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/ChapterCacheMigration.kt new file mode 100644 index 0000000000..3b2793be1d --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/ChapterCacheMigration.kt @@ -0,0 +1,25 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import java.io.File + +/** + * Delete external chapter cache dir. + */ +class ChapterCacheMigration : Migration { + override val version: Float = 26f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val extCache = context.externalCacheDir + if (extCache != null) { + val chapterCache = File(extCache, "chapter_disk_cache") + if (chapterCache.exists()) { + chapterCache.deleteRecursively() + } + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/CoverCacheMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/CoverCacheMigration.kt new file mode 100644 index 0000000000..ad1f809258 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/CoverCacheMigration.kt @@ -0,0 +1,27 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import java.io.File + +/** + * Move covers to external files dir. + */ +class CoverCacheMigration : Migration { + override val version: Float = 19f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val oldDir = File(context.externalCacheDir, "cover_disk_cache") + if (oldDir.exists()) { + val destDir = context.getExternalFilesDir("covers") + if (destDir != null) { + oldDir.listFiles()?.forEach { + it.renameTo(File(destDir, it.name)) + } + } + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/CustomInfoMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/CustomInfoMigration.kt new file mode 100644 index 0000000000..ecc043d567 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/CustomInfoMigration.kt @@ -0,0 +1,18 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.ui.library.LibraryPresenter + +class CustomInfoMigration : Migration { + override val version: Float = 66f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + try { + LibraryPresenter.updateCustoms() + } catch (e: Exception) { + return false + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/CutoutMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/CutoutMigration.kt new file mode 100644 index 0000000000..ff030c0495 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/CutoutMigration.kt @@ -0,0 +1,47 @@ +package dev.yokai.core.migration.migrations + +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import dev.yokai.domain.ui.settings.ReaderPreferences +import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour +import dev.yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.preference.PreferenceKeys +import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig + +class CutoutMigration : Migration { + override val version: Float = 121f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val readerPreferences: ReaderPreferences = migrationContext.get() ?: return false + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + try { + val oldCutoutBehaviour = prefs.getInt(PreferenceKeys.pagerCutoutBehavior, 0) + readerPreferences.pagerCutoutBehavior().set( + when (oldCutoutBehaviour) { + PagerConfig.CUTOUT_PAD -> CutoutBehaviour.HIDE + PagerConfig.CUTOUT_IGNORE -> CutoutBehaviour.IGNORE + else -> CutoutBehaviour.SHOW + } + ) + } catch (_: Exception) { + readerPreferences.pagerCutoutBehavior().set(CutoutBehaviour.SHOW) + } + + try { + val oldCutoutBehaviour = prefs.getInt("landscape_cutout_behavior", 0) + readerPreferences.landscapeCutoutBehavior().set( + when (oldCutoutBehaviour) { + 0 -> LandscapeCutoutBehaviour.HIDE + else -> LandscapeCutoutBehaviour.DEFAULT + } + ) + } catch (_: Exception) { + readerPreferences.landscapeCutoutBehavior().set(LandscapeCutoutBehaviour.DEFAULT) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/DoHMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/DoHMigration.kt new file mode 100644 index 0000000000..dc8fa5e6fe --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/DoHMigration.kt @@ -0,0 +1,27 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.preference.PreferenceKeys +import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE + +class DoHMigration : Migration { + override val version: Float = 71f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + // Migrate DNS over HTTPS setting + val wasDohEnabled = prefs.getBoolean("enable_doh", false) + if (wasDohEnabled) { + prefs.edit { + putInt(PreferenceKeys.dohProvider, PREF_DOH_CLOUDFLARE) + remove("enable_doh") + } + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/DownloadedChaptersMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/DownloadedChaptersMigration.kt new file mode 100644 index 0000000000..495a78b48d --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/DownloadedChaptersMigration.kt @@ -0,0 +1,16 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.download.DownloadProvider + +class DownloadedChaptersMigration : Migration { + override val version: Float = 54f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + DownloadProvider(context).renameChapters() + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/EnabledLanguageMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/EnabledLanguageMigration.kt new file mode 100644 index 0000000000..4a4d4d8589 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/EnabledLanguageMigration.kt @@ -0,0 +1,21 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.plusAssign + +class EnabledLanguageMigration : Migration { + override val version: Float = 83f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: PreferencesHelper = migrationContext.get() ?: return false + + if (preferences.enabledLanguages().isSet()) { + preferences.enabledLanguages() += "all" + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/EvernoteJobUpgradeMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/EvernoteJobUpgradeMigration.kt new file mode 100644 index 0000000000..8156de774c --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/EvernoteJobUpgradeMigration.kt @@ -0,0 +1,24 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.updater.AppUpdateJob + +/** + * Restore jobs after upgrading to evernote's job scheduler. + */ +class EvernoteJobUpgradeMigration : Migration { + override val version: Float = 14f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + if (BuildConfig.INCLUDE_UPDATER) { + AppUpdateJob.setupTask(context) + } + LibraryUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/ExtensionInstallerEnumMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/ExtensionInstallerEnumMigration.kt new file mode 100644 index 0000000000..1993733182 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/ExtensionInstallerEnumMigration.kt @@ -0,0 +1,34 @@ +package dev.yokai.core.migration.migrations + +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import dev.yokai.domain.base.BasePreferences +import eu.kanade.tachiyomi.App + +/** + * Upstream no longer use Int for extension installer prefs, this solves incompatibility with upstreams backup + */ +class ExtensionInstallerEnumMigration : Migration { + override val version: Float = 119f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val basePreferences: BasePreferences = migrationContext.get() ?: return false + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + try { + val oldExtensionInstall = prefs.getInt("extension_installer", 0) + basePreferences.extensionInstaller().set( + when (oldExtensionInstall) { + 1 -> BasePreferences.ExtensionInstaller.SHIZUKU + 2 -> BasePreferences.ExtensionInstaller.PRIVATE + else -> BasePreferences.ExtensionInstaller.PACKAGEINSTALLER + } + ) + } catch (_: Exception) { + basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/InternalChapterCacheUpdateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/InternalChapterCacheUpdateMigration.kt new file mode 100644 index 0000000000..ab21e51f0d --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/InternalChapterCacheUpdateMigration.kt @@ -0,0 +1,19 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import java.io.File + +/** + * Delete internal chapter cache dir. + */ +class InternalChapterCacheUpdateMigration : Migration { + override val version: Float = 15f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + File(context.cacheDir, "chapter_disk_cache").deleteRecursively() + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/LibrarySortMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/LibrarySortMigration.kt new file mode 100644 index 0000000000..643c74b9a4 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/LibrarySortMigration.kt @@ -0,0 +1,32 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.library.LibrarySort + +class LibrarySortMigration : Migration { + override val version: Float = 110f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + try { + val librarySortString = prefs.getString("library_sorting_mode", "") + if (!librarySortString.isNullOrEmpty()) { + prefs.edit { + remove("library_sorting_mode") + putInt( + "library_sorting_mode", + LibrarySort.deserialize(librarySortString).mainValue, + ) + } + } + } catch (_: Exception) { + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/LibraryUpdateResetMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/LibraryUpdateResetMigration.kt new file mode 100644 index 0000000000..82ff102477 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/LibraryUpdateResetMigration.kt @@ -0,0 +1,17 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob + +class LibraryUpdateResetMigration : Migration { + override val version: Float = 105f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + LibraryUpdateJob.cancelAllWorks(context) + LibraryUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt b/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt new file mode 100644 index 0000000000..db933c0c41 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/Migrations.kt @@ -0,0 +1,39 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +val migrations: ImmutableList = persistentListOf( + SetupAppUpdateMigration(), + SetupBackupCreateMigration(), + SetupExtensionUpdateMigration(), + SetupLibraryUpdateMigration(), + + // For archive purposes + EvernoteJobUpgradeMigration(), + InternalChapterCacheUpdateMigration(), + CoverCacheMigration(), + ChapterCacheMigration(), + DownloadedChaptersMigration(), + WorkManagerMigration(), + CustomInfoMigration(), + MyAnimeListMigration(), + DoHMigration(), + RotationTypeMigration(), + ShortcutsMigration(), + RotationTypeEnumMigration(), + EnabledLanguageMigration(), + UpdateIntervalMigration(), + ReaderUpdateMigration(), + PrefsMigration(), + LibraryUpdateResetMigration(), + TrackerPrivateSettingsMigration(), + LibrarySortMigration(), + + // Yokai fork + ThePurgeMigration(), + ExtensionInstallerEnumMigration(), + CutoutMigration(), + RepoJsonMigration(), +) diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/MyAnimeListMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/MyAnimeListMigration.kt new file mode 100644 index 0000000000..8585ba6173 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/MyAnimeListMigration.kt @@ -0,0 +1,28 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.track.TrackManager +import eu.kanade.tachiyomi.util.system.toast + +/** + * Force MAL log out due to login flow change + * v67: switched from scraping to WebView + * v68: switched from WebView to OAuth + */ +class MyAnimeListMigration : Migration { + override val version: Float = 68f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val trackManager: TrackManager = migrationContext.get() ?: return false + val context: App = migrationContext.get() ?: return false + + if (trackManager.myAnimeList.isLogged) { + trackManager.myAnimeList.logout() + context.toast(R.string.myanimelist_relogin) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/PrefsMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/PrefsMigration.kt new file mode 100644 index 0000000000..5c3fe365e1 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/PrefsMigration.kt @@ -0,0 +1,37 @@ +package dev.yokai.core.migration.migrations + +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.preference.PreferenceValues +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.recents.RecentsPresenter +import kotlin.math.max + +class PrefsMigration : Migration { + override val version: Float = 102f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: PreferencesHelper = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val oldSecureScreen = prefs.getBoolean("secure_screen", false) + if (oldSecureScreen) { + preferences.secureScreen().set(PreferenceValues.SecureScreenMode.ALWAYS) + } + + val oldDLAfterReading = prefs.getInt("auto_download_after_reading", 0) + if (oldDLAfterReading > 0) { + preferences.autoDownloadWhileReading().set(max(2, oldDLAfterReading)) + } + + val oldGroupHistory = prefs.getBoolean("group_chapters_history", true) + if (!oldGroupHistory) { + preferences.groupChaptersHistory().set(RecentsPresenter.GroupType.Never) + } + + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/ReaderUpdateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/ReaderUpdateMigration.kt new file mode 100644 index 0000000000..0d3df4acab --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/ReaderUpdateMigration.kt @@ -0,0 +1,29 @@ +package dev.yokai.core.migration.migrations + +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.library.LibraryPresenter +import eu.kanade.tachiyomi.util.system.withIOContext + +class ReaderUpdateMigration : Migration { + override val version: Float = 88f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: PreferencesHelper = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + withIOContext { + LibraryPresenter.updateRatiosAndColors() + } + val oldReaderTap = prefs.getBoolean("reader_tap", true) + if (!oldReaderTap) { + preferences.navigationModePager().set(5) + preferences.navigationModeWebtoon().set(5) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/RepoJsonMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/RepoJsonMigration.kt new file mode 100644 index 0000000000..2f2318bf66 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/RepoJsonMigration.kt @@ -0,0 +1,37 @@ +package dev.yokai.core.migration.migrations + +import co.touchlab.kermit.Logger +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import dev.yokai.domain.extension.repo.ExtensionRepoRepository +import dev.yokai.domain.extension.repo.exception.SaveExtensionRepoException +import eu.kanade.tachiyomi.core.preference.Preference +import eu.kanade.tachiyomi.core.preference.PreferenceStore +import eu.kanade.tachiyomi.util.system.withIOContext + +class RepoJsonMigration : Migration { + override val version: Float = 130f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext { + val extensionRepoRepository: ExtensionRepoRepository = migrationContext.get() ?: return@withIOContext false + val preferenceStore: PreferenceStore = migrationContext.get() ?: return@withIOContext false + val extensionRepos: Preference> = preferenceStore.getStringSet("extension_repos", emptySet()) + + for ((index, source) in extensionRepos.get().withIndex()) { + try { + extensionRepoRepository.upsertRepository( + source, + "Repo #${index + 1}", + null, + source, + "NOFINGERPRINT-${index + 1}", + ) + } catch (e: SaveExtensionRepoException) { + Logger.e(e) { "Error Migrating Extension Repo with baseUrl: $source" } + } + } + extensionRepos.delete() + + return@withIOContext true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeEnumMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeEnumMigration.kt new file mode 100644 index 0000000000..48a24fa251 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeEnumMigration.kt @@ -0,0 +1,38 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.ui.reader.settings.OrientationType + +class RotationTypeEnumMigration : Migration { + override val version: Float = 77f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return 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 -> OrientationType.FREE.flagValue + 2 -> OrientationType.PORTRAIT.flagValue + 3 -> OrientationType.LANDSCAPE.flagValue + 4 -> OrientationType.LOCKED_PORTRAIT.flagValue + 5 -> OrientationType.LOCKED_LANDSCAPE.flagValue + else -> OrientationType.FREE.flagValue + } + + // Reading mode flag and prefValue is the same value + val newReadingMode = prefs.getInt("pref_default_viewer_key", 1) + + prefs.edit { + putInt("pref_default_orientation_type_key", newOrientation) + remove("pref_rotation_type_key") + putInt("pref_default_reading_mode_key", newReadingMode) + remove("pref_default_viewer_key") + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeMigration.kt new file mode 100644 index 0000000000..a990cbf873 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/RotationTypeMigration.kt @@ -0,0 +1,22 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App + +class RotationTypeMigration : Migration { + override val version: Float = 73f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + if (prefs.contains("pref_rotation_type_key")) { + prefs.edit { + putInt("pref_rotation_type_key", 1) + } + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/SetupAppUpdateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/SetupAppUpdateMigration.kt new file mode 100644 index 0000000000..6006fd171e --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/SetupAppUpdateMigration.kt @@ -0,0 +1,19 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.data.updater.AppUpdateJob + +class SetupAppUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + if (!BuildConfig.INCLUDE_UPDATER) return false + + val context: App = migrationContext.get() ?: return false + AppUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/SetupBackupCreateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/SetupBackupCreateMigration.kt new file mode 100644 index 0000000000..b535e435cb --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/SetupBackupCreateMigration.kt @@ -0,0 +1,16 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob + +class SetupBackupCreateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + BackupCreatorJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/SetupExtensionUpdateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/SetupExtensionUpdateMigration.kt new file mode 100644 index 0000000000..507a5eb6d1 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/SetupExtensionUpdateMigration.kt @@ -0,0 +1,16 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.extension.ExtensionUpdateJob + +class SetupExtensionUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + ExtensionUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/SetupLibraryUpdateMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/SetupLibraryUpdateMigration.kt new file mode 100644 index 0000000000..fd6d3de533 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/SetupLibraryUpdateMigration.kt @@ -0,0 +1,16 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob + +class SetupLibraryUpdateMigration : Migration { + override val version: Float = Migration.ALWAYS + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + LibraryUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/ShortcutsMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/ShortcutsMigration.kt new file mode 100644 index 0000000000..195a638c95 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/ShortcutsMigration.kt @@ -0,0 +1,36 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.preference.PreferenceKeys +import eu.kanade.tachiyomi.data.preference.PreferencesHelper + +class ShortcutsMigration : Migration { + override val version: Float = 75f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: PreferencesHelper = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + val wasShortcutsDisabled = !prefs.getBoolean("show_manga_app_shortcuts", true) + if (wasShortcutsDisabled) { + prefs.edit { + putBoolean(PreferenceKeys.showSourcesInShortcuts, false) + putBoolean(PreferenceKeys.showSeriesInShortcuts, false) + remove("show_manga_app_shortcuts") + } + } + // Handle removed every 1 or 2 hour library updates + val updateInterval = preferences.libraryUpdateInterval().get() + if (updateInterval == 1 || updateInterval == 2) { + preferences.libraryUpdateInterval().set(3) + LibraryUpdateJob.setupTask(context, 3) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/ThePurgeMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/ThePurgeMigration.kt new file mode 100644 index 0000000000..e739dd15fc --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/ThePurgeMigration.kt @@ -0,0 +1,21 @@ +package dev.yokai.core.migration.migrations + +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App + +class ThePurgeMigration : Migration { + override val version: Float = 112f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + + prefs.edit { + remove("trusted_signatures") + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/TrackerPrivateSettingsMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/TrackerPrivateSettingsMigration.kt new file mode 100644 index 0000000000..6aff6401ff --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/TrackerPrivateSettingsMigration.kt @@ -0,0 +1,26 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.core.preference.Preference +import eu.kanade.tachiyomi.core.preference.PreferenceStore + +class TrackerPrivateSettingsMigration : Migration { + override val version: Float = 108f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val preferenceStore: PreferenceStore = migrationContext.get() ?: return false + preferenceStore.getAll() + .filter { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") } + .forEach { (key, value) -> + if (value is String) { + preferenceStore + .getString(Preference.privateKey(key)) + .set(value) + + preferenceStore.getString(key).delete() + } + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/UpdateIntervalMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/UpdateIntervalMigration.kt new file mode 100644 index 0000000000..1569b96d18 --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/UpdateIntervalMigration.kt @@ -0,0 +1,24 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.preference.PreferencesHelper + +class UpdateIntervalMigration : Migration { + override val version: Float = 86f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + val preferences: PreferencesHelper = migrationContext.get() ?: return false + + // Handle removed every 3, 4, 6, and 8 hour library updates + val updateInterval = preferences.libraryUpdateInterval().get() + if (updateInterval in listOf(3, 4, 6, 8)) { + preferences.libraryUpdateInterval().set(12) + LibraryUpdateJob.setupTask(context, 12) + } + return true + } +} diff --git a/app/src/main/java/dev/yokai/core/migration/migrations/WorkManagerMigration.kt b/app/src/main/java/dev/yokai/core/migration/migrations/WorkManagerMigration.kt new file mode 100644 index 0000000000..9eff8814cf --- /dev/null +++ b/app/src/main/java/dev/yokai/core/migration/migrations/WorkManagerMigration.kt @@ -0,0 +1,30 @@ +package dev.yokai.core.migration.migrations + +import dev.yokai.core.migration.Migration +import dev.yokai.core.migration.MigrationContext +import eu.kanade.tachiyomi.App +import eu.kanade.tachiyomi.BuildConfig +import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob +import eu.kanade.tachiyomi.data.library.LibraryUpdateJob +import eu.kanade.tachiyomi.data.updater.AppUpdateJob +import eu.kanade.tachiyomi.extension.ExtensionUpdateJob +import eu.kanade.tachiyomi.ui.library.LibraryPresenter + +/** + * Restore jobs after migrating from Evernote's job scheduler to WorkManager. + */ +class WorkManagerMigration : Migration { + override val version: Float = 62f + + override suspend fun invoke(migrationContext: MigrationContext): Boolean { + val context: App = migrationContext.get() ?: return false + LibraryPresenter.updateDB() + if (BuildConfig.INCLUDE_UPDATER) { + AppUpdateJob.setupTask(context) + } + LibraryUpdateJob.setupTask(context) + BackupCreatorJob.setupTask(context) + ExtensionUpdateJob.setupTask(context) + return true + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 543510a6d1..b987a461de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -38,8 +38,12 @@ import dev.yokai.core.CrashlyticsLogWriter import dev.yokai.core.di.AppModule import dev.yokai.core.di.DomainModule import dev.yokai.core.di.PreferenceModule +import dev.yokai.core.migration.Migrator +import dev.yokai.core.migration.migrations.migrations import dev.yokai.domain.base.BasePreferences import eu.kanade.tachiyomi.appwidget.TachiyomiWidgetManager +import eu.kanade.tachiyomi.core.preference.Preference +import eu.kanade.tachiyomi.core.preference.PreferenceStore import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher import eu.kanade.tachiyomi.data.coil.CoilDiskCache import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher @@ -154,6 +158,34 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F } } .launchIn(ProcessLifecycleOwner.get().lifecycleScope) + + initializeMigrator() + } + + private fun initializeMigrator() { + val preferenceStore = Injekt.get() + + val preference = preferenceStore.getInt( + Preference.appStateKey("last_version_code"), + 0, + ) + // TODO: Remove later + val old = preferenceStore.getInt("last_version_code", -1) + if (old.get() >= preference.get()) { + preference.set(old.get()) + old.delete() + } + + Logger.i { "Migration from ${preference.get()} to ${BuildConfig.VERSION_CODE}" } + Migrator.initialize( + old = preference.get(), + new = BuildConfig.VERSION_CODE, + migrations = migrations, + onMigrationComplete = { + Logger.i { "Updating last version to ${BuildConfig.VERSION_CODE}" } + preference.set(BuildConfig.VERSION_CODE) + }, + ) } override fun onPause(owner: LifecycleOwner) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt deleted file mode 100644 index 4baed80fc8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ /dev/null @@ -1,342 +0,0 @@ -package eu.kanade.tachiyomi - -import androidx.core.content.edit -import androidx.preference.PreferenceManager -import co.touchlab.kermit.Logger -import dev.yokai.domain.base.BasePreferences -import dev.yokai.domain.extension.repo.ExtensionRepoRepository -import dev.yokai.domain.extension.repo.exception.SaveExtensionRepoException -import dev.yokai.domain.ui.settings.ReaderPreferences -import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour -import dev.yokai.domain.ui.settings.ReaderPreferences.LandscapeCutoutBehaviour -import eu.kanade.tachiyomi.core.preference.Preference -import eu.kanade.tachiyomi.core.preference.PreferenceStore -import eu.kanade.tachiyomi.core.preference.plusAssign -import eu.kanade.tachiyomi.data.backup.create.BackupCreatorJob -import eu.kanade.tachiyomi.data.download.DownloadProvider -import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.PreferenceKeys -import eu.kanade.tachiyomi.data.preference.PreferenceValues -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.track.TrackManager -import eu.kanade.tachiyomi.data.updater.AppDownloadInstallJob -import eu.kanade.tachiyomi.data.updater.AppUpdateJob -import eu.kanade.tachiyomi.extension.ExtensionUpdateJob -import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE -import eu.kanade.tachiyomi.ui.library.LibraryPresenter -import eu.kanade.tachiyomi.ui.library.LibrarySort -import eu.kanade.tachiyomi.ui.reader.settings.OrientationType -import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig -import eu.kanade.tachiyomi.ui.recents.RecentsPresenter -import eu.kanade.tachiyomi.util.system.launchIO -import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.io.File -import kotlin.math.max - -object Migrations { - - /** - * Performs a migration when the application is updated. - * - * @param preferences Preferences of the application. - * @return true if a migration is performed, false otherwise. - */ - fun upgrade( - preferences: PreferencesHelper, - preferenceStore: PreferenceStore, - scope: CoroutineScope, - ): Boolean { - val context = preferences.context - val prefs = PreferenceManager.getDefaultSharedPreferences(context) - prefs.edit { - remove(AppDownloadInstallJob.NOTIFY_ON_INSTALL_KEY) - } - val oldVersion = preferences.lastVersionCode().get() - if (oldVersion < BuildConfig.VERSION_CODE) { - preferences.lastVersionCode().set(BuildConfig.VERSION_CODE) - - // Always set up background tasks to ensure they're running - if (BuildConfig.INCLUDE_UPDATER) { - AppUpdateJob.setupTask(context) - } - ExtensionUpdateJob.setupTask(context) - LibraryUpdateJob.setupTask(context) - BackupCreatorJob.setupTask(context) - - if (oldVersion == 0) { - return BuildConfig.DEBUG - } - - if (oldVersion < 14) { - // Restore jobs after upgrading to evernote's job scheduler. - if (BuildConfig.INCLUDE_UPDATER) { - AppUpdateJob.setupTask(context) - } - LibraryUpdateJob.setupTask(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 < 54) { - DownloadProvider(context).renameChapters() - } - if (oldVersion < 62) { - LibraryPresenter.updateDB() - // Restore jobs after migrating from Evernote's job scheduler to WorkManager. - if (BuildConfig.INCLUDE_UPDATER) { - AppUpdateJob.setupTask(context) - } - LibraryUpdateJob.setupTask(context) - BackupCreatorJob.setupTask(context) - ExtensionUpdateJob.setupTask(context) - } - if (oldVersion < 66) { - LibraryPresenter.updateCustoms() - } - if (oldVersion < 68) { - // Force MAL log out due to login flow change - // v67: switched from scraping to WebView - // v68: switched from WebView to OAuth - val trackManager = Injekt.get() - if (trackManager.myAnimeList.isLogged) { - trackManager.myAnimeList.logout() - context.toast(R.string.myanimelist_relogin) - } - } - if (oldVersion < 71) { - // Migrate DNS over HTTPS setting - val wasDohEnabled = prefs.getBoolean("enable_doh", false) - if (wasDohEnabled) { - prefs.edit { - putInt(PreferenceKeys.dohProvider, PREF_DOH_CLOUDFLARE) - remove("enable_doh") - } - } - } - if (oldVersion < 73) { - // Reset rotation to Free after replacing Lock - if (prefs.contains("pref_rotation_type_key")) { - prefs.edit { - putInt("pref_rotation_type_key", 1) - } - } - } - if (oldVersion < 74) { - // Turn on auto updates for all users - if (BuildConfig.INCLUDE_UPDATER) { - AppUpdateJob.setupTask(context) - } - } - if (oldVersion < 75) { - val wasShortcutsDisabled = !prefs.getBoolean("show_manga_app_shortcuts", true) - if (wasShortcutsDisabled) { - prefs.edit { - putBoolean(PreferenceKeys.showSourcesInShortcuts, false) - putBoolean(PreferenceKeys.showSeriesInShortcuts, false) - remove("show_manga_app_shortcuts") - } - } - // Handle removed every 1 or 2 hour library updates - val updateInterval = preferences.libraryUpdateInterval().get() - if (updateInterval == 1 || updateInterval == 2) { - preferences.libraryUpdateInterval().set(3) - LibraryUpdateJob.setupTask(context, 3) - } - } - if (oldVersion < 77) { - // Migrate Rotation and Viewer values to default values for viewer_flags - val newOrientation = when (prefs.getInt("pref_rotation_type_key", 1)) { - 1 -> OrientationType.FREE.flagValue - 2 -> OrientationType.PORTRAIT.flagValue - 3 -> OrientationType.LANDSCAPE.flagValue - 4 -> OrientationType.LOCKED_PORTRAIT.flagValue - 5 -> OrientationType.LOCKED_LANDSCAPE.flagValue - else -> OrientationType.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 < 83) { - if (preferences.enabledLanguages().isSet()) { - preferences.enabledLanguages() += "all" - } - } - if (oldVersion < 86) { - // Handle removed every 3, 4, 6, and 8 hour library updates - val updateInterval = preferences.libraryUpdateInterval().get() - if (updateInterval in listOf(3, 4, 6, 8)) { - preferences.libraryUpdateInterval().set(12) - LibraryUpdateJob.setupTask(context, 12) - } - } - if (oldVersion < 88) { - scope.launchIO { - LibraryPresenter.updateRatiosAndColors() - } - val oldReaderTap = prefs.getBoolean("reader_tap", true) - if (!oldReaderTap) { - preferences.navigationModePager().set(5) - preferences.navigationModeWebtoon().set(5) - } - } - if (oldVersion < 90) { - val oldSecureScreen = prefs.getBoolean("secure_screen", false) - if (oldSecureScreen) { - preferences.secureScreen().set(PreferenceValues.SecureScreenMode.ALWAYS) - } - } - if (oldVersion < 97) { - val oldDLAfterReading = prefs.getInt("auto_download_after_reading", 0) - if (oldDLAfterReading > 0) { - preferences.autoDownloadWhileReading().set(max(2, oldDLAfterReading)) - } - } - if (oldVersion < 102) { - val oldGroupHistory = prefs.getBoolean("group_chapters_history", true) - if (!oldGroupHistory) { - preferences.groupChaptersHistory().set(RecentsPresenter.GroupType.Never) - } - } - if (oldVersion < 105) { - LibraryUpdateJob.cancelAllWorks(context) - LibraryUpdateJob.setupTask(context) - } - if (oldVersion < 108) { - preferenceStore.getAll() - .filter { it.key.startsWith("pref_mangasync_") || it.key.startsWith("track_token_") } - .forEach { (key, value) -> - if (value is String) { - preferenceStore - .getString(Preference.privateKey(key)) - .set(value) - - preferenceStore.getString(key).delete() - } - } - } - if (oldVersion < 110) { - try { - val librarySortString = prefs.getString("library_sorting_mode", "") - if (!librarySortString.isNullOrEmpty()) { - prefs.edit { - remove("library_sorting_mode") - putInt( - "library_sorting_mode", - LibrarySort.deserialize(librarySortString).mainValue, - ) - } - } - } catch (_: Exception) { - } - } - if (oldVersion < 112) { - prefs.edit { - remove("trusted_signatures") - } - } - if (oldVersion < 119) { - val basePreferences: BasePreferences = Injekt.get() - try { - val oldExtensionInstall = prefs.getInt("extension_installer", 0) - basePreferences.extensionInstaller().set( - when (oldExtensionInstall) { - 1 -> BasePreferences.ExtensionInstaller.SHIZUKU - 2 -> BasePreferences.ExtensionInstaller.PRIVATE - else -> BasePreferences.ExtensionInstaller.PACKAGEINSTALLER - } - ) - } catch (_: Exception) { - basePreferences.extensionInstaller().set(BasePreferences.ExtensionInstaller.PACKAGEINSTALLER) - } - } - if (oldVersion < 121) { - val readerPreferences: ReaderPreferences = Injekt.get() - try { - val oldCutoutBehaviour = prefs.getInt(PreferenceKeys.pagerCutoutBehavior, 0) - readerPreferences.pagerCutoutBehavior().set( - when (oldCutoutBehaviour) { - PagerConfig.CUTOUT_PAD -> CutoutBehaviour.HIDE - PagerConfig.CUTOUT_IGNORE -> CutoutBehaviour.IGNORE - else -> CutoutBehaviour.SHOW - } - ) - } catch (_: Exception) { - readerPreferences.pagerCutoutBehavior().set(CutoutBehaviour.SHOW) - } - - try { - val oldCutoutBehaviour = prefs.getInt("landscape_cutout_behavior", 0) - readerPreferences.landscapeCutoutBehavior().set( - when (oldCutoutBehaviour) { - 0 -> LandscapeCutoutBehaviour.HIDE - else -> LandscapeCutoutBehaviour.DEFAULT - } - ) - } catch (_: Exception) { - readerPreferences.landscapeCutoutBehavior().set(LandscapeCutoutBehaviour.DEFAULT) - } - } - if (oldVersion < 130) { - val coroutineScope = CoroutineScope(Dispatchers.IO) - val extensionRepoRepository: ExtensionRepoRepository by injectLazy() - val extensionRepos: Preference> = preferenceStore.getStringSet("extension_repos", emptySet()) - - coroutineScope.launchIO { - for ((index, source) in extensionRepos.get().withIndex()) { - try { - extensionRepoRepository.upsertRepository( - source, - "Repo #${index + 1}", - null, - source, - "NOFINGERPRINT-${index + 1}", - ) - } catch (e: SaveExtensionRepoException) { - Logger.e(e) { "Error Migrating Extension Repo with baseUrl: $source" } - } - } - extensionRepos.delete() - } - } - - return true - } - return false - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt index bd8caeb36b..deb9db4962 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionUpdateJob.kt @@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.extension.api.ExtensionApi import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.util.system.connectivityManager +import eu.kanade.tachiyomi.util.system.jobIsRunning import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.toInt @@ -38,7 +39,7 @@ import rikka.shizuku.Shizuku import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import java.util.concurrent.TimeUnit +import java.util.concurrent.* class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { @@ -179,6 +180,7 @@ class ExtensionUpdateJob(private val context: Context, workerParams: WorkerParam fun setupTask(context: Context, forceAutoUpdateJob: Boolean? = null) { val preferences = Injekt.get() val autoUpdateJob = forceAutoUpdateJob ?: preferences.automaticExtUpdates().get() + WorkManager.getInstance(context).jobIsRunning(TAG) if (autoUpdateJob) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index 6a20fcfdf8..2bf412774f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -67,11 +67,11 @@ import com.google.android.material.snackbar.Snackbar import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback import com.google.common.primitives.Floats.max import com.google.common.primitives.Ints.max +import dev.yokai.core.migration.Migrator import dev.yokai.domain.base.BasePreferences import dev.yokai.presentation.extension.repo.ExtensionRepoController import dev.yokai.presentation.onboarding.OnboardingController import eu.kanade.tachiyomi.BuildConfig -import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.download.DownloadJob @@ -142,9 +142,22 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import kotlin.collections.List +import kotlin.collections.MutableList +import kotlin.collections.MutableMap +import kotlin.collections.distinct +import kotlin.collections.filterNotNull +import kotlin.collections.firstOrNull +import kotlin.collections.forEach +import kotlin.collections.forEachIndexed +import kotlin.collections.lastOrNull +import kotlin.collections.listOf +import kotlin.collections.map +import kotlin.collections.maxByOrNull +import kotlin.collections.orEmpty +import kotlin.collections.plus +import kotlin.collections.set import kotlin.math.abs import kotlin.math.min import kotlin.math.roundToLong @@ -442,8 +455,10 @@ open class MainActivity : BaseActivity() { // Reset Incognito Mode on relaunch preferences.incognitoMode().set(false) + val didMigration = Migrator.awaitAndRelease() + // Show changelog if needed - if (Migrations.upgrade(preferences, Injekt.get(), lifecycleScope)) { + if (didMigration) { if (!BuildConfig.DEBUG) { content.post { whatsNewSheet().show()