mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-03 19:04:39 -05:00
Lint
This commit is contained in:
@@ -60,22 +60,23 @@ object SyncManager {
|
|||||||
serverConfig.syncInterval,
|
serverConfig.syncInterval,
|
||||||
) { enabled, interval -> Pair(enabled, interval) },
|
) { enabled, interval -> Pair(enabled, interval) },
|
||||||
{ (enabled, interval) ->
|
{ (enabled, interval) ->
|
||||||
currentTaskId = if (enabled && interval > 0) {
|
currentTaskId =
|
||||||
val intervalMs = interval.minutes.inWholeMilliseconds
|
if (enabled && interval > 0) {
|
||||||
|
val intervalMs = interval.minutes.inWholeMilliseconds
|
||||||
|
|
||||||
currentTaskId?.let { HAScheduler.deschedule(it) }
|
currentTaskId?.let { HAScheduler.deschedule(it) }
|
||||||
|
|
||||||
HAScheduler.schedule(
|
HAScheduler.schedule(
|
||||||
{
|
{
|
||||||
startSync()
|
startSync()
|
||||||
},
|
},
|
||||||
interval = intervalMs,
|
interval = intervalMs,
|
||||||
delay = intervalMs,
|
delay = intervalMs,
|
||||||
name = "sync",
|
name = "sync",
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ignoreInitialValue = false,
|
ignoreInitialValue = false,
|
||||||
)
|
)
|
||||||
@@ -132,29 +133,32 @@ object SyncManager {
|
|||||||
|
|
||||||
val databaseManga = getAllMangaThatNeedsSync()
|
val databaseManga = getAllMangaThatNeedsSync()
|
||||||
|
|
||||||
val backupFlags = BackupFlags(
|
val backupFlags =
|
||||||
includeManga = serverConfig.syncDataManga.value,
|
BackupFlags(
|
||||||
includeCategories = serverConfig.syncDataCategories.value,
|
includeManga = serverConfig.syncDataManga.value,
|
||||||
includeChapters = serverConfig.syncDataChapters.value,
|
includeCategories = serverConfig.syncDataCategories.value,
|
||||||
includeTracking = serverConfig.syncDataTracking.value,
|
includeChapters = serverConfig.syncDataChapters.value,
|
||||||
includeHistory = serverConfig.syncDataHistory.value,
|
includeTracking = serverConfig.syncDataTracking.value,
|
||||||
includeClientData = false,
|
includeHistory = serverConfig.syncDataHistory.value,
|
||||||
includeServerSettings = false,
|
includeClientData = false,
|
||||||
)
|
includeServerSettings = false,
|
||||||
|
)
|
||||||
|
|
||||||
val backupMangas = BackupMangaHandler.backup(backupFlags)
|
val backupMangas = BackupMangaHandler.backup(backupFlags)
|
||||||
|
|
||||||
val backup = Backup(
|
val backup =
|
||||||
BackupMangaHandler.backup(backupFlags),
|
Backup(
|
||||||
BackupCategoryHandler.backup(backupFlags).filter { it.name != Category.DEFAULT_CATEGORY_NAME },
|
BackupMangaHandler.backup(backupFlags),
|
||||||
BackupSourceHandler.backup(backupMangas, backupFlags),
|
BackupCategoryHandler.backup(backupFlags).filter { it.name != Category.DEFAULT_CATEGORY_NAME },
|
||||||
emptyMap(),
|
BackupSourceHandler.backup(backupMangas, backupFlags),
|
||||||
null,
|
emptyMap(),
|
||||||
)
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
val syncData = SyncData(
|
val syncData =
|
||||||
backup = backup,
|
SyncData(
|
||||||
)
|
backup = backup,
|
||||||
|
)
|
||||||
|
|
||||||
val remoteBackup = SyncYomiSyncService.doSync(syncData)
|
val remoteBackup = SyncYomiSyncService.doSync(syncData)
|
||||||
|
|
||||||
@@ -167,7 +171,8 @@ object SyncManager {
|
|||||||
if (remoteBackup === syncData.backup) {
|
if (remoteBackup === syncData.backup) {
|
||||||
// nothing changed
|
// nothing changed
|
||||||
logger.debug { "Skip restore due to remote was overwrite from local" }
|
logger.debug { "Skip restore due to remote was overwrite from local" }
|
||||||
syncPreferences.edit()
|
syncPreferences
|
||||||
|
.edit()
|
||||||
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
||||||
.apply()
|
.apply()
|
||||||
return
|
return
|
||||||
@@ -181,7 +186,8 @@ object SyncManager {
|
|||||||
// Check if it's first sync based on lastSyncTimestamp
|
// Check if it's first sync based on lastSyncTimestamp
|
||||||
if (syncPreferences.getLong("last_sync_timestamp", 0) == 0L && databaseManga.isNotEmpty()) {
|
if (syncPreferences.getLong("last_sync_timestamp", 0) == 0L && databaseManga.isNotEmpty()) {
|
||||||
// It's first sync no need to restore data. (just update remote data)
|
// It's first sync no need to restore data. (just update remote data)
|
||||||
syncPreferences.edit()
|
syncPreferences
|
||||||
|
.edit()
|
||||||
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
||||||
.apply()
|
.apply()
|
||||||
return
|
return
|
||||||
@@ -190,16 +196,18 @@ object SyncManager {
|
|||||||
val (filteredFavorites, nonFavorites) = filterFavoritesAndNonFavorites(remoteBackup)
|
val (filteredFavorites, nonFavorites) = filterFavoritesAndNonFavorites(remoteBackup)
|
||||||
updateNonFavorites(nonFavorites)
|
updateNonFavorites(nonFavorites)
|
||||||
|
|
||||||
val newSyncData = backup.copy(
|
val newSyncData =
|
||||||
backupManga = filteredFavorites,
|
backup.copy(
|
||||||
backupCategories = remoteBackup.backupCategories,
|
backupManga = filteredFavorites,
|
||||||
backupSources = remoteBackup.backupSources,
|
backupCategories = remoteBackup.backupCategories,
|
||||||
)
|
backupSources = remoteBackup.backupSources,
|
||||||
|
)
|
||||||
|
|
||||||
// It's local sync no need to restore data. (just update remote data)
|
// It's local sync no need to restore data. (just update remote data)
|
||||||
if (filteredFavorites.isEmpty()) {
|
if (filteredFavorites.isEmpty()) {
|
||||||
// update the sync timestamp
|
// update the sync timestamp
|
||||||
syncPreferences.edit()
|
syncPreferences
|
||||||
|
.edit()
|
||||||
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
||||||
.apply()
|
.apply()
|
||||||
return
|
return
|
||||||
@@ -208,48 +216,52 @@ object SyncManager {
|
|||||||
val backupStream = ProtoBuf.encodeToByteArray(Backup.serializer(), newSyncData).inputStream()
|
val backupStream = ProtoBuf.encodeToByteArray(Backup.serializer(), newSyncData).inputStream()
|
||||||
ProtoBackupImport.restore(
|
ProtoBackupImport.restore(
|
||||||
sourceStream = backupStream,
|
sourceStream = backupStream,
|
||||||
flags = BackupFlags(
|
flags =
|
||||||
includeManga = true,
|
BackupFlags(
|
||||||
includeCategories = true,
|
includeManga = true,
|
||||||
includeChapters = true,
|
includeCategories = true,
|
||||||
includeTracking = true,
|
includeChapters = true,
|
||||||
includeHistory = true,
|
includeTracking = true,
|
||||||
includeClientData = false,
|
includeHistory = true,
|
||||||
includeServerSettings = false,
|
includeClientData = false,
|
||||||
),
|
includeServerSettings = false,
|
||||||
|
),
|
||||||
isSync = true,
|
isSync = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// update the sync timestamp
|
// update the sync timestamp
|
||||||
syncPreferences.edit()
|
syncPreferences
|
||||||
|
.edit()
|
||||||
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
.putLong("last_sync_timestamp", Clock.System.now().toEpochMilliseconds())
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAllMangaFromDB(): List<MangaDataClass> {
|
private fun getAllMangaFromDB(): List<MangaDataClass> = transaction { MangaTable.selectAll().map { MangaTable.toDataClass(it) } }
|
||||||
return transaction { MangaTable.selectAll().map { MangaTable.toDataClass(it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getAllMangaThatNeedsSync(): List<MangaDataClass> {
|
private fun getAllMangaThatNeedsSync(): List<MangaDataClass> =
|
||||||
return transaction {
|
transaction {
|
||||||
MangaTable.selectAll().where { MangaTable.inLibrary eq true }.map { MangaTable.toDataClass(it) }
|
MangaTable.selectAll().where { MangaTable.inLibrary eq true }.map { MangaTable.toDataClass(it) }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun isMangaDifferent(localManga: MangaDataClass, remoteManga: BackupManga): Boolean {
|
private fun isMangaDifferent(
|
||||||
val localChapters = transaction {
|
localManga: MangaDataClass,
|
||||||
ChapterTable
|
remoteManga: BackupManga,
|
||||||
.selectAll()
|
): Boolean {
|
||||||
.where { ChapterTable.manga eq localManga.id }
|
val localChapters =
|
||||||
.map { ChapterTable.toDataClass(it) }
|
transaction {
|
||||||
}
|
ChapterTable
|
||||||
val localCategories = transaction {
|
.selectAll()
|
||||||
CategoryMangaTable
|
.where { ChapterTable.manga eq localManga.id }
|
||||||
.innerJoin(CategoryTable)
|
.map { ChapterTable.toDataClass(it) }
|
||||||
.selectAll()
|
}
|
||||||
.where { CategoryMangaTable.manga eq localManga.id }
|
val localCategories =
|
||||||
.map { it[CategoryTable.order] }
|
transaction {
|
||||||
}
|
CategoryMangaTable
|
||||||
|
.innerJoin(CategoryTable)
|
||||||
|
.selectAll()
|
||||||
|
.where { CategoryMangaTable.manga eq localManga.id }
|
||||||
|
.map { it[CategoryTable.order] }
|
||||||
|
}
|
||||||
|
|
||||||
if (areChaptersDifferent(localChapters, remoteManga.chapters)) {
|
if (areChaptersDifferent(localChapters, remoteManga.chapters)) {
|
||||||
return true
|
return true
|
||||||
@@ -293,39 +305,44 @@ object SyncManager {
|
|||||||
val favorites = mutableListOf<BackupManga>()
|
val favorites = mutableListOf<BackupManga>()
|
||||||
val nonFavorites = mutableListOf<BackupManga>()
|
val nonFavorites = mutableListOf<BackupManga>()
|
||||||
|
|
||||||
val elapsedTimeMillis = measureTimeMillis {
|
val elapsedTimeMillis =
|
||||||
val databaseManga = getAllMangaFromDB()
|
measureTimeMillis {
|
||||||
val localMangaMap = databaseManga.associateBy {
|
val databaseManga = getAllMangaFromDB()
|
||||||
Triple(it.sourceId.toLong(), it.url, it.title)
|
val localMangaMap =
|
||||||
}
|
databaseManga.associateBy {
|
||||||
|
Triple(it.sourceId.toLong(), it.url, it.title)
|
||||||
logger.debug { "Starting to filter favorites and non-favorites from backup data." }
|
|
||||||
|
|
||||||
backup.backupManga.forEach { remoteManga ->
|
|
||||||
val compositeKey = Triple(remoteManga.source, remoteManga.url, remoteManga.title)
|
|
||||||
val localManga = localMangaMap[compositeKey]
|
|
||||||
when {
|
|
||||||
// Checks if the manga is in favorites and needs updating or adding
|
|
||||||
remoteManga.favorite -> {
|
|
||||||
if (localManga == null || isMangaDifferent(localManga, remoteManga)) {
|
|
||||||
logger.debug { "Adding to favorites: ${remoteManga.title}" }
|
|
||||||
favorites.add(remoteManga)
|
|
||||||
} else {
|
|
||||||
logger.debug { "Already up-to-date favorite: ${remoteManga.title}" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Handle non-favorites
|
|
||||||
!remoteManga.favorite -> {
|
logger.debug { "Starting to filter favorites and non-favorites from backup data." }
|
||||||
logger.debug { "Adding to non-favorites: ${remoteManga.title}" }
|
|
||||||
nonFavorites.add(remoteManga)
|
backup.backupManga.forEach { remoteManga ->
|
||||||
|
val compositeKey = Triple(remoteManga.source, remoteManga.url, remoteManga.title)
|
||||||
|
val localManga = localMangaMap[compositeKey]
|
||||||
|
when {
|
||||||
|
// Checks if the manga is in favorites and needs updating or adding
|
||||||
|
remoteManga.favorite -> {
|
||||||
|
if (localManga == null || isMangaDifferent(localManga, remoteManga)) {
|
||||||
|
logger.debug { "Adding to favorites: ${remoteManga.title}" }
|
||||||
|
favorites.add(remoteManga)
|
||||||
|
} else {
|
||||||
|
logger.debug { "Already up-to-date favorite: ${remoteManga.title}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-favorites
|
||||||
|
!remoteManga.favorite -> {
|
||||||
|
logger.debug { "Adding to non-favorites: ${remoteManga.title}" }
|
||||||
|
nonFavorites.add(remoteManga)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val minutes = elapsedTimeMillis / 60000
|
val minutes = elapsedTimeMillis / 60000
|
||||||
val seconds = (elapsedTimeMillis % 60000) / 1000
|
val seconds = (elapsedTimeMillis % 60000) / 1000
|
||||||
logger.debug { "Filtering completed in ${minutes}m ${seconds}s. Favorites found: ${favorites.size}, Non-favorites found: ${nonFavorites.size}" }
|
logger.debug {
|
||||||
|
"Filtering completed in ${minutes}m ${seconds}s. Favorites found: ${favorites.size}, Non-favorites found: ${nonFavorites.size}"
|
||||||
|
}
|
||||||
|
|
||||||
return Pair(favorites, nonFavorites)
|
return Pair(favorites, nonFavorites)
|
||||||
}
|
}
|
||||||
@@ -378,4 +395,3 @@ object SyncManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,21 +31,24 @@ object SyncYomiSyncService {
|
|||||||
private val network: NetworkHelper by injectLazy()
|
private val network: NetworkHelper by injectLazy()
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
private class SyncYomiException(message: String?) : Exception(message)
|
private class SyncYomiException(
|
||||||
|
message: String?,
|
||||||
|
) : Exception(message)
|
||||||
|
|
||||||
suspend fun doSync(syncData: SyncData): Backup? {
|
suspend fun doSync(syncData: SyncData): Backup? {
|
||||||
try {
|
try {
|
||||||
val (remoteData, etag) = pullSyncData()
|
val (remoteData, etag) = pullSyncData()
|
||||||
|
|
||||||
val finalSyncData = if (remoteData != null) {
|
val finalSyncData =
|
||||||
require(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
|
if (remoteData != null) {
|
||||||
logger.debug { "Try update remote data with ETag($etag)" }
|
require(etag.isNotEmpty()) { "ETag should never be empty if remote data is not null" }
|
||||||
mergeSyncData(syncData, remoteData)
|
logger.debug { "Try update remote data with ETag($etag)" }
|
||||||
} else {
|
mergeSyncData(syncData, remoteData)
|
||||||
// init or overwrite remote data
|
} else {
|
||||||
logger.debug { "Try overwrite remote data with ETag($etag)" }
|
// init or overwrite remote data
|
||||||
syncData
|
logger.debug { "Try overwrite remote data with ETag($etag)" }
|
||||||
}
|
syncData
|
||||||
|
}
|
||||||
|
|
||||||
pushSyncData(finalSyncData, etag)
|
pushSyncData(finalSyncData, etag)
|
||||||
return finalSyncData.backup
|
return finalSyncData.backup
|
||||||
@@ -67,10 +70,11 @@ object SyncYomiSyncService {
|
|||||||
}
|
}
|
||||||
val headers = headersBuilder.build()
|
val headers = headersBuilder.build()
|
||||||
|
|
||||||
val downloadRequest = GET(
|
val downloadRequest =
|
||||||
url = downloadUrl,
|
GET(
|
||||||
headers = headers,
|
url = downloadUrl,
|
||||||
)
|
headers = headers,
|
||||||
|
)
|
||||||
|
|
||||||
val response = network.client.newCall(downloadRequest).await()
|
val response = network.client.newCall(downloadRequest).await()
|
||||||
|
|
||||||
@@ -85,12 +89,14 @@ object SyncYomiSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val newETag = response.headers["ETag"]
|
val newETag =
|
||||||
?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag")
|
response.headers["ETag"]
|
||||||
|
?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag")
|
||||||
|
|
||||||
val byteArray = response.body.byteStream().use {
|
val byteArray =
|
||||||
return@use it.readBytes()
|
response.body.byteStream().use {
|
||||||
}
|
return@use it.readBytes()
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val backup = ProtoBuf.decodeFromByteArray(Backup.serializer(), byteArray)
|
val backup = ProtoBuf.decodeFromByteArray(Backup.serializer(), byteArray)
|
||||||
@@ -108,7 +114,10 @@ object SyncYomiSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun pushSyncData(syncData: SyncData, eTag: String) {
|
private suspend fun pushSyncData(
|
||||||
|
syncData: SyncData,
|
||||||
|
eTag: String,
|
||||||
|
) {
|
||||||
val backup = syncData.backup ?: return
|
val backup = syncData.backup ?: return
|
||||||
|
|
||||||
val host = serverConfig.syncYomiHost.value
|
val host = serverConfig.syncYomiHost.value
|
||||||
@@ -123,11 +132,13 @@ object SyncYomiSyncService {
|
|||||||
val headers = headersBuilder.build()
|
val headers = headersBuilder.build()
|
||||||
|
|
||||||
// Set timeout to 30 seconds
|
// Set timeout to 30 seconds
|
||||||
val client = network.client.newBuilder()
|
val client =
|
||||||
.connectTimeout(timeout, TimeUnit.SECONDS)
|
network.client
|
||||||
.readTimeout(timeout, TimeUnit.SECONDS)
|
.newBuilder()
|
||||||
.writeTimeout(timeout, TimeUnit.SECONDS)
|
.connectTimeout(timeout, TimeUnit.SECONDS)
|
||||||
.build()
|
.readTimeout(timeout, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(timeout, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
val byteArray = ProtoBuf.encodeToByteArray(Backup.serializer(), backup)
|
val byteArray = ProtoBuf.encodeToByteArray(Backup.serializer(), backup)
|
||||||
if (byteArray.isEmpty()) {
|
if (byteArray.isEmpty()) {
|
||||||
@@ -135,18 +146,21 @@ object SyncYomiSyncService {
|
|||||||
}
|
}
|
||||||
val body = byteArray.toRequestBody("application/octet-stream".toMediaType())
|
val body = byteArray.toRequestBody("application/octet-stream".toMediaType())
|
||||||
|
|
||||||
val uploadRequest = PUT(
|
val uploadRequest =
|
||||||
url = uploadUrl,
|
PUT(
|
||||||
headers = headers,
|
url = uploadUrl,
|
||||||
body = body,
|
headers = headers,
|
||||||
)
|
body = body,
|
||||||
|
)
|
||||||
|
|
||||||
val response = client.newCall(uploadRequest).await()
|
val response = client.newCall(uploadRequest).await()
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val newETag = response.headers["ETag"]
|
val newETag =
|
||||||
?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag")
|
response.headers["ETag"]
|
||||||
syncPreferences.edit()
|
?.takeIf { it.isNotEmpty() } ?: throw SyncYomiException("Missing ETag")
|
||||||
|
syncPreferences
|
||||||
|
.edit()
|
||||||
.putString("last_sync_etag", newETag)
|
.putString("last_sync_etag", newETag)
|
||||||
.apply()
|
.apply()
|
||||||
logger.debug { "SyncYomi sync completed" }
|
logger.debug { "SyncYomi sync completed" }
|
||||||
@@ -159,29 +173,34 @@ object SyncYomiSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mergeSyncData(localSyncData: SyncData, remoteSyncData: SyncData): SyncData {
|
fun mergeSyncData(
|
||||||
|
localSyncData: SyncData,
|
||||||
|
remoteSyncData: SyncData,
|
||||||
|
): SyncData {
|
||||||
val mergedCategoriesList =
|
val mergedCategoriesList =
|
||||||
mergeCategoriesLists(localSyncData.backup?.backupCategories, remoteSyncData.backup?.backupCategories)
|
mergeCategoriesLists(localSyncData.backup?.backupCategories, remoteSyncData.backup?.backupCategories)
|
||||||
|
|
||||||
val mergedMangaList = mergeMangaLists(
|
val mergedMangaList =
|
||||||
localSyncData.backup?.backupManga,
|
mergeMangaLists(
|
||||||
remoteSyncData.backup?.backupManga,
|
localSyncData.backup?.backupManga,
|
||||||
localSyncData.backup?.backupCategories ?: emptyList(),
|
remoteSyncData.backup?.backupManga,
|
||||||
remoteSyncData.backup?.backupCategories ?: emptyList(),
|
localSyncData.backup?.backupCategories ?: emptyList(),
|
||||||
mergedCategoriesList,
|
remoteSyncData.backup?.backupCategories ?: emptyList(),
|
||||||
)
|
mergedCategoriesList,
|
||||||
|
)
|
||||||
|
|
||||||
val mergedSourcesList =
|
val mergedSourcesList =
|
||||||
mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources)
|
mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources)
|
||||||
|
|
||||||
// Create the merged Backup object
|
// Create the merged Backup object
|
||||||
val mergedBackup = Backup(
|
val mergedBackup =
|
||||||
backupManga = mergedMangaList,
|
Backup(
|
||||||
backupCategories = mergedCategoriesList,
|
backupManga = mergedMangaList,
|
||||||
backupSources = mergedSourcesList,
|
backupCategories = mergedCategoriesList,
|
||||||
meta = emptyMap(),
|
backupSources = mergedSourcesList,
|
||||||
serverSettings = null,
|
meta = emptyMap(),
|
||||||
)
|
serverSettings = null,
|
||||||
|
)
|
||||||
|
|
||||||
// Create the merged SData object
|
// Create the merged SData object
|
||||||
return SyncData(
|
return SyncData(
|
||||||
@@ -201,9 +220,8 @@ object SyncYomiSyncService {
|
|||||||
|
|
||||||
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
||||||
|
|
||||||
fun mangaCompositeKey(manga: BackupManga): String {
|
fun mangaCompositeKey(manga: BackupManga): String =
|
||||||
return "${manga.source}|${manga.url}|${manga.title.lowercase().trim()}|${manga.author?.lowercase()?.trim()}"
|
"${manga.source}|${manga.url}|${manga.title.lowercase().trim()}|${manga.author?.lowercase()?.trim()}"
|
||||||
}
|
|
||||||
|
|
||||||
// Create maps using composite keys
|
// Create maps using composite keys
|
||||||
val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) }
|
val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) }
|
||||||
@@ -213,51 +231,65 @@ object SyncYomiSyncService {
|
|||||||
val remoteCategoriesMapByOrder = remoteCategories.associateBy { it.order }
|
val remoteCategoriesMapByOrder = remoteCategories.associateBy { it.order }
|
||||||
val mergedCategoriesMapByName = mergedCategories.associateBy { it.name }
|
val mergedCategoriesMapByName = mergedCategories.associateBy { it.name }
|
||||||
|
|
||||||
fun updateCategories(theManga: BackupManga, theMap: Map<Int, BackupCategory>): BackupManga {
|
fun updateCategories(
|
||||||
return theManga.copy(
|
theManga: BackupManga,
|
||||||
categories = theManga.categories.mapNotNull {
|
theMap: Map<Int, BackupCategory>,
|
||||||
theMap[it]?.let { category ->
|
): BackupManga =
|
||||||
mergedCategoriesMapByName[category.name]?.order
|
theManga.copy(
|
||||||
}
|
categories =
|
||||||
},
|
theManga.categories.mapNotNull {
|
||||||
|
theMap[it]?.let { category ->
|
||||||
|
mergedCategoriesMapByName[category.name]?.order
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
||||||
|
|
||||||
val mergedList = (localMangaMap.keys + remoteMangaMap.keys).distinct().mapNotNull { compositeKey ->
|
val mergedList =
|
||||||
val local = localMangaMap[compositeKey]
|
(localMangaMap.keys + remoteMangaMap.keys).distinct().mapNotNull { compositeKey ->
|
||||||
val remote = remoteMangaMap[compositeKey]
|
val local = localMangaMap[compositeKey]
|
||||||
|
val remote = remoteMangaMap[compositeKey]
|
||||||
|
|
||||||
// New version comparison logic
|
// New version comparison logic
|
||||||
when {
|
when {
|
||||||
local != null && remote == null -> updateCategories(local, localCategoriesMapByOrder)
|
local != null && remote == null -> {
|
||||||
local == null && remote != null -> updateCategories(remote, remoteCategoriesMapByOrder)
|
updateCategories(local, localCategoriesMapByOrder)
|
||||||
local != null && remote != null -> {
|
|
||||||
// Compare versions to decide which manga to keep
|
|
||||||
if (local.version >= remote.version) {
|
|
||||||
logger.debug { "Keeping local version of ${local.title} with merged chapters." }
|
|
||||||
updateCategories(
|
|
||||||
local.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
|
|
||||||
localCategoriesMapByOrder,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
logger.debug { "Keeping remote version of ${remote.title} with merged chapters." }
|
|
||||||
updateCategories(
|
|
||||||
remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
|
|
||||||
remoteCategoriesMapByOrder,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
else -> null // No manga found for key
|
local == null && remote != null -> {
|
||||||
|
updateCategories(remote, remoteCategoriesMapByOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
local != null && remote != null -> {
|
||||||
|
// Compare versions to decide which manga to keep
|
||||||
|
if (local.version >= remote.version) {
|
||||||
|
logger.debug { "Keeping local version of ${local.title} with merged chapters." }
|
||||||
|
updateCategories(
|
||||||
|
local.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
|
||||||
|
localCategoriesMapByOrder,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug { "Keeping remote version of ${remote.title} with merged chapters." }
|
||||||
|
updateCategories(
|
||||||
|
remote.copy(chapters = mergeChapters(local.chapters, remote.chapters)),
|
||||||
|
remoteCategoriesMapByOrder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
} // No manga found for key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Counting favorites and non-favorites
|
// Counting favorites and non-favorites
|
||||||
val (favorites, nonFavorites) = mergedList.partition { it.favorite }
|
val (favorites, nonFavorites) = mergedList.partition { it.favorite }
|
||||||
|
|
||||||
logger.debug { "Merge completed. Total merged manga: ${mergedList.size}, Favorites: ${favorites.size}, Non-Favorites: ${nonFavorites.size}" }
|
logger.debug {
|
||||||
|
"Merge completed. Total merged manga: ${mergedList.size}, Favorites: ${favorites.size}, Non-Favorites: ${nonFavorites.size}"
|
||||||
|
}
|
||||||
|
|
||||||
return mergedList
|
return mergedList
|
||||||
}
|
}
|
||||||
@@ -266,9 +298,7 @@ object SyncYomiSyncService {
|
|||||||
localChapters: List<BackupChapter>,
|
localChapters: List<BackupChapter>,
|
||||||
remoteChapters: List<BackupChapter>,
|
remoteChapters: List<BackupChapter>,
|
||||||
): List<BackupChapter> {
|
): List<BackupChapter> {
|
||||||
fun chapterCompositeKey(chapter: BackupChapter): String {
|
fun chapterCompositeKey(chapter: BackupChapter): String = "${chapter.url}|${chapter.name}|${chapter.chapterNumber}"
|
||||||
return "${chapter.url}|${chapter.name}|${chapter.chapterNumber}"
|
|
||||||
}
|
|
||||||
|
|
||||||
val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) }
|
val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) }
|
||||||
val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) }
|
val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) }
|
||||||
@@ -276,45 +306,51 @@ object SyncYomiSyncService {
|
|||||||
logger.debug { "Starting chapter merge. Local chapters: ${localChapters.size}, Remote chapters: ${remoteChapters.size}" }
|
logger.debug { "Starting chapter merge. Local chapters: ${localChapters.size}, Remote chapters: ${remoteChapters.size}" }
|
||||||
|
|
||||||
// Merge both chapter maps based on version numbers
|
// Merge both chapter maps based on version numbers
|
||||||
val mergedChapters = (localChapterMap.keys + remoteChapterMap.keys).distinct().mapNotNull { compositeKey ->
|
val mergedChapters =
|
||||||
val localChapter = localChapterMap[compositeKey]
|
(localChapterMap.keys + remoteChapterMap.keys).distinct().mapNotNull { compositeKey ->
|
||||||
val remoteChapter = remoteChapterMap[compositeKey]
|
val localChapter = localChapterMap[compositeKey]
|
||||||
|
val remoteChapter = remoteChapterMap[compositeKey]
|
||||||
|
|
||||||
logger.debug { "Processing chapter key: $compositeKey. Local chapter: ${localChapter != null}, Remote chapter: ${remoteChapter != null}" }
|
logger.debug {
|
||||||
|
"Processing chapter key: $compositeKey. Local chapter: ${localChapter != null}, Remote chapter: ${remoteChapter != null}"
|
||||||
when {
|
|
||||||
localChapter != null && remoteChapter == null -> {
|
|
||||||
logger.debug { "Keeping local chapter: ${localChapter.name}." }
|
|
||||||
localChapter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localChapter == null && remoteChapter != null -> {
|
when {
|
||||||
logger.debug { "Taking remote chapter: ${remoteChapter.name}." }
|
localChapter != null && remoteChapter == null -> {
|
||||||
remoteChapter
|
logger.debug { "Keeping local chapter: ${localChapter.name}." }
|
||||||
}
|
localChapter
|
||||||
|
}
|
||||||
|
|
||||||
localChapter != null && remoteChapter != null -> {
|
localChapter == null && remoteChapter != null -> {
|
||||||
// Use version number to decide which chapter to keep
|
logger.debug { "Taking remote chapter: ${remoteChapter.name}." }
|
||||||
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 (localChapters.size < remoteChapters.size) {
|
|
||||||
localChapter.copy(sourceOrder = remoteChapter.sourceOrder)
|
|
||||||
} else {
|
|
||||||
localChapter
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
remoteChapter
|
remoteChapter
|
||||||
}
|
}
|
||||||
logger.debug { "Merging chapter: ${chosenChapter.name}. Chosen version from: ${if (localChapter.version >= remoteChapter.version) "Local" else "Remote"}, Local version: ${localChapter.version}, Remote version: ${remoteChapter.version}." }
|
|
||||||
chosenChapter
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
localChapter != null && remoteChapter != null -> {
|
||||||
logger.debug { "No chapter found for composite key: $compositeKey. Skipping." }
|
// Use version number to decide which chapter to keep
|
||||||
null
|
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 (localChapters.size < remoteChapters.size) {
|
||||||
|
localChapter.copy(sourceOrder = remoteChapter.sourceOrder)
|
||||||
|
} else {
|
||||||
|
localChapter
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remoteChapter
|
||||||
|
}
|
||||||
|
logger.debug {
|
||||||
|
"Merging chapter: ${chosenChapter.name}. Chosen version from: ${if (localChapter.version >= remoteChapter.version) "Local" else "Remote"}, Local version: ${localChapter.version}, Remote version: ${remoteChapter.version}."
|
||||||
|
}
|
||||||
|
chosenChapter
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
logger.debug { "No chapter found for composite key: $compositeKey. Skipping." }
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug { "Chapter merge completed. Total merged chapters: ${mergedChapters.size}" }
|
logger.debug { "Chapter merge completed. Total merged chapters: ${mergedChapters.size}" }
|
||||||
|
|
||||||
@@ -336,11 +372,12 @@ object SyncYomiSyncService {
|
|||||||
val remoteCategory = remoteCategoriesMap[name]
|
val remoteCategory = remoteCategoriesMap[name]
|
||||||
if (remoteCategory != null) {
|
if (remoteCategory != null) {
|
||||||
// Compare and merge local and remote categories
|
// Compare and merge local and remote categories
|
||||||
val mergedCategory = if (localCategory.order > remoteCategory.order) {
|
val mergedCategory =
|
||||||
localCategory
|
if (localCategory.order > remoteCategory.order) {
|
||||||
} else {
|
localCategory
|
||||||
remoteCategory
|
} else {
|
||||||
}
|
remoteCategory
|
||||||
|
}
|
||||||
mergedCategoriesMap[name] = mergedCategory
|
mergedCategoriesMap[name] = mergedCategory
|
||||||
} else {
|
} else {
|
||||||
// If the category is only in the local list, add it to the merged list
|
// If the category is only in the local list, add it to the merged list
|
||||||
@@ -369,29 +406,32 @@ object SyncYomiSyncService {
|
|||||||
logger.debug { "Starting source merge. Local sources: ${localSources?.size}, Remote sources: ${remoteSources?.size}" }
|
logger.debug { "Starting source merge. Local sources: ${localSources?.size}, Remote sources: ${remoteSources?.size}" }
|
||||||
|
|
||||||
// Merge both source maps
|
// Merge both source maps
|
||||||
val mergedSources = (localSourceMap.keys + remoteSourceMap.keys).distinct().mapNotNull { sourceId ->
|
val mergedSources =
|
||||||
val localSource = localSourceMap[sourceId]
|
(localSourceMap.keys + remoteSourceMap.keys).distinct().mapNotNull { sourceId ->
|
||||||
val remoteSource = remoteSourceMap[sourceId]
|
val localSource = localSourceMap[sourceId]
|
||||||
|
val remoteSource = remoteSourceMap[sourceId]
|
||||||
|
|
||||||
logger.debug { "Processing source ID: $sourceId. Local source: ${localSource != null}, Remote source: ${remoteSource != null}" }
|
logger.debug {
|
||||||
|
"Processing source ID: $sourceId. Local source: ${localSource != null}, Remote source: ${remoteSource != null}"
|
||||||
when {
|
|
||||||
localSource != null && remoteSource == null -> {
|
|
||||||
logger.debug { "Using local source: ${localSource.name}." }
|
|
||||||
localSource
|
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteSource != null && localSource == null -> {
|
when {
|
||||||
logger.debug { "Using remote source: ${remoteSource.name}." }
|
localSource != null && remoteSource == null -> {
|
||||||
remoteSource
|
logger.debug { "Using local source: ${localSource.name}." }
|
||||||
}
|
localSource
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
remoteSource != null && localSource == null -> {
|
||||||
logger.debug { "Remote and local is not empty: $sourceId. Skipping." }
|
logger.debug { "Using remote source: ${remoteSource.name}." }
|
||||||
null
|
remoteSource
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
logger.debug { "Remote and local is not empty: $sourceId. Skipping." }
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug { "Source merge completed. Total merged sources: ${mergedSources.size}" }
|
logger.debug { "Source merge completed. Total merged sources: ${mergedSources.size}" }
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("ktlint:standard:filename")
|
||||||
|
|
||||||
package suwayomi.tachidesk.graphql.types
|
package suwayomi.tachidesk.graphql.types
|
||||||
|
|
||||||
enum class StartSyncResult {
|
enum class StartSyncResult {
|
||||||
|
|||||||
@@ -165,8 +165,7 @@ object ProtoBackupImport : ProtoBackupBase() {
|
|||||||
.source()
|
.source()
|
||||||
.run {
|
.run {
|
||||||
if (!isSync) gzip() else this
|
if (!isSync) gzip() else this
|
||||||
}
|
}.buffer()
|
||||||
.buffer()
|
|
||||||
.use { it.readByteArray() }
|
.use { it.readByteArray() }
|
||||||
val backup = parser.decodeFromByteArray(Backup.serializer(), backupString)
|
val backup = parser.decodeFromByteArray(Backup.serializer(), backupString)
|
||||||
|
|
||||||
@@ -246,10 +245,10 @@ object ProtoBackupImport : ProtoBackupBase() {
|
|||||||
|
|
||||||
if (isSync) {
|
if (isSync) {
|
||||||
transaction {
|
transaction {
|
||||||
MangaTable.update({ MangaTable.isSyncing eq true}) {
|
MangaTable.update({ MangaTable.isSyncing eq true }) {
|
||||||
it[isSyncing] = false
|
it[isSyncing] = false
|
||||||
}
|
}
|
||||||
ChapterTable.update({ ChapterTable.isSyncing eq true}) {
|
ChapterTable.update({ ChapterTable.isSyncing eq true }) {
|
||||||
it[isSyncing] = false
|
it[isSyncing] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ object BackupMangaHandler {
|
|||||||
it.uploadDate,
|
it.uploadDate,
|
||||||
it.chapterNumber,
|
it.chapterNumber,
|
||||||
chapters.size - it.index,
|
chapters.size - it.index,
|
||||||
it.version
|
it.version,
|
||||||
).apply {
|
).apply {
|
||||||
if (flags.includeClientData) {
|
if (flags.includeClientData) {
|
||||||
this.meta = chapterToMeta[it.id] ?: emptyMap()
|
this.meta = chapterToMeta[it.id] ?: emptyMap()
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import suwayomi.tachidesk.server.serverConfig
|
|||||||
|
|
||||||
@Suppress("ClassName", "unused")
|
@Suppress("ClassName", "unused")
|
||||||
class M0053_SyncYomi : SQLMigration() {
|
class M0053_SyncYomi : SQLMigration() {
|
||||||
override val sql = when (serverConfig.databaseType.value) {
|
override val sql =
|
||||||
DatabaseType.POSTGRESQL -> postgresQuery()
|
when (serverConfig.databaseType.value) {
|
||||||
DatabaseType.H2 -> h2Query()
|
DatabaseType.POSTGRESQL -> postgresQuery()
|
||||||
}
|
DatabaseType.H2 -> h2Query()
|
||||||
|
}
|
||||||
|
|
||||||
fun postgresQuery(): String = """
|
fun postgresQuery(): String =
|
||||||
|
"""
|
||||||
ALTER TABLE manga ADD COLUMN version BIGINT DEFAULT 0;
|
ALTER TABLE manga ADD COLUMN version BIGINT DEFAULT 0;
|
||||||
ALTER TABLE manga ADD COLUMN is_syncing BOOLEAN DEFAULT FALSE;
|
ALTER TABLE manga ADD COLUMN is_syncing BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
@@ -75,9 +77,10 @@ class M0053_SyncYomi : SQLMigration() {
|
|||||||
AFTER INSERT ON categorymanga
|
AFTER INSERT ON categorymanga
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION insert_manga_category_update_version();
|
EXECUTE FUNCTION insert_manga_category_update_version();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
fun h2Query() = """
|
fun h2Query() =
|
||||||
|
"""
|
||||||
ALTER TABLE manga ADD COLUMN version BIGINT DEFAULT 0;
|
ALTER TABLE manga ADD COLUMN version BIGINT DEFAULT 0;
|
||||||
ALTER TABLE manga ADD COLUMN is_syncing BOOLEAN DEFAULT FALSE;
|
ALTER TABLE manga ADD COLUMN is_syncing BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
@@ -98,5 +101,5 @@ class M0053_SyncYomi : SQLMigration() {
|
|||||||
AFTER INSERT ON categorymanga
|
AFTER INSERT ON categorymanga
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
CALL "suwayomi.tachidesk.server.database.trigger.InsertMangaCategoryUpdateVersionTrigger";
|
CALL "suwayomi.tachidesk.server.database.trigger.InsertMangaCategoryUpdateVersionTrigger";
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,21 @@ class UpdateMangaVersionTrigger : TriggerAdapter() {
|
|||||||
newRow: ResultSet,
|
newRow: ResultSet,
|
||||||
) {
|
) {
|
||||||
val isSyncing = newRow.getBoolean("is_syncing")
|
val isSyncing = newRow.getBoolean("is_syncing")
|
||||||
val hasChanged = oldRow.getString("url") != newRow.getString("url") ||
|
val hasChanged =
|
||||||
oldRow.getString("description") != newRow.getString("description") ||
|
oldRow.getString("url") != newRow.getString("url") ||
|
||||||
oldRow.getBoolean("in_library") != newRow.getBoolean("in_library")
|
oldRow.getString("description") != newRow.getString("description") ||
|
||||||
|
oldRow.getBoolean("in_library") != newRow.getBoolean("in_library")
|
||||||
|
|
||||||
if (!isSyncing && hasChanged) {
|
if (!isSyncing && hasChanged) {
|
||||||
val id = newRow.getInt("id")
|
val id = newRow.getInt("id")
|
||||||
|
|
||||||
conn.prepareStatement(
|
conn
|
||||||
"UPDATE MANGA SET version = version + 1 WHERE id = ?",
|
.prepareStatement(
|
||||||
).use {
|
"UPDATE MANGA SET version = version + 1 WHERE id = ?",
|
||||||
it.setInt(1, id)
|
).use {
|
||||||
it.executeUpdate()
|
it.setInt(1, id)
|
||||||
}
|
it.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,28 +39,31 @@ class UpdateChapterAndMangaVersionTrigger : TriggerAdapter() {
|
|||||||
newRow: ResultSet,
|
newRow: ResultSet,
|
||||||
) {
|
) {
|
||||||
val isSyncing = newRow.getBoolean("is_syncing")
|
val isSyncing = newRow.getBoolean("is_syncing")
|
||||||
val hasChanged = oldRow.getBoolean("read") != newRow.getBoolean("read") ||
|
val hasChanged =
|
||||||
oldRow.getBoolean("bookmark") != newRow.getBoolean("bookmark") ||
|
oldRow.getBoolean("read") != newRow.getBoolean("read") ||
|
||||||
oldRow.getInt("last_page_read") != newRow.getInt("last_page_read")
|
oldRow.getBoolean("bookmark") != newRow.getBoolean("bookmark") ||
|
||||||
|
oldRow.getInt("last_page_read") != newRow.getInt("last_page_read")
|
||||||
|
|
||||||
if (!isSyncing && hasChanged) {
|
if (!isSyncing && hasChanged) {
|
||||||
val chapterId = newRow.getInt("id")
|
val chapterId = newRow.getInt("id")
|
||||||
val mangaId = newRow.getInt("manga")
|
val mangaId = newRow.getInt("manga")
|
||||||
|
|
||||||
conn.prepareStatement(
|
conn
|
||||||
"UPDATE CHAPTER SET version = version + 1 WHERE id = ?",
|
.prepareStatement(
|
||||||
).use {
|
"UPDATE CHAPTER SET version = version + 1 WHERE id = ?",
|
||||||
it.setInt(1, chapterId)
|
).use {
|
||||||
it.executeUpdate()
|
it.setInt(1, chapterId)
|
||||||
}
|
it.executeUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
conn.prepareStatement(
|
conn
|
||||||
"UPDATE MANGA SET version = version + 1 WHERE id = ? AND (SELECT is_syncing FROM MANGA WHERE id = ?) = FALSE",
|
.prepareStatement(
|
||||||
).use {
|
"UPDATE MANGA SET version = version + 1 WHERE id = ? AND (SELECT is_syncing FROM MANGA WHERE id = ?) = FALSE",
|
||||||
it.setInt(1, mangaId)
|
).use {
|
||||||
it.setInt(2, mangaId)
|
it.setInt(1, mangaId)
|
||||||
it.executeUpdate()
|
it.setInt(2, mangaId)
|
||||||
}
|
it.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,13 +77,13 @@ class InsertMangaCategoryUpdateVersionTrigger : TriggerAdapter() {
|
|||||||
) {
|
) {
|
||||||
val mangaId = newRow.getInt("manga")
|
val mangaId = newRow.getInt("manga")
|
||||||
|
|
||||||
conn.prepareStatement(
|
conn
|
||||||
"UPDATE MANGA SET version = version + 1 WHERE id = ? AND (SELECT is_syncing FROM MANGA WHERE id = ?) = FALSE",
|
.prepareStatement(
|
||||||
).use {
|
"UPDATE MANGA SET version = version + 1 WHERE id = ? AND (SELECT is_syncing FROM MANGA WHERE id = ?) = FALSE",
|
||||||
it.setInt(1, mangaId)
|
).use {
|
||||||
it.setInt(2, mangaId)
|
it.setInt(1, mangaId)
|
||||||
it.executeUpdate()
|
it.setInt(2, mangaId)
|
||||||
}
|
it.executeUpdate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user