Table for Track Searches (#833)

* Table for Track Searches

* Lint
This commit is contained in:
Mitchell Syer
2024-01-20 23:12:18 -05:00
committed by GitHub
parent 621468a183
commit 46e1e4c043
13 changed files with 228 additions and 92 deletions

View File

@@ -14,7 +14,6 @@ import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.track.Track
import suwayomi.tachidesk.manga.model.dataclass.TrackSearchDataClass
import suwayomi.tachidesk.manga.model.dataclass.TrackerDataClass
import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.util.handler
@@ -104,17 +103,15 @@ object TrackController {
val bind =
handler(
queryParam<Int>("mangaId"),
queryParam<Int>("trackSearchId"),
documentWith = {
withOperation {
summary("Track Record Bind")
description("Bind a Track Record to a Manga")
}
body<TrackSearchDataClass>()
},
behaviorOf = { ctx, mangaId ->
val input = json.decodeFromString<TrackSearchDataClass>(ctx.body())
logger.debug { "tracker bind $input" }
ctx.future(future { Track.bind(mangaId, input) })
behaviorOf = { ctx, mangaId, trackSearchId ->
ctx.future(future { Track.bind(mangaId, trackSearchId) })
},
withResults = {
httpCode(HttpCode.OK)

View File

@@ -24,6 +24,8 @@ import suwayomi.tachidesk.manga.model.dataclass.TrackSearchDataClass
import suwayomi.tachidesk.manga.model.dataclass.TrackerDataClass
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
import suwayomi.tachidesk.manga.model.table.TrackSearchTable
import suwayomi.tachidesk.manga.model.table.insertAll
object Track {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
@@ -66,7 +68,7 @@ object Track {
transaction {
TrackRecordTable.select { TrackRecordTable.mangaId eq mangaId }
.map { it.toTrackRecordDataClass() }
}.associateBy { it.syncId }
}.associateBy { it.trackerId }
val trackers = TrackerManager.services
return trackers
@@ -95,29 +97,42 @@ object Track {
suspend fun search(input: SearchInput): List<TrackSearchDataClass> {
val tracker = TrackerManager.getTracker(input.trackerId)!!
val list = tracker.search(input.title)
return list.map {
return list.insertAll().map {
TrackSearchDataClass(
syncId = it.sync_id,
mediaId = it.media_id,
title = it.title,
totalChapters = it.total_chapters,
trackingUrl = it.tracking_url,
coverUrl = it.cover_url,
summary = it.summary,
publishingStatus = it.publishing_status,
publishingType = it.publishing_type,
startDate = it.start_date,
id = it[TrackSearchTable.id].value,
trackerId = it[TrackSearchTable.trackerId],
remoteId = it[TrackSearchTable.remoteId],
title = it[TrackSearchTable.title],
totalChapters = it[TrackSearchTable.totalChapters],
trackingUrl = it[TrackSearchTable.trackingUrl],
coverUrl = it[TrackSearchTable.coverUrl],
summary = it[TrackSearchTable.summary],
publishingStatus = it[TrackSearchTable.publishingStatus],
publishingType = it[TrackSearchTable.publishingType],
startDate = it[TrackSearchTable.startDate],
)
}
}
private fun ResultRow.toTrack(mangaId: Int): Track =
Track.create(this[TrackSearchTable.trackerId]).also {
it.manga_id = mangaId
it.media_id = this[TrackSearchTable.remoteId]
it.title = this[TrackSearchTable.title]
it.total_chapters = this[TrackSearchTable.totalChapters]
it.tracking_url = this[TrackSearchTable.trackingUrl]
}
suspend fun bind(
mangaId: Int,
input: TrackSearchDataClass,
trackSearchId: Int,
) {
val tracker = TrackerManager.getTracker(input.syncId)!!
val track = input.toTrack(mangaId)
val track =
transaction {
TrackSearchTable.select { TrackSearchTable.id eq trackSearchId }.first()
.toTrack(mangaId)
}
val tracker = TrackerManager.getTracker(track.sync_id)!!
val chapter = queryMaxReadChapter(mangaId)
val hasReadChapters = chapter != null
@@ -168,7 +183,7 @@ object Track {
TrackRecordTable.select { TrackRecordTable.id eq input.recordId }.first()
}
val tracker = TrackerManager.getTracker(recordDb[TrackRecordTable.syncId])!!
val tracker = TrackerManager.getTracker(recordDb[TrackRecordTable.trackerId])!!
if (input.status != null) {
recordDb[TrackRecordTable.status] = input.status
@@ -250,7 +265,7 @@ object Track {
}
records.forEach {
val tracker = TrackerManager.getTracker(it[TrackRecordTable.syncId])
val tracker = TrackerManager.getTracker(it[TrackRecordTable.trackerId])
val lastChapterRead = it[TrackRecordTable.lastChapterRead]
val isLogin = tracker?.isLoggedIn == true
logger.debug {
@@ -271,14 +286,14 @@ object Track {
val existingRecord =
TrackRecordTable.select {
(TrackRecordTable.mangaId eq track.manga_id) and
(TrackRecordTable.syncId eq track.sync_id)
(TrackRecordTable.trackerId eq track.sync_id)
}
.singleOrNull()
if (existingRecord != null) {
TrackRecordTable.update({
(TrackRecordTable.mangaId eq track.manga_id) and
(TrackRecordTable.syncId eq track.sync_id)
(TrackRecordTable.trackerId eq track.sync_id)
}) {
it[remoteId] = track.media_id
it[libraryId] = track.library_id
@@ -295,7 +310,7 @@ object Track {
} else {
TrackRecordTable.insertAndGetId {
it[mangaId] = track.manga_id
it[syncId] = track.sync_id
it[trackerId] = track.sync_id
it[remoteId] = track.media_id
it[libraryId] = track.library_id
it[title] = track.title

View File

@@ -58,12 +58,12 @@ class TrackerPreferences {
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
companion object {
fun trackUsername(syncId: Int) = "pref_mangasync_username_$syncId"
fun trackUsername(trackerId: Int) = "pref_mangasync_username_$trackerId"
private fun trackPassword(syncId: Int) = "pref_mangasync_password_$syncId"
private fun trackPassword(trackerId: Int) = "pref_mangasync_password_$trackerId"
private fun trackToken(syncId: Int) = "track_token_$syncId"
private fun trackToken(trackerId: Int) = "track_token_$trackerId"
private fun scoreType(syncId: Int) = "score_type_$syncId"
private fun scoreType(trackerId: Int) = "score_type_$trackerId"
}
}

View File

@@ -2,23 +2,13 @@ package suwayomi.tachidesk.manga.impl.track.tracker.model
import org.jetbrains.exposed.sql.ResultRow
import suwayomi.tachidesk.manga.model.dataclass.TrackRecordDataClass
import suwayomi.tachidesk.manga.model.dataclass.TrackSearchDataClass
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
fun TrackSearchDataClass.toTrack(mangaId: Int): Track =
Track.create(syncId).also {
it.manga_id = mangaId
it.media_id = mediaId
it.title = title
it.total_chapters = totalChapters
it.tracking_url = trackingUrl
}
fun ResultRow.toTrackRecordDataClass(): TrackRecordDataClass =
TrackRecordDataClass(
id = this[TrackRecordTable.id].value,
mangaId = this[TrackRecordTable.mangaId].value,
syncId = this[TrackRecordTable.syncId],
trackerId = this[TrackRecordTable.trackerId],
remoteId = this[TrackRecordTable.remoteId],
libraryId = this[TrackRecordTable.libraryId],
title = this[TrackRecordTable.title],
@@ -32,7 +22,7 @@ fun ResultRow.toTrackRecordDataClass(): TrackRecordDataClass =
)
fun ResultRow.toTrack(): Track =
Track.create(this[TrackRecordTable.syncId]).also {
Track.create(this[TrackRecordTable.trackerId]).also {
it.id = this[TrackRecordTable.id].value
it.manga_id = this[TrackRecordTable.mangaId].value
it.media_id = this[TrackRecordTable.remoteId]

View File

@@ -10,7 +10,7 @@ package suwayomi.tachidesk.manga.model.dataclass
data class TrackRecordDataClass(
val id: Int,
val mangaId: Int,
val syncId: Int,
val trackerId: Int,
val remoteId: Long,
val libraryId: Long?,
val title: String,

View File

@@ -11,8 +11,9 @@ import kotlinx.serialization.Serializable
@Serializable
data class TrackSearchDataClass(
val syncId: Int,
val mediaId: Long,
val id: Int,
val trackerId: Int,
val remoteId: Long,
val title: String,
val totalChapters: Int,
val trackingUrl: String,

View File

@@ -12,7 +12,7 @@ import org.jetbrains.exposed.sql.ReferenceOption
object TrackRecordTable : IntIdTable() {
val mangaId = reference("manga_id", MangaTable, ReferenceOption.CASCADE)
val syncId = integer("sync_id")
val trackerId = integer("sync_id")
val remoteId = long("remote_id")
val libraryId = long("library_id").nullable()
val title = varchar("title", 512)

View File

@@ -0,0 +1,105 @@
package suwayomi.tachidesk.manga.model.table
/*
* 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 org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.batchInsert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.manga.impl.track.tracker.model.TrackSearch
object TrackSearchTable : IntIdTable() {
val trackerId = integer("tracker_id")
val remoteId = long("remote_id")
val title = varchar("title", 512)
val totalChapters = integer("total_chapters")
val trackingUrl = varchar("tracking_url", 512)
val coverUrl = varchar("cover_url", 512)
val summary = varchar("summary", 4096)
val publishingStatus = varchar("publishing_status", 512)
val publishingType = varchar("publishing_type", 512)
val startDate = varchar("start_date", 128)
}
fun List<TrackSearch>.insertAll(): List<ResultRow> {
if (isEmpty()) return emptyList()
return transaction {
val trackerIds = map { it.sync_id }.toSet()
val remoteIds = map { it.media_id }.toSet()
val existing =
transaction {
TrackSearchTable.select {
TrackSearchTable.trackerId inList trackerIds and (TrackSearchTable.remoteId inList remoteIds)
}.toList()
}
val grouped = mutableMapOf<Boolean, MutableList<Pair<Int?, TrackSearch>>>()
forEach { trackSearch ->
val existingRow =
existing.find {
it[TrackSearchTable.trackerId] == trackSearch.sync_id &&
it[TrackSearchTable.remoteId] == trackSearch.media_id
}
grouped.getOrPut(existingRow != null) { mutableListOf() }
.add(existingRow?.get(TrackSearchTable.id)?.value to trackSearch)
}
val toUpdate = grouped[true]
val toInsert = grouped[false]?.map { it.second }
if (!toUpdate.isNullOrEmpty()) {
BatchUpdateStatement(TrackSearchTable).apply {
toUpdate.forEach { (id, trackSearch) ->
id ?: return@forEach
addBatch(EntityID(id, TrackSearchTable))
this[TrackSearchTable.title] = trackSearch.title.take(512)
this[TrackSearchTable.totalChapters] = trackSearch.total_chapters
this[TrackSearchTable.trackingUrl] = trackSearch.tracking_url.take(512)
this[TrackSearchTable.coverUrl] = trackSearch.cover_url.take(512)
this[TrackSearchTable.summary] = trackSearch.summary.take(4096)
this[TrackSearchTable.publishingStatus] = trackSearch.publishing_status.take(512)
this[TrackSearchTable.publishingType] = trackSearch.publishing_type.take(512)
this[TrackSearchTable.startDate] = trackSearch.start_date.take(128)
}
execute(this@transaction)
}
}
val insertedRows =
if (!toInsert.isNullOrEmpty()) {
TrackSearchTable.batchInsert(toInsert) {
this[TrackSearchTable.trackerId] = it.sync_id
this[TrackSearchTable.remoteId] = it.media_id
this[TrackSearchTable.title] = it.title.take(512)
this[TrackSearchTable.totalChapters] = it.total_chapters
this[TrackSearchTable.trackingUrl] = it.tracking_url.take(512)
this[TrackSearchTable.coverUrl] = it.cover_url.take(512)
this[TrackSearchTable.summary] = it.summary.take(4096)
this[TrackSearchTable.publishingStatus] = it.publishing_status.take(512)
this[TrackSearchTable.publishingType] = it.publishing_type.take(512)
this[TrackSearchTable.startDate] = it.start_date.take(128)
}
} else {
emptyList()
}
val updatedRows =
toUpdate?.mapNotNull { it.first }?.let { ids ->
transaction { TrackSearchTable.select { TrackSearchTable.id inList ids }.toList() }
}.orEmpty()
(insertedRows + updatedRows)
.sortedBy { row ->
indexOfFirst {
it.sync_id == row[TrackSearchTable.trackerId] &&
it.media_id == row[TrackSearchTable.remoteId]
}
}
}
}