feat(SyncManager): implement timestamp optimization in sync process

Introduce timestamp optimization to sync process by storing the last successful sync timestamp. Now, only records modified after this timestamp are queried, ensuring efficiency by considering only the latest changes.

Import measureTimeMillis for performance measurement, add processFavoriteManga method to refine favorite manga processing, and update various conditions for streamlined sync checks.

Enhance logging for better process visibility and timing accuracy.

Signed-off-by: KaiserBh <kaiserbh@proton.me>
This commit is contained in:
KaiserBh 2024-01-08 06:56:57 +11:00
parent 35ce19d8f3
commit db4ec11262
No known key found for this signature in database
GPG Key ID: 14D73B142042BBA9

View File

@ -31,6 +31,7 @@ import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.Date import java.util.Date
import kotlin.system.measureTimeMillis
/** /**
* A manager to handle synchronization tasks in the app, such as updating * A manager to handle synchronization tasks in the app, such as updating
@ -122,8 +123,10 @@ class SyncManager(
val (filteredFavorites, nonFavorites) = filterFavoritesAndNonFavorites(remoteBackup) val (filteredFavorites, nonFavorites) = filterFavoritesAndNonFavorites(remoteBackup)
updateNonFavorites(nonFavorites) updateNonFavorites(nonFavorites)
val mangas = processFavoriteManga(filteredFavorites)
val newSyncData = backup.copy( val newSyncData = backup.copy(
backupManga = filteredFavorites, backupManga = mangas,
backupCategories = remoteBackup.backupCategories, backupCategories = remoteBackup.backupCategories,
backupSources = remoteBackup.backupSources, backupSources = remoteBackup.backupSources,
backupPreferences = remoteBackup.backupPreferences, backupPreferences = remoteBackup.backupPreferences,
@ -131,7 +134,7 @@ class SyncManager(
) )
// 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 (mangas.isEmpty()) {
// update the sync timestamp // update the sync timestamp
syncPreferences.lastSyncTimestamp().set(Date().time) syncPreferences.lastSyncTimestamp().set(Date().time)
return return
@ -150,6 +153,9 @@ class SyncManager(
library = true, library = true,
), ),
) )
// update the sync timestamp
syncPreferences.lastSyncTimestamp().set(Date().time)
} else { } else {
logcat(LogPriority.ERROR) { "Failed to write sync data to file" } logcat(LogPriority.ERROR) { "Failed to write sync data to file" }
} }
@ -185,10 +191,6 @@ class SyncManager(
return localManga.source != remoteManga.source || return localManga.source != remoteManga.source ||
localManga.url != remoteManga.url || localManga.url != remoteManga.url ||
localManga.title != remoteManga.title || localManga.title != remoteManga.title ||
localManga.artist != remoteManga.artist ||
localManga.author != remoteManga.author ||
localManga.description != remoteManga.description ||
localManga.genre != remoteManga.genre ||
localManga.status.toInt() != remoteManga.status || localManga.status.toInt() != remoteManga.status ||
localManga.thumbnailUrl != remoteManga.thumbnailUrl || localManga.thumbnailUrl != remoteManga.thumbnailUrl ||
localManga.dateAdded != remoteManga.dateAdded || localManga.dateAdded != remoteManga.dateAdded ||
@ -217,15 +219,10 @@ class SyncManager(
val localChapter = localChapterMap[remoteChapter.url] val localChapter = localChapterMap[remoteChapter.url]
localChapter == null || // No corresponding local chapter localChapter == null || // No corresponding local chapter
localChapter.url != remoteChapter.url || localChapter.url != remoteChapter.url ||
localChapter.name != remoteChapter.name ||
localChapter.scanlator != remoteChapter.scanlator ||
localChapter.read != remoteChapter.read || localChapter.read != remoteChapter.read ||
localChapter.bookmark != remoteChapter.bookmark || localChapter.bookmark != remoteChapter.bookmark ||
localChapter.last_page_read != remoteChapter.lastPageRead || localChapter.last_page_read != remoteChapter.lastPageRead ||
localChapter.chapter_number != remoteChapter.chapterNumber || localChapter.chapter_number != remoteChapter.chapterNumber
localChapter.source_order != remoteChapter.sourceOrder ||
localChapter.date_fetch != remoteChapter.dateFetch ||
localChapter.date_upload != remoteChapter.dateUpload
} }
} }
@ -235,26 +232,85 @@ class SyncManager(
* @return a Pair of lists, where the first list contains different favorite manga and the second list contains non-favorite manga. * @return a Pair of lists, where the first list contains different favorite manga and the second list contains non-favorite manga.
*/ */
private suspend fun filterFavoritesAndNonFavorites(backup: Backup): Pair<List<BackupManga>, List<BackupManga>> { private suspend fun filterFavoritesAndNonFavorites(backup: Backup): Pair<List<BackupManga>, List<BackupManga>> {
val databaseMangaFavorites = getFavorites.await()
val localMangaMap = databaseMangaFavorites.associateBy { it.url }
val favorites = mutableListOf<BackupManga>() val favorites = mutableListOf<BackupManga>()
val nonFavorites = mutableListOf<BackupManga>() val nonFavorites = mutableListOf<BackupManga>()
val elapsedTimeMillis = measureTimeMillis {
val databaseMangaFavorites = getFavorites.await()
val localMangaMap = databaseMangaFavorites.associateBy { it.url }
logcat(LogPriority.DEBUG) { "Starting to filter favorites and non-favorites from backup data." }
backup.backupManga.forEach { remoteManga -> backup.backupManga.forEach { remoteManga ->
if (remoteManga.favorite) { val localManga = localMangaMap[remoteManga.url]
localMangaMap[remoteManga.url]?.let { localManga -> when {
if (isMangaDifferent(localManga, remoteManga)) { // Checks if the manga is in favorites and needs updating or adding
remoteManga.favorite -> {
if (localManga == null || isMangaDifferent(localManga, remoteManga)) {
logcat(LogPriority.DEBUG) { "Adding to favorites: ${remoteManga.title}" }
favorites.add(remoteManga) favorites.add(remoteManga)
}
} ?: favorites.add(remoteManga)
} else { } else {
logcat(LogPriority.DEBUG) { "Already up-to-date favorite: ${remoteManga.title}" }
}
}
// Handle non-favorites
!remoteManga.favorite -> {
logcat(LogPriority.DEBUG) { "Adding to non-favorites: ${remoteManga.title}" }
nonFavorites.add(remoteManga) nonFavorites.add(remoteManga)
} }
} }
}
}
val minutes = elapsedTimeMillis / 60000
val seconds = (elapsedTimeMillis % 60000) / 1000
logcat(LogPriority.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)
} }
private fun processFavoriteManga(backupManga: List<BackupManga>): List<BackupManga> {
val mangas = mutableListOf<BackupManga>()
val lastSyncTimeStamp = syncPreferences.lastSyncTimestamp().get()
val elapsedTimeMillis = measureTimeMillis {
logcat(LogPriority.DEBUG) { "Starting to process BackupMangas." }
backupManga.forEach { manga ->
val mangaLastUpdatedStatus = manga.lastModifiedAt * 1000L > lastSyncTimeStamp
val chaptersUpdatedStatus = chaptersUpdatedAfterSync(manga, lastSyncTimeStamp)
if (mangaLastUpdatedStatus || chaptersUpdatedStatus) {
mangas.add(manga)
logcat(LogPriority.DEBUG) {
"Added ${manga.title} to the process list. Manga Last Updated: ${mangaLastUpdatedStatus}, Chapters Updated: ${chaptersUpdatedStatus}."
}
} else {
logcat(LogPriority.DEBUG) {
"Skipped ${manga.title} as it has not been updated since the last sync (Last Modified: ${manga.lastModifiedAt * 1000L}, Last Sync: $lastSyncTimeStamp)."
}
}
}
}
val minutes = elapsedTimeMillis / 60000
val seconds = (elapsedTimeMillis % 60000) / 1000
logcat(LogPriority.DEBUG) { "Processing completed in ${minutes}m ${seconds}s. Total Processed: ${mangas.size}" }
return mangas
}
private fun chaptersUpdatedAfterSync(manga: BackupManga, lastSyncTimeStamp: Long): Boolean {
return manga.chapters.any { chapter ->
val updated = chapter.lastModifiedAt * 1000L > lastSyncTimeStamp
if(updated) {
logcat(LogPriority.DEBUG) {
"Chapter ${chapter.name} of ${manga.title} updated after last sync (Chapter Last Modified: ${chapter.lastModifiedAt}, Last Sync: $lastSyncTimeStamp)."
}
}
updated
}
}
/** /**
* Updates the non-favorite manga in the local database with their favorite status from the backup. * Updates the non-favorite manga in the local database with their favorite status from the backup.
* @param nonFavorites the list of non-favorite BackupManga objects from the backup. * @param nonFavorites the list of non-favorite BackupManga objects from the backup.