From f5b10cd07f294a569ef5417ca487a5cf3baf8e69 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Fri, 12 Jan 2024 02:01:15 +1100 Subject: [PATCH 1/4] feat: add a way to reset last_sync_timestamp. --- .../settings/screen/SettingsAdvancedScreen.kt | 21 +++++++++++++++++++ .../commonMain/resources/MR/base/strings.xml | 3 +++ 2 files changed, 24 insertions(+) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 4b8f2b9d7..833a4a1a2 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -63,6 +63,7 @@ import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.system.logcat import tachiyomi.domain.manga.interactor.ResetViewerFlags +import tachiyomi.domain.sync.SyncPreferences import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState @@ -142,6 +143,7 @@ object SettingsAdvancedScreen : SearchableSettings { getNetworkGroup(networkPreferences = networkPreferences), getLibraryGroup(), getExtensionsGroup(basePreferences = basePreferences), + getSyncGroup(), ), ) } @@ -404,4 +406,23 @@ object SettingsAdvancedScreen : SearchableSettings { ), ) } + + @Composable + private fun getSyncGroup(): Preference.PreferenceGroup { + val context = LocalContext.current + val syncPreferences = remember { Injekt.get() } + return Preference.PreferenceGroup( + title = stringResource(MR.strings.label_sync), + preferenceItems = persistentListOf( + Preference.PreferenceItem.TextPreference( + title = stringResource(MR.strings.pref_reset_sync_timestamp), + subtitle = stringResource(MR.strings.pref_reset_sync_timestamp_subtitle), + onClick = { + syncPreferences.lastSyncTimestamp().set(0) + context.toast(MR.strings.success_reset_sync_timestamp) + }, + ), + ), + ) + } } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index e0ba8bec1..1b6df365e 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -561,6 +561,9 @@ Sync Automatic Synchronization Synchronization frequency + Reset last sync timestamp + Reset the last sync timestamp to force a full sync + Last sync timestamp reset SyncYomi Done in %1$s Last Synchronization: %1$s From 2f9f673326e53c8df079ebb3d3a9160408ae1ac4 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Fri, 12 Jan 2024 09:26:42 +1100 Subject: [PATCH 2/4] feat: add backupSource, backupPref, and "SY" backupSavedSearches. I forgot to add the data into the merging logic, So remote always have empty value :(. Better late than never. --- .../data/sync/service/SyncService.kt | 178 +++++++++++++++++- 1 file changed, 174 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt index 45392a25e..55694b78a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt @@ -5,6 +5,9 @@ import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupChapter import eu.kanade.tachiyomi.data.backup.models.BackupManga +import eu.kanade.tachiyomi.data.backup.models.BackupPreference +import eu.kanade.tachiyomi.data.backup.models.BackupSource +import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import logcat.LogPriority @@ -62,17 +65,26 @@ abstract class SyncService( * @param remoteSyncData The SData containing the remote sync data. * @return The JSON string containing the merged sync data. */ - fun mergeSyncData(localSyncData: SyncData, remoteSyncData: SyncData): SyncData { + private fun mergeSyncData(localSyncData: SyncData, remoteSyncData: SyncData): SyncData { val mergedMangaList = mergeMangaLists(localSyncData.backup?.backupManga, remoteSyncData.backup?.backupManga) val mergedCategoriesList = mergeCategoriesLists(localSyncData.backup?.backupCategories, remoteSyncData.backup?.backupCategories) + val mergedSourcesList = mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources) + val mergedPreferencesList = + mergePreferencesLists(localSyncData.backup?.backupPreferences, remoteSyncData.backup?.backupPreferences) + val mergedSourcePreferencesList = mergeSourcePreferencesLists( + localSyncData.backup?.backupSourcePreferences, + remoteSyncData.backup?.backupSourcePreferences, + ) + // Create the merged Backup object val mergedBackup = Backup( backupManga = mergedMangaList, backupCategories = mergedCategoriesList, - backupBrokenSources = localSyncData.backup?.backupBrokenSources ?: emptyList(), - backupSources = localSyncData.backup?.backupSources ?: emptyList(), + backupSources = mergedSourcesList, + backupPreferences = mergedPreferencesList, + backupSourcePreferences = mergedSourcePreferencesList, ) // Create the merged SData object @@ -260,7 +272,9 @@ abstract class SyncService( chosenChapter } else -> { - logcat(LogPriority.DEBUG, logTag) { "No chapter found for composite key: $compositeKey. Skipping." } + logcat(LogPriority.DEBUG, logTag) { + "No chapter found for composite key: $compositeKey. Skipping." + } null } } @@ -314,4 +328,160 @@ abstract class SyncService( return mergedCategoriesMap.values.toList() } + + private fun mergeSourcesLists( + localSources: List?, + remoteSources: List? + ): List { + val logTag = "MergeSources" + + // Create maps using sourceId as key + val localSourceMap = localSources?.associateBy { it.sourceId } ?: emptyMap() + val remoteSourceMap = remoteSources?.associateBy { it.sourceId } ?: emptyMap() + + logcat(LogPriority.DEBUG, logTag) { + "Starting source merge. Local sources: ${localSources?.size}, Remote sources: ${remoteSources?.size}" + } + + // Merge both source maps + val mergedSources = (localSourceMap.keys + remoteSourceMap.keys).distinct().mapNotNull { sourceId -> + val localSource = localSourceMap[sourceId] + val remoteSource = remoteSourceMap[sourceId] + + logcat(LogPriority.DEBUG, logTag) { + "Processing source ID: $sourceId. Local source: ${localSource != null}, " + + "Remote source: ${remoteSource != null}" + } + + when { + localSource != null && remoteSource == null -> { + logcat(LogPriority.DEBUG, logTag) { "Using local source: ${localSource.name}." } + localSource + } + remoteSource != null && localSource == null -> { + logcat(LogPriority.DEBUG, logTag) { "Using remote source: ${remoteSource.name}." } + remoteSource + } + else -> { + logcat(LogPriority.DEBUG, logTag) { "Remote and local is not empty: $sourceId. Skipping." } + null + } + } + } + + logcat(LogPriority.DEBUG, logTag) { "Source merge completed. Total merged sources: ${mergedSources.size}" } + + return mergedSources + } + + private fun mergePreferencesLists( + localPreferences: List?, + remotePreferences: List? + ): List { + val logTag = "MergePreferences" + + // Create maps using key as the unique identifier + val localPreferencesMap = localPreferences?.associateBy { it.key } ?: emptyMap() + val remotePreferencesMap = remotePreferences?.associateBy { it.key } ?: emptyMap() + + logcat(LogPriority.DEBUG, logTag) { + "Starting preferences merge. Local preferences: ${localPreferences?.size}, " + + "Remote preferences: ${remotePreferences?.size}" + } + + // Merge both preferences maps + val mergedPreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { key -> + val localPreference = localPreferencesMap[key] + val remotePreference = remotePreferencesMap[key] + + logcat(LogPriority.DEBUG, logTag) { + "Processing preference key: $key. Local preference: ${localPreference != null}, " + + "Remote preference: ${remotePreference != null}" + } + + when { + localPreference != null && remotePreference == null -> { + logcat(LogPriority.DEBUG, logTag) { "Using local preference: ${localPreference.key}." } + localPreference + } + remotePreference != null && localPreference == null -> { + logcat(LogPriority.DEBUG, logTag) { "Using remote preference: ${remotePreference.key}." } + remotePreference + } + else -> { + logcat(LogPriority.DEBUG, logTag) { "Both remote and local have keys. Skipping: $key" } + null + } + } + } + + logcat(LogPriority.DEBUG, logTag) { + "Preferences merge completed. Total merged preferences: ${mergedPreferences.size}" + } + + return mergedPreferences + } + + private fun mergeSourcePreferencesLists( + localPreferences: List?, + remotePreferences: List? + ): List { + val logTag = "MergeSourcePreferences" + + // Create maps using sourceKey as the unique identifier + val localPreferencesMap = localPreferences?.associateBy { it.sourceKey } ?: emptyMap() + val remotePreferencesMap = remotePreferences?.associateBy { it.sourceKey } ?: emptyMap() + + logcat(LogPriority.DEBUG, logTag) { + "Starting source preferences merge. Local source preferences: ${localPreferences?.size}, " + + "Remote source preferences: ${remotePreferences?.size}" + } + + // Merge both source preferences maps + val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { sourceKey -> + val localSourcePreference = localPreferencesMap[sourceKey] + val remoteSourcePreference = remotePreferencesMap[sourceKey] + + logcat(LogPriority.DEBUG, logTag) { + "Processing source preference key: $sourceKey. " + + "Local source preference: ${localSourcePreference != null}, " + + "Remote source preference: ${remoteSourcePreference != null}" + } + + when { + localSourcePreference != null && remoteSourcePreference == null -> { + logcat(LogPriority.DEBUG, logTag) { + "Using local source preference: ${localSourcePreference.sourceKey}." + } + localSourcePreference + } + remoteSourcePreference != null && localSourcePreference == null -> { + logcat(LogPriority.DEBUG, logTag) { + "Using remote source preference: ${remoteSourcePreference.sourceKey}." + } + remoteSourcePreference + } + localSourcePreference != null && remoteSourcePreference != null -> { + // Merge the individual preferences within the source preferences + val mergedPrefs = mergeIndividualPreferences(localSourcePreference.prefs, remoteSourcePreference.prefs) + BackupSourcePreferences(sourceKey, mergedPrefs) + } + else -> null + } + } + + logcat(LogPriority.DEBUG, logTag) { + "Source preferences merge completed. Total merged source preferences: ${mergedSourcePreferences.size}" + } + + return mergedSourcePreferences + } + + private fun mergeIndividualPreferences( + localPrefs: List, + remotePrefs: List + ): List { + val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key } + return mergedPrefsMap.values.toList() + } } From d407d698c8eec82c9685cf76634dd48867df15b2 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Fri, 12 Jan 2024 09:29:58 +1100 Subject: [PATCH 3/4] chore: lint --- .../tachiyomi/data/sync/service/SyncService.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt index 55694b78a..4471adf51 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt @@ -70,7 +70,8 @@ abstract class SyncService( val mergedCategoriesList = mergeCategoriesLists(localSyncData.backup?.backupCategories, remoteSyncData.backup?.backupCategories) - val mergedSourcesList = mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources) + val mergedSourcesList = + mergeSourcesLists(localSyncData.backup?.backupSources, remoteSyncData.backup?.backupSources) val mergedPreferencesList = mergePreferencesLists(localSyncData.backup?.backupPreferences, remoteSyncData.backup?.backupPreferences) val mergedSourcePreferencesList = mergeSourcePreferencesLists( @@ -331,7 +332,7 @@ abstract class SyncService( private fun mergeSourcesLists( localSources: List?, - remoteSources: List? + remoteSources: List?, ): List { val logTag = "MergeSources" @@ -376,7 +377,7 @@ abstract class SyncService( private fun mergePreferencesLists( localPreferences: List?, - remotePreferences: List? + remotePreferences: List?, ): List { val logTag = "MergePreferences" @@ -424,7 +425,7 @@ abstract class SyncService( private fun mergeSourcePreferencesLists( localPreferences: List?, - remotePreferences: List? + remotePreferences: List?, ): List { val logTag = "MergeSourcePreferences" @@ -438,7 +439,8 @@ abstract class SyncService( } // Merge both source preferences maps - val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { sourceKey -> + val mergedSourcePreferences = (localPreferencesMap.keys + remotePreferencesMap.keys).distinct().mapNotNull { + sourceKey -> val localSourcePreference = localPreferencesMap[sourceKey] val remoteSourcePreference = remotePreferencesMap[sourceKey] @@ -463,7 +465,8 @@ abstract class SyncService( } localSourcePreference != null && remoteSourcePreference != null -> { // Merge the individual preferences within the source preferences - val mergedPrefs = mergeIndividualPreferences(localSourcePreference.prefs, remoteSourcePreference.prefs) + val mergedPrefs = + mergeIndividualPreferences(localSourcePreference.prefs, remoteSourcePreference.prefs) BackupSourcePreferences(sourceKey, mergedPrefs) } else -> null @@ -479,7 +482,7 @@ abstract class SyncService( private fun mergeIndividualPreferences( localPrefs: List, - remotePrefs: List + remotePrefs: List, ): List { val mergedPrefsMap = (localPrefs + remotePrefs).associateBy { it.key } return mergedPrefsMap.values.toList() From b13e3358a8fd2de04429c2411c60c5a1c342ea67 Mon Sep 17 00:00:00 2001 From: KaiserBh Date: Fri, 12 Jan 2024 09:34:19 +1100 Subject: [PATCH 4/4] refactor: clean up comment docs, outdated. --- .../data/sync/service/SyncService.kt | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt index 4471adf51..c1e591c46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/sync/service/SyncService.kt @@ -94,15 +94,6 @@ abstract class SyncService( ) } - /** - * Merges two lists of BackupManga objects, selecting the most recent manga based on the lastModifiedAt value. - * If lastModifiedAt is null for a manga, it treats that manga as the oldest possible for comparison purposes. - * This function is designed to reconcile local and remote manga lists, ensuring the most up-to-date manga is retained. - * - * @param localMangaList The list of local BackupManga objects or null. - * @param remoteMangaList The list of remote BackupManga objects or null. - * @return A list of BackupManga objects, each representing the most recent version of the manga from either local or remote sources. - */ private fun mergeMangaLists( localMangaList: List?, remoteMangaList: List?, @@ -203,23 +194,6 @@ abstract class SyncService( return mergedList } -/** - * Merges two lists of BackupChapter objects, selecting the most recent chapter based on the lastModifiedAt value. - * If lastModifiedAt is null for a chapter, it treats that chapter as the oldest possible for comparison purposes. - * This function is designed to reconcile local and remote chapter lists, ensuring the most up-to-date chapter is retained. - * - * @param localChapters The list of local BackupChapter objects. - * @param remoteChapters The list of remote BackupChapter objects. - * @return A list of BackupChapter objects, each representing the most recent version of the chapter from either local or remote sources. - * - * - This function is used in scenarios where local and remote chapter lists need to be synchronized. - * - It iterates over the union of the URLs from both local and remote chapters. - * - For each URL, it compares the corresponding local and remote chapters based on the lastModifiedAt value. - * - If only one source (local or remote) has the chapter for a URL, that chapter is used. - * - If both sources have the chapter, the one with the more recent lastModifiedAt value is chosen. - * - 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. - */ private fun mergeChapters( localChapters: List, remoteChapters: List, @@ -286,13 +260,6 @@ abstract class SyncService( return mergedChapters } - /** - * Merges two lists of SyncCategory objects, prioritizing the category with the most recent order value. - * - * @param localCategoriesList The list of local SyncCategory objects. - * @param remoteCategoriesList The list of remote SyncCategory objects. - * @return The merged list of SyncCategory objects. - */ private fun mergeCategoriesLists( localCategoriesList: List?, remoteCategoriesList: List?,