Files
Suwayomi-Server/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt
schroda 2b767eb488 Fix/local manga thumbnails handling (#1630)
* Skip thumbnail download for local manga sources

Local manga sources do not require downloading thumbnails as they are stored locally.

* Always update local source manga info when browsing

When making changes to an in library local source manga, a refresh from the source was required to get the latest data.
From a user perspective, this is unexpected behavior that looks like a bug.

If, for example, the thumbnail file extension got changed, the file could not be found anymore and an error was shown in the client. To fix this, a manga refresh was required.
2025-09-09 18:14:01 -04:00

104 lines
3.5 KiB
Kotlin

package suwayomi.tachidesk.manga.impl
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.local.LocalSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.manga.impl.Manga.getManga
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
import suwayomi.tachidesk.manga.model.table.CategoryTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import java.time.Instant
object Library {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
suspend fun addMangaToLibrary(mangaId: Int) {
val manga = getManga(mangaId)
if (!manga.inLibrary) {
transaction {
val defaultCategories =
CategoryTable
.selectAll()
.where {
(CategoryTable.isDefault eq true) and
(CategoryTable.id neq Category.DEFAULT_CATEGORY_ID)
}.toList()
val existingCategories = CategoryMangaTable.selectAll().where { CategoryMangaTable.manga eq mangaId }.toList()
MangaTable.update({ MangaTable.id eq manga.id }) {
it[inLibrary] = true
it[inLibraryAt] = Instant.now().epochSecond
}
if (existingCategories.isEmpty()) {
defaultCategories.forEach { category ->
CategoryMangaTable.insert {
it[CategoryMangaTable.category] = category[CategoryTable.id].value
it[CategoryMangaTable.manga] = mangaId
}
}
}
}.apply {
handleMangaThumbnail(mangaId, true)
}
}
}
suspend fun removeMangaFromLibrary(mangaId: Int) {
val manga = getManga(mangaId)
if (manga.inLibrary) {
transaction {
MangaTable.update({ MangaTable.id eq manga.id }) {
it[inLibrary] = false
}
}.apply {
handleMangaThumbnail(mangaId, false)
}
}
}
fun handleMangaThumbnail(
mangaId: Int,
inLibrary: Boolean,
) {
scope.launch {
val sourceId =
transaction {
MangaTable
.select(MangaTable.sourceReference)
.where { MangaTable.id eq mangaId }
.first()
.get(MangaTable.sourceReference)
}
if (sourceId == LocalSource.ID) {
return@launch
}
try {
if (inLibrary) {
ThumbnailDownloadHelper.download(mangaId)
} else {
ThumbnailDownloadHelper.delete(mangaId)
}
} catch (e: Exception) {
// ignore
}
}
}
}