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
This commit is contained in:
schroda
2025-06-12 17:44:38 +02:00
committed by GitHub
parent f04060b31b
commit ecea2ecdf5
3 changed files with 90 additions and 33 deletions

View File

@@ -8,6 +8,8 @@ import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
interface IUpdater { interface IUpdater {
fun getLastUpdateTimestamp(): Long fun getLastUpdateTimestamp(): Long
fun deleteLastAutomatedUpdateTimestamp()
fun addCategoriesToUpdateQueue( fun addCategoriesToUpdateQueue(
categories: List<CategoryDataClass>, categories: List<CategoryDataClass>,
clear: Boolean?, clear: Boolean?,

View File

@@ -116,10 +116,24 @@ class Updater : IUpdater {
override fun getLastUpdateTimestamp(): Long = preferences.getLong(lastUpdateKey, 0) 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() { private fun autoUpdateTask() {
try { try {
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0) val lastAutomatedUpdate = getLastAutomatedUpdateTimestamp()
preferences.edit().putLong(lastAutomatedUpdateKey, System.currentTimeMillis()).apply() saveLastAutomatedUpdateTimestamp()
if (getStatus().isRunning) { if (getStatus().isRunning) {
logger.debug { "Global update is already in progress" } logger.debug { "Global update is already in progress" }
@@ -150,12 +164,23 @@ class Updater : IUpdater {
serverConfig.globalUpdateInterval.value.hours serverConfig.globalUpdateInterval.value.hours
.coerceAtLeast(6.hours) .coerceAtLeast(6.hours)
.inWholeMilliseconds .inWholeMilliseconds
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0) val lastAutomatedUpdate = getLastAutomatedUpdateTimestamp()
val timeToNextExecution = (updateInterval - (System.currentTimeMillis() - lastAutomatedUpdate)).mod(updateInterval) val isInitialScheduling = lastAutomatedUpdate == 0L
val timeToNextExecution =
if (!isInitialScheduling) {
(updateInterval - (System.currentTimeMillis() - lastAutomatedUpdate)).mod(updateInterval)
} else {
updateInterval
}
if (isInitialScheduling) {
saveLastAutomatedUpdateTimestamp()
}
val wasPreviousUpdateTriggered = val wasPreviousUpdateTriggered =
System.currentTimeMillis() - ( System.currentTimeMillis() - (
if (lastAutomatedUpdate > 0) lastAutomatedUpdate else System.currentTimeMillis() if (!isInitialScheduling) lastAutomatedUpdate else System.currentTimeMillis()
) < updateInterval ) < updateInterval
if (!wasPreviousUpdateTriggered) { if (!wasPreviousUpdateTriggered) {
GlobalScope.launch { GlobalScope.launch {
@@ -316,7 +341,7 @@ class Updater : IUpdater {
clear: Boolean?, clear: Boolean?,
forceAll: Boolean, forceAll: Boolean,
) { ) {
preferences.edit().putLong(lastUpdateKey, System.currentTimeMillis()).apply() saveLastUpdateTimestamp()
if (clear == true) { if (clear == true) {
reset() reset()

View File

@@ -3,10 +3,13 @@ package suwayomi.tachidesk.server
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import suwayomi.tachidesk.manga.impl.update.IUpdater
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.util.prefs.Preferences import java.util.prefs.Preferences
import kotlin.collections.isNotEmpty
import kotlin.collections.orEmpty
private fun migratePreferences( private fun migratePreferences(
parent: String?, 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<Pair<String, (ApplicationDirs) -> Unit>>(
"InitialMigration" to { applicationDirs ->
migrateMangaDownloadDir(applicationDirs)
migratePreferencesToNewXmlFileBasedStorage()
},
"FixGlobalUpdateScheduling" to {
Injekt.get<IUpdater>().deleteLastAutomatedUpdateTimestamp()
},
)
fun runMigrations(applicationDirs: ApplicationDirs) { fun runMigrations(applicationDirs: ApplicationDirs) {
val logger = KotlinLogging.logger("Migration")
val migrationPreferences = val migrationPreferences =
Injekt Injekt
.get<Application>() .get<Application>()
@@ -54,36 +98,22 @@ fun runMigrations(applicationDirs: ApplicationDirs) {
Context.MODE_PRIVATE, Context.MODE_PRIVATE,
) )
val version = migrationPreferences.getInt("version", 0) 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 migrations, previous version $version, target version ${MIGRATIONS.size}" }
logger.info { "Running migration for version: 1" }
val oldMangaDownloadDir = File(applicationDirs.downloadsRoot)
val newMangaDownloadDir = File(applicationDirs.mangaDownloadsRoot)
val downloadDirs = oldMangaDownloadDir.listFiles().orEmpty()
val moveDownloadsToNewFolder = !newMangaDownloadDir.exists() && downloadDirs.isNotEmpty() MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) ->
if (moveDownloadsToNewFolder) { val migrationVersion = index + 1
newMangaDownloadDir.mkdirs()
for (downloadDir in downloadDirs) { val isMigrationRequired = version < migrationVersion
if (downloadDir == File(applicationDirs.thumbnailDownloadsRoot)) { if (!isMigrationRequired) {
continue logger.info { "Skipping migration version $migrationVersion: $migrationName" }
} return@forEachIndexed
downloadDir.renameTo(File(newMangaDownloadDir, downloadDir.name))
}
} }
// Migrate from old preferences api logger.info { "Running migration version $migrationVersion: $migrationName" }
val prefRootNode = "suwayomi/tachidesk"
val isMigrationRequired = Preferences.userRoot().nodeExists(prefRootNode) migrationFunction(applicationDirs)
if (isMigrationRequired) {
val preferences = Preferences.userRoot().node(prefRootNode) migrationPreferences.edit().putInt("version", migrationVersion).apply()
migratePreferences(null, preferences)
preferences.removeNode()
}
} }
migrationPreferences.edit().putInt("version", MIGRATION_VERSION).apply()
} }