mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
feat: Refactor merge logic with composite keys and debugging logs
Refactored the mergeMangaLists and mergeChapters functions to use composite keys for enhanced manga and chapter identification. Implemented composite keys incorporating multiple fields (source, url, title, and author for manga; url, name, and chapterNumber for chapters) to ensure a unique and robust matching process. Added detailed debugging logs at each step of the merge to provide insights into the matching process, making it easier to trace and debug issues related to manga and chapter mismatches. These improvements ensure greater accuracy and reliability in identifying and merging manga and chapters across local and remote lists. Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
parent
86a21e2506
commit
35ce19d8f3
@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupChapter
|
|||||||
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
import eu.kanade.tachiyomi.data.backup.models.BackupManga
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import logcat.LogPriority
|
||||||
|
import logcat.logcat
|
||||||
import tachiyomi.domain.sync.SyncPreferences
|
import tachiyomi.domain.sync.SyncPreferences
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -92,37 +94,84 @@ abstract class SyncService(
|
|||||||
localMangaList: List<BackupManga>?,
|
localMangaList: List<BackupManga>?,
|
||||||
remoteMangaList: List<BackupManga>?,
|
remoteMangaList: List<BackupManga>?,
|
||||||
): List<BackupManga> {
|
): List<BackupManga> {
|
||||||
|
val logTag = "MergeMangaLists"
|
||||||
|
|
||||||
// Convert null lists to empty to simplify logic
|
// Convert null lists to empty to simplify logic
|
||||||
val localMangaListSafe = localMangaList.orEmpty()
|
val localMangaListSafe = localMangaList.orEmpty()
|
||||||
val remoteMangaListSafe = remoteMangaList.orEmpty()
|
val remoteMangaListSafe = remoteMangaList.orEmpty()
|
||||||
|
|
||||||
// Associate both local and remote manga by their unique keys (source and url)
|
logcat(logTag, LogPriority.DEBUG) { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
||||||
val localMangaMap = localMangaListSafe.associateBy { Pair(it.source, it.url) }
|
|
||||||
val remoteMangaMap = remoteMangaListSafe.associateBy { Pair(it.source, it.url) }
|
// Define a function to create a composite key from manga
|
||||||
|
fun mangaCompositeKey(manga: BackupManga): String {
|
||||||
|
return "${manga.source}|${manga.url}|${manga.title.lowercase().trim()}|${manga.author?.lowercase()?.trim()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Create maps using composite keys
|
||||||
|
val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) }
|
||||||
|
val remoteMangaMap = remoteMangaListSafe.associateBy { mangaCompositeKey(it) }
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
|
||||||
|
|
||||||
// Prepare to merge both sets of manga
|
// Prepare to merge both sets of manga
|
||||||
return (localMangaMap.keys + remoteMangaMap.keys).mapNotNull { key ->
|
val mergedList = (localMangaMap.keys + remoteMangaMap.keys).distinct().mapNotNull { compositeKey ->
|
||||||
val local = localMangaMap[key]
|
val local = localMangaMap[compositeKey]
|
||||||
val remote = remoteMangaMap[key]
|
val remote = remoteMangaMap[compositeKey]
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Processing key: $compositeKey. Local favorite: ${local?.favorite}, Remote favorite: ${remote?.favorite}"
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
local != null && remote == null -> local
|
local != null && remote == null -> {
|
||||||
local == null && remote != null -> remote
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Taking local manga: ${local.title} as it is not present remotely. Favorite status: ${local.favorite}"
|
||||||
|
}
|
||||||
|
local
|
||||||
|
}
|
||||||
|
local == null && remote != null -> {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Taking remote manga: ${remote.title} as it is not present locally. Favorite status: ${remote.favorite}"
|
||||||
|
}
|
||||||
|
remote
|
||||||
|
}
|
||||||
local != null && remote != null -> {
|
local != null && remote != null -> {
|
||||||
// Compare last modified times and merge chapters
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
val localTime = Instant.ofEpochMilli(local.lastModifiedAt)
|
"Inspecting timestamps for ${local.title}. Local lastModifiedAt: ${local.lastModifiedAt}, Remote lastModifiedAt: ${remote.lastModifiedAt}"
|
||||||
val remoteTime = Instant.ofEpochMilli(remote.lastModifiedAt)
|
}
|
||||||
|
// Convert seconds to milliseconds for accurate time comparison
|
||||||
|
val localTime = Instant.ofEpochMilli(local.lastModifiedAt * 1000L)
|
||||||
|
val remoteTime = Instant.ofEpochMilli(remote.lastModifiedAt * 1000L)
|
||||||
val mergedChapters = mergeChapters(local.chapters, remote.chapters)
|
val mergedChapters = mergeChapters(local.chapters, remote.chapters)
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Merging manga: ${local.title}. Local time: $localTime, Remote time: $remoteTime, Local favorite: ${local.favorite}, Remote favorite: ${remote.favorite}"
|
||||||
|
}
|
||||||
|
|
||||||
if (localTime >= remoteTime) {
|
if (localTime >= remoteTime) {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Keeping local version of ${local.title} with merged chapters." }
|
||||||
local.copy(chapters = mergedChapters)
|
local.copy(chapters = mergedChapters)
|
||||||
} else {
|
} else {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Keeping remote version of ${remote.title} with merged chapters." }
|
||||||
remote.copy(chapters = mergedChapters)
|
remote.copy(chapters = mergedChapters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> null
|
else -> {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "No manga found for key: $compositeKey. Skipping." }
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counting favorites and non-favorites
|
||||||
|
val (favorites, nonFavorites) = mergedList.partition { it.favorite }
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Merge completed. Total merged manga: ${mergedList.size}, Favorites: ${favorites.size}, Non-Favorites: ${nonFavorites.size}"
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedList
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,33 +191,63 @@ abstract class SyncService(
|
|||||||
* - If lastModifiedAt is null or missing, the chapter is considered the oldest for safety, ensuring that any chapter with a valid timestamp is preferred.
|
* - If lastModifiedAt is null or missing, the chapter is considered the oldest for safety, ensuring that any chapter with a valid timestamp is preferred.
|
||||||
* - The resulting list contains the most recent chapters from the combined set of local and remote chapters.
|
* - The resulting list contains the most recent chapters from the combined set of local and remote chapters.
|
||||||
*/
|
*/
|
||||||
private fun mergeChapters(
|
private fun mergeChapters(
|
||||||
localChapters: List<BackupChapter>,
|
localChapters: List<BackupChapter>,
|
||||||
remoteChapters: List<BackupChapter>,
|
remoteChapters: List<BackupChapter>,
|
||||||
): List<BackupChapter> {
|
): List<BackupChapter> {
|
||||||
// Associate chapters by URL for both local and remote
|
val logTag = "MergeChapters"
|
||||||
val localChapterMap = localChapters.associateBy { it.url }
|
|
||||||
val remoteChapterMap = remoteChapters.associateBy { it.url }
|
|
||||||
|
|
||||||
// Merge both chapter maps
|
// Define a function to create a composite key from a chapter
|
||||||
return (localChapterMap.keys + remoteChapterMap.keys).mapNotNull { url ->
|
fun chapterCompositeKey(chapter: BackupChapter): String {
|
||||||
// Determine the most recent chapter by comparing lastModifiedAt, considering null as Instant.MIN
|
return "${chapter.url}|${chapter.name}|${chapter.chapterNumber}"
|
||||||
val localChapter = localChapterMap[url]
|
}
|
||||||
val remoteChapter = remoteChapterMap[url]
|
|
||||||
|
|
||||||
when {
|
// Create maps using composite keys
|
||||||
localChapter != null && remoteChapter == null -> localChapter
|
val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) }
|
||||||
localChapter == null && remoteChapter != null -> remoteChapter
|
val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) }
|
||||||
localChapter != null && remoteChapter != null -> {
|
|
||||||
val localInstant = localChapter.lastModifiedAt.let { Instant.ofEpochMilli(it) } ?: Instant.MIN
|
logcat(LogPriority.DEBUG, logTag) { "Starting chapter merge. Local chapters: ${localChapters.size}, Remote chapters: ${remoteChapters.size}" }
|
||||||
val remoteInstant = remoteChapter.lastModifiedAt.let { Instant.ofEpochMilli(it) } ?: Instant.MIN
|
|
||||||
if (localInstant >= remoteInstant) localChapter else remoteChapter
|
// Merge both chapter maps
|
||||||
|
val mergedChapters = (localChapterMap.keys + remoteChapterMap.keys).distinct().mapNotNull { compositeKey ->
|
||||||
|
val localChapter = localChapterMap[compositeKey]
|
||||||
|
val remoteChapter = remoteChapterMap[compositeKey]
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Processing chapter key: $compositeKey. Local chapter: ${localChapter != null}, Remote chapter: ${remoteChapter != null}"
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
localChapter != null && remoteChapter == null -> {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Keeping local chapter: ${localChapter.name}." }
|
||||||
|
localChapter
|
||||||
|
}
|
||||||
|
localChapter == null && remoteChapter != null -> {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Taking remote chapter: ${remoteChapter.name}." }
|
||||||
|
remoteChapter
|
||||||
|
}
|
||||||
|
localChapter != null && remoteChapter != null -> {
|
||||||
|
val localInstant = Instant.ofEpochMilli(localChapter.lastModifiedAt * 1000L)
|
||||||
|
val remoteInstant = Instant.ofEpochMilli(remoteChapter.lastModifiedAt * 1000L)
|
||||||
|
|
||||||
|
val chosenChapter = if (localInstant >= remoteInstant) localChapter else remoteChapter
|
||||||
|
logcat(LogPriority.DEBUG, logTag) {
|
||||||
|
"Merging chapter: ${chosenChapter.name}. Chosen from: ${if (localInstant >= remoteInstant) "Local" else "Remote"}."
|
||||||
}
|
}
|
||||||
else -> null
|
chosenChapter
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "No chapter found for composite key: $compositeKey. Skipping." }
|
||||||
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logcat(LogPriority.DEBUG, logTag) { "Chapter merge completed. Total merged chapters: ${mergedChapters.size}" }
|
||||||
|
|
||||||
|
return mergedChapters
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges two lists of SyncCategory objects, prioritizing the category with the most recent order value.
|
* Merges two lists of SyncCategory objects, prioritizing the category with the most recent order value.
|
||||||
*
|
*
|
||||||
|
Loading…
Reference in New Issue
Block a user