From ecea2ecdf51dfef73f9f41764602232b201e726b Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:44:38 +0200 Subject: [PATCH] Fix/initial scheduling of global update (#1416) * Fix setting initial global update delay In case no update has run yet, and the "last automated update" defaulted to 0, the calculation always resulted in a multiple of the interval. This resulted for e.g., interval 24, to always be scheduled at 00:00. For e.g., interval 6, it was always one of the following times: 00:00, 06:00, 12:00, 18:00; depending on the current system time. * Delete the existing "last automated update time" So that the update will be triggered based on the time the server got started again after the update. * Extract migrations into separate functions * Cleanup migration execution logic --- .../tachidesk/manga/impl/update/IUpdater.kt | 2 + .../tachidesk/manga/impl/update/Updater.kt | 37 ++++++-- .../suwayomi/tachidesk/server/Migration.kt | 84 +++++++++++++------ 3 files changed, 90 insertions(+), 33 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt index 033c1b22..a7ba6f7d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt @@ -8,6 +8,8 @@ import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass interface IUpdater { fun getLastUpdateTimestamp(): Long + fun deleteLastAutomatedUpdateTimestamp() + fun addCategoriesToUpdateQueue( categories: List, clear: Boolean?, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt index 49ab685f..9f632cb4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt @@ -116,10 +116,24 @@ class Updater : IUpdater { override fun getLastUpdateTimestamp(): Long = preferences.getLong(lastUpdateKey, 0) + fun saveLastUpdateTimestamp() { + preferences.edit().putLong(lastUpdateKey, System.currentTimeMillis()).apply() + } + + fun getLastAutomatedUpdateTimestamp(): Long = preferences.getLong(lastAutomatedUpdateKey, 0) + + fun saveLastAutomatedUpdateTimestamp() { + preferences.edit().putLong(lastAutomatedUpdateKey, System.currentTimeMillis()).apply() + } + + override fun deleteLastAutomatedUpdateTimestamp() { + preferences.edit().remove(lastAutomatedUpdateKey).apply() + } + private fun autoUpdateTask() { try { - val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0) - preferences.edit().putLong(lastAutomatedUpdateKey, System.currentTimeMillis()).apply() + val lastAutomatedUpdate = getLastAutomatedUpdateTimestamp() + saveLastAutomatedUpdateTimestamp() if (getStatus().isRunning) { logger.debug { "Global update is already in progress" } @@ -150,12 +164,23 @@ class Updater : IUpdater { serverConfig.globalUpdateInterval.value.hours .coerceAtLeast(6.hours) .inWholeMilliseconds - val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0) - val timeToNextExecution = (updateInterval - (System.currentTimeMillis() - lastAutomatedUpdate)).mod(updateInterval) + val lastAutomatedUpdate = getLastAutomatedUpdateTimestamp() + val isInitialScheduling = lastAutomatedUpdate == 0L + + val timeToNextExecution = + if (!isInitialScheduling) { + (updateInterval - (System.currentTimeMillis() - lastAutomatedUpdate)).mod(updateInterval) + } else { + updateInterval + } + + if (isInitialScheduling) { + saveLastAutomatedUpdateTimestamp() + } val wasPreviousUpdateTriggered = System.currentTimeMillis() - ( - if (lastAutomatedUpdate > 0) lastAutomatedUpdate else System.currentTimeMillis() + if (!isInitialScheduling) lastAutomatedUpdate else System.currentTimeMillis() ) < updateInterval if (!wasPreviousUpdateTriggered) { GlobalScope.launch { @@ -316,7 +341,7 @@ class Updater : IUpdater { clear: Boolean?, forceAll: Boolean, ) { - preferences.edit().putLong(lastUpdateKey, System.currentTimeMillis()).apply() + saveLastUpdateTimestamp() if (clear == true) { reset() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/Migration.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/Migration.kt index 19253c1a..c21e8c05 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/Migration.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/Migration.kt @@ -3,10 +3,13 @@ package suwayomi.tachidesk.server import android.app.Application import android.content.Context import io.github.oshai.kotlinlogging.KotlinLogging +import suwayomi.tachidesk.manga.impl.update.IUpdater import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.File import java.util.prefs.Preferences +import kotlin.collections.isNotEmpty +import kotlin.collections.orEmpty private fun migratePreferences( parent: String?, @@ -43,9 +46,50 @@ private fun migratePreferences( } } -const val MIGRATION_VERSION = 1 +private fun migratePreferencesToNewXmlFileBasedStorage() { + // Migrate from old preferences api + val prefRootNode = "suwayomi/tachidesk" + val isMigrationRequired = Preferences.userRoot().nodeExists(prefRootNode) + if (isMigrationRequired) { + val preferences = Preferences.userRoot().node(prefRootNode) + migratePreferences(null, preferences) + preferences.removeNode() + } +} + +private fun migrateMangaDownloadDir(applicationDirs: ApplicationDirs) { + val oldMangaDownloadDir = File(applicationDirs.downloadsRoot) + val newMangaDownloadDir = File(applicationDirs.mangaDownloadsRoot) + val downloadDirs = oldMangaDownloadDir.listFiles().orEmpty() + + val moveDownloadsToNewFolder = !newMangaDownloadDir.exists() && downloadDirs.isNotEmpty() + if (moveDownloadsToNewFolder) { + newMangaDownloadDir.mkdirs() + + for (downloadDir in downloadDirs) { + if (downloadDir == File(applicationDirs.thumbnailDownloadsRoot)) { + continue + } + + downloadDir.renameTo(File(newMangaDownloadDir, downloadDir.name)) + } + } +} + +private val MIGRATIONS = + listOf Unit>>( + "InitialMigration" to { applicationDirs -> + migrateMangaDownloadDir(applicationDirs) + migratePreferencesToNewXmlFileBasedStorage() + }, + "FixGlobalUpdateScheduling" to { + Injekt.get().deleteLastAutomatedUpdateTimestamp() + }, + ) fun runMigrations(applicationDirs: ApplicationDirs) { + val logger = KotlinLogging.logger("Migration") + val migrationPreferences = Injekt .get() @@ -54,36 +98,22 @@ fun runMigrations(applicationDirs: ApplicationDirs) { Context.MODE_PRIVATE, ) val version = migrationPreferences.getInt("version", 0) - val logger = KotlinLogging.logger("Migration") - logger.info { "Running migrations, previous version $version, target version $MIGRATION_VERSION" } - if (version < 1) { - logger.info { "Running migration for version: 1" } - val oldMangaDownloadDir = File(applicationDirs.downloadsRoot) - val newMangaDownloadDir = File(applicationDirs.mangaDownloadsRoot) - val downloadDirs = oldMangaDownloadDir.listFiles().orEmpty() + logger.info { "Running migrations, previous version $version, target version ${MIGRATIONS.size}" } - val moveDownloadsToNewFolder = !newMangaDownloadDir.exists() && downloadDirs.isNotEmpty() - if (moveDownloadsToNewFolder) { - newMangaDownloadDir.mkdirs() + MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) -> + val migrationVersion = index + 1 - for (downloadDir in downloadDirs) { - if (downloadDir == File(applicationDirs.thumbnailDownloadsRoot)) { - continue - } - - downloadDir.renameTo(File(newMangaDownloadDir, downloadDir.name)) - } + val isMigrationRequired = version < migrationVersion + if (!isMigrationRequired) { + logger.info { "Skipping migration version $migrationVersion: $migrationName" } + return@forEachIndexed } - // Migrate from old preferences api - val prefRootNode = "suwayomi/tachidesk" - val isMigrationRequired = Preferences.userRoot().nodeExists(prefRootNode) - if (isMigrationRequired) { - val preferences = Preferences.userRoot().node(prefRootNode) - migratePreferences(null, preferences) - preferences.removeNode() - } + logger.info { "Running migration version $migrationVersion: $migrationName" } + + migrationFunction(applicationDirs) + + migrationPreferences.edit().putInt("version", migrationVersion).apply() } - migrationPreferences.edit().putInt("version", MIGRATION_VERSION).apply() }