Dataclass cleanup and minor fixes (#2115)

This commit is contained in:
Mitchell Syer
2026-06-15 14:32:09 -04:00
committed by GitHub
parent bab58daecc
commit 934459f15f
18 changed files with 120 additions and 145 deletions

View File

@@ -21,7 +21,7 @@ object CefHelper {
}
fun waitForInit() =
callbackFlow<CefApp> {
callbackFlow {
val app = cefApp.first { it.isFailure || it.getOrThrow() != null }.getOrThrow()!!
app.onInitialization {
logger.debug { "CEF: Initialization state $it" }

View File

@@ -15,16 +15,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import org.jetbrains.exposed.v1.core.SortOrder
import suwayomi.tachidesk.graphql.types.AuthMode

View File

@@ -377,7 +377,7 @@ object SyncYomiSyncService {
// Use version number to decide which chapter to keep
val chosenChapter =
if (localChapter.version >= remoteChapter.version) {
// If there mare more chapter on remote, local sourceOrder will need to be updated to maintain correct source order.
// If there are more chapter on remote, local sourceOrder will need to be updated to maintain correct source order.
if (localChapters.size < remoteChapters.size) {
localChapter.copy(sourceOrder = remoteChapter.sourceOrder)
} else {

View File

@@ -117,12 +117,14 @@ object CategoryManga {
val transform: (ResultRow) -> MangaDataClass = {
// Map the data from the result row to the MangaDataClass
val dataClass = MangaTable.toDataClass(it)
dataClass.lastReadAt = it[lastReadAt]
dataClass.unreadCount = it[unreadCount]
dataClass.downloadCount = it[downloadedCount]
dataClass.chapterCount = it[chapterCount]
dataClass
MangaTable
.toDataClass(it)
.copy(
lastReadAt = it[lastReadAt],
unreadCount = it[unreadCount],
downloadCount = it[downloadedCount],
chapterCount = it[chapterCount],
)
}
return transaction {

View File

@@ -104,9 +104,6 @@ object Chapter {
.associateBy({ it[ChapterTable.url] }, { it })
}
val chapterIds = chapterList.map { dbChapterMap.getValue(it.url)[ChapterTable.id] }
val chapterMetas = getChaptersMetaMaps(chapterIds.map { it.value })
return chapterList.mapIndexed { index, it ->
val dbChapter = dbChapterMap.getValue(it.url)
@@ -128,10 +125,8 @@ object Chapter {
realUrl = dbChapter[ChapterTable.realUrl],
downloaded = dbChapter[ChapterTable.isDownloaded],
pageCount = dbChapter[ChapterTable.pageCount],
chapterCount = chapterList.size,
lastModifiedAt = dbChapter[ChapterTable.lastModifiedAt],
version = dbChapter[ChapterTable.version],
meta = chapterMetas.getValue(dbChapter[ChapterTable.id].value),
)
}
}
@@ -618,7 +613,7 @@ object Chapter {
.withDefault { emptyMap() }
}
fun getChapterMetaMap(chapter: EntityID<Int>): Map<String, String> =
fun getChapterMetaMap(chapter: Int): Map<String, String> =
transaction {
ChapterMetaTable
.selectAll()

View File

@@ -92,7 +92,6 @@ object Manga {
inLibrary = mangaEntry[MangaTable.inLibrary],
inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
source = getSource(mangaEntry[MangaTable.sourceReference]),
meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl],
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
@@ -213,12 +212,12 @@ object Manga {
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
.firstOrNull { it[ChapterTable.isRead] }
mangaDaaClass.unreadCount = unreadCount
mangaDaaClass.downloadCount = downloadCount
mangaDaaClass.chapterCount = chapterCount
mangaDaaClass.lastChapterRead = lastChapterRead?.let { ChapterTable.toDataClass(it) }
mangaDaaClass
mangaDaaClass.copy(
unreadCount = unreadCount,
downloadCount = downloadCount,
chapterCount = chapterCount,
lastChapterRead = lastChapterRead?.let { ChapterTable.toDataClass(it) },
)
}
}
@@ -241,7 +240,6 @@ object Manga {
inLibrary = mangaEntry[MangaTable.inLibrary],
inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
source = getSource(mangaEntry[MangaTable.sourceReference]),
meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl],
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],

View File

@@ -1,6 +1,7 @@
package suwayomi.tachidesk.manga.model.dataclass
import com.fasterxml.jackson.annotation.JsonValue
import suwayomi.tachidesk.manga.impl.Category
/*
* Copyright (C) Contributors to the Suwayomi project
@@ -18,7 +19,7 @@ enum class IncludeOrExclude(
;
companion object {
fun fromValue(value: Int) = IncludeOrExclude.values().find { it.value == value } ?: UNSET
fun fromValue(value: Int) = entries.find { it.value == value } ?: UNSET
}
}
@@ -27,11 +28,19 @@ data class CategoryDataClass(
val order: Int,
val name: String,
val default: Boolean,
val size: Int,
val includeInUpdate: IncludeOrExclude,
val includeInDownload: IncludeOrExclude,
val version: Long,
val uid: Long,
val lastModifiedAt: Long,
val meta: Map<String, String> = emptyMap(),
)
) {
@Deprecated("Remove with V1 Api")
val size: Int by lazy {
Category.getCategorySize(id)
}
@Deprecated("Remove with V1 Api")
val meta: Map<String, String> by lazy {
Category.getCategoryMetaMap(id)
}
}

View File

@@ -1,6 +1,11 @@
package suwayomi.tachidesk.manga.model.dataclass
import eu.kanade.tachiyomi.source.model.SChapter
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.Chapter.getChapterMetaMap
import suwayomi.tachidesk.manga.model.table.ChapterTable
/*
* Copyright (C) Contributors to the Suwayomi project
@@ -36,12 +41,8 @@ data class ChapterDataClass(
val downloaded: Boolean,
/** used to construct pages in the front-end */
val pageCount: Int = -1,
/** total chapter count, used to calculate if there's a next and prev chapter */
val chapterCount: Int? = null,
val lastModifiedAt: Long = 0,
val version: Long = 0,
/** used to store client specific values */
val meta: Map<String, String> = emptyMap(),
) {
companion object {
fun fromSChapter(
@@ -70,4 +71,20 @@ data class ChapterDataClass(
downloaded = false,
)
}
@Deprecated("Remove with V1 Api")
val chapterCount: Int by lazy {
transaction {
ChapterTable
.selectAll()
.where { ChapterTable.manga eq mangaId }
.count()
.toInt()
}
}
@Deprecated("Remove with V1 Api")
val meta: Map<String, String> by lazy {
getChapterMetaMap(id)
}
}

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.dataclass
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
import suwayomi.tachidesk.manga.impl.util.lang.trimAll
import suwayomi.tachidesk.manga.model.table.MangaStatus
import java.time.Instant
@@ -28,18 +29,16 @@ data class MangaDataClass(
val inLibrary: Boolean = false,
val inLibraryAt: Long = 0,
val source: SourceDataClass? = null,
/** meta data for clients */
val meta: Map<String, String> = emptyMap(),
val realUrl: String? = null,
var lastFetchedAt: Long? = 0,
var chaptersLastFetchedAt: Long? = 0,
var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
val lastFetchedAt: Long? = 0,
val chaptersLastFetchedAt: Long? = 0,
val updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
val freshData: Boolean = false,
var unreadCount: Long? = null,
var downloadCount: Long? = null,
var chapterCount: Long? = null,
var lastReadAt: Long? = null,
var lastChapterRead: ChapterDataClass? = null,
val unreadCount: Long? = null,
val downloadCount: Long? = null,
val chapterCount: Long? = null,
val lastReadAt: Long? = null,
val lastChapterRead: ChapterDataClass? = null,
val age: Long? = if (lastFetchedAt == null) 0 else Instant.now().epochSecond.minus(lastFetchedAt),
val chaptersAge: Long? = if (chaptersLastFetchedAt == null) null else Instant.now().epochSecond.minus(chaptersLastFetchedAt),
val trackers: List<MangaTrackerDataClass>? = null,
@@ -47,6 +46,11 @@ data class MangaDataClass(
val version: Long = 0,
) {
override fun toString(): String = "\"$title\" (id= $id) (sourceId= $sourceId)"
@Deprecated("Remove with V1 Api")
val meta: Map<String, String> by lazy {
getMangaMetaMap(id)
}
}
data class PagedMangaListDataClass(

View File

@@ -9,7 +9,6 @@ package suwayomi.tachidesk.manga.model.table
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
@@ -28,15 +27,13 @@ object CategoryTable : IntIdTable() {
fun CategoryTable.toDataClass(categoryEntry: ResultRow) =
CategoryDataClass(
categoryEntry[id].value,
categoryEntry[order],
categoryEntry[name],
categoryEntry[isDefault],
Category.getCategorySize(categoryEntry[id].value),
IncludeOrExclude.fromValue(categoryEntry[includeInUpdate]),
IncludeOrExclude.fromValue(categoryEntry[includeInDownload]),
categoryEntry[version],
categoryEntry[uid],
categoryEntry[lastModifiedAt],
Category.getCategoryMetaMap(categoryEntry[id].value),
id = categoryEntry[id].value,
order = categoryEntry[order],
name = categoryEntry[name],
default = categoryEntry[isDefault],
includeInUpdate = IncludeOrExclude.fromValue(categoryEntry[includeInUpdate]),
includeInDownload = IncludeOrExclude.fromValue(categoryEntry[includeInDownload]),
version = categoryEntry[version],
uid = categoryEntry[uid],
lastModifiedAt = categoryEntry[lastModifiedAt],
)

View File

@@ -10,10 +10,6 @@ package suwayomi.tachidesk.manga.model.table
import org.jetbrains.exposed.v1.core.ReferenceOption
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.Chapter.getChapterMetaMap
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.table.columns.truncatingVarchar
@@ -48,45 +44,24 @@ object ChapterTable : IntIdTable() {
val isSyncing = bool("is_syncing").default(false)
}
fun ChapterTable.toDataClass(
chapterEntry: ResultRow,
includeChapterCount: Boolean = true,
includeChapterMeta: Boolean = true,
) = ChapterDataClass(
id = chapterEntry[id].value,
url = chapterEntry[url],
name = chapterEntry[name],
uploadDate = chapterEntry[date_upload],
chapterNumber = chapterEntry[chapter_number],
scanlator = chapterEntry[scanlator],
mangaId = chapterEntry[manga].value,
read = chapterEntry[isRead],
bookmarked = chapterEntry[isBookmarked],
lastPageRead = chapterEntry[lastPageRead],
lastReadAt = chapterEntry[lastReadAt],
index = chapterEntry[sourceOrder],
fetchedAt = chapterEntry[fetchedAt],
realUrl = chapterEntry[realUrl],
downloaded = chapterEntry[isDownloaded],
pageCount = chapterEntry[pageCount],
chapterCount =
if (includeChapterCount) {
transaction {
ChapterTable
.selectAll()
.where { manga eq chapterEntry[manga].value }
.count()
.toInt()
}
} else {
null
},
meta =
if (includeChapterMeta) {
getChapterMetaMap(chapterEntry[id])
} else {
emptyMap()
},
lastModifiedAt = chapterEntry[lastModifiedAt],
version = chapterEntry[version],
)
fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
ChapterDataClass(
id = chapterEntry[id].value,
url = chapterEntry[url],
name = chapterEntry[name],
uploadDate = chapterEntry[date_upload],
chapterNumber = chapterEntry[chapter_number],
scanlator = chapterEntry[scanlator],
mangaId = chapterEntry[manga].value,
read = chapterEntry[isRead],
bookmarked = chapterEntry[isBookmarked],
lastPageRead = chapterEntry[lastPageRead],
lastReadAt = chapterEntry[lastReadAt],
index = chapterEntry[sourceOrder],
fetchedAt = chapterEntry[fetchedAt],
realUrl = chapterEntry[realUrl],
downloaded = chapterEntry[isDownloaded],
pageCount = chapterEntry[pageCount],
lastModifiedAt = chapterEntry[lastModifiedAt],
version = chapterEntry[version],
)

View File

@@ -11,11 +11,9 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
import suwayomi.tachidesk.manga.model.table.MangaStatus.Companion
import suwayomi.tachidesk.manga.model.table.columns.truncatingVarchar
import suwayomi.tachidesk.manga.model.table.columns.unlimitedVarchar
@@ -52,37 +50,29 @@ object MangaTable : IntIdTable() {
val isSyncing = bool("is_syncing").default(false)
}
fun MangaTable.toDataClass(
mangaEntry: ResultRow,
includeMangaMeta: Boolean = true,
) = MangaDataClass(
id = mangaEntry[this.id].value,
sourceId = mangaEntry[sourceReference].toString(),
url = mangaEntry[url],
title = mangaEntry[title],
thumbnailUrl = proxyThumbnailUrl(mangaEntry[this.id].value),
thumbnailUrlLastFetched = mangaEntry[thumbnailUrlLastFetched],
initialized = mangaEntry[initialized],
artist = mangaEntry[artist],
author = mangaEntry[author],
description = mangaEntry[description],
genre = mangaEntry[genre].toGenreList(),
status = Companion.valueOf(mangaEntry[status]).name,
inLibrary = mangaEntry[inLibrary],
inLibraryAt = mangaEntry[inLibraryAt],
meta =
if (includeMangaMeta) {
getMangaMetaMap(mangaEntry[id].value)
} else {
emptyMap()
},
realUrl = mangaEntry[realUrl],
lastFetchedAt = mangaEntry[lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt],
updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy]),
lastModifiedAt = mangaEntry[lastModifiedAt],
version = mangaEntry[version],
)
fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass(
id = mangaEntry[this.id].value,
sourceId = mangaEntry[sourceReference].toString(),
url = mangaEntry[url],
title = mangaEntry[title],
thumbnailUrl = proxyThumbnailUrl(mangaEntry[this.id].value),
thumbnailUrlLastFetched = mangaEntry[thumbnailUrlLastFetched],
initialized = mangaEntry[initialized],
artist = mangaEntry[artist],
author = mangaEntry[author],
description = mangaEntry[description],
genre = mangaEntry[genre].toGenreList(),
status = MangaStatus.valueOf(mangaEntry[status]).name,
inLibrary = mangaEntry[inLibrary],
inLibraryAt = mangaEntry[inLibraryAt],
realUrl = mangaEntry[realUrl],
lastFetchedAt = mangaEntry[lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt],
updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy]),
lastModifiedAt = mangaEntry[lastModifiedAt],
version = mangaEntry[version],
)
enum class MangaStatus(
val value: Int,

View File

@@ -71,11 +71,6 @@ import java.net.Authenticator
import java.net.PasswordAuthentication
import java.security.Security
import java.util.Locale
import kotlin.concurrent.thread
import kotlin.io.path.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div
import kotlin.math.roundToInt
private val logger = KotlinLogging.logger {}

View File

@@ -11,9 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.subscribe
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

View File

@@ -136,7 +136,7 @@ object WebInterfaceManager {
fun getAboutInfo(): AboutWebUI {
val currentVersion = getLocalVersion()
val failedToGetVersion = currentVersion === "r-1"
val failedToGetVersion = currentVersion == "r-1"
if (failedToGetVersion) {
throw Exception("Failed to get current version")
}

View File

@@ -2,14 +2,12 @@ package suwayomi.tachidesk
import android.os.Handler
import android.os.Looper
import android.os.Message
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
import kotlin.text.StringBuilder
class LooperThread : Thread() {
var mHandler: Handler? = null

View File

@@ -46,6 +46,7 @@ class TestUpdater : IUpdater {
TODO("Not yet implemented")
}
@Deprecated("Replaced with updates", replaceWith = ReplaceWith("updates"))
override val status: Flow<UpdateStatus>
get() = TODO("Not yet implemented")
override val updates: Flow<UpdateUpdates>

View File

@@ -28,7 +28,6 @@ import suwayomi.tachidesk.server.serverModule
import suwayomi.tachidesk.server.settings.SettingsRegistry
import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex
import suwayomi.tachidesk.server.util.ConfigTypeRegistration
import suwayomi.tachidesk.server.util.SystemTray
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import xyz.nulldev.androidcompat.AndroidCompatInitializer