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 {
fun getLastUpdateTimestamp(): Long
fun deleteLastAutomatedUpdateTimestamp()
fun addCategoriesToUpdateQueue(
categories: List<CategoryDataClass>,
clear: Boolean?,

View File

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

View File

@@ -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<Pair<String, (ApplicationDirs) -> Unit>>(
"InitialMigration" to { applicationDirs ->
migrateMangaDownloadDir(applicationDirs)
migratePreferencesToNewXmlFileBasedStorage()
},
"FixGlobalUpdateScheduling" to {
Injekt.get<IUpdater>().deleteLastAutomatedUpdateTimestamp()
},
)
fun runMigrations(applicationDirs: ApplicationDirs) {
val logger = KotlinLogging.logger("Migration")
val migrationPreferences =
Injekt
.get<Application>()
@@ -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()
}