Improve favorites sync statuses

This commit is contained in:
Jobobby04 2024-12-08 13:35:36 -05:00
parent 3f8cce8a32
commit 76af3b59f0
4 changed files with 243 additions and 141 deletions

View File

@ -15,7 +15,6 @@ import androidx.compose.ui.window.DialogProperties
import exh.favorites.FavoritesSyncStatus import exh.favorites.FavoritesSyncStatus
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -23,7 +22,6 @@ import kotlin.time.Duration.Companion.seconds
data class SyncFavoritesProgressProperties( data class SyncFavoritesProgressProperties(
val title: String, val title: String,
val text: String, val text: String,
val canDismiss: Boolean,
val positiveButtonText: String? = null, val positiveButtonText: String? = null,
val positiveButton: (() -> Unit)? = null, val positiveButton: (() -> Unit)? = null,
val negativeButtonText: String? = null, val negativeButtonText: String? = null,
@ -34,18 +32,23 @@ data class SyncFavoritesProgressProperties(
fun SyncFavoritesProgressDialog( fun SyncFavoritesProgressDialog(
status: FavoritesSyncStatus, status: FavoritesSyncStatus,
setStatusIdle: () -> Unit, setStatusIdle: () -> Unit,
openManga: (Manga) -> Unit, openManga: (Long) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) { val properties by produceState<SyncFavoritesProgressProperties?>(initialValue = null, status) {
when (status) { when (status) {
is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties( is FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error), title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(SYMR.strings.favorites_sync_bad_library_state, status.message), text = context.stringResource(
canDismiss = false, SYMR.strings.favorites_sync_bad_library_state,
context.stringResource(
SYMR.strings.favorites_sync_gallery_in_multiple_categories, status.mangaTitle,
status.categories.joinToString(),
),
),
positiveButtonText = context.stringResource(SYMR.strings.show_gallery), positiveButtonText = context.stringResource(SYMR.strings.show_gallery),
positiveButton = { positiveButton = {
openManga(status.manga) openManga(status.mangaId)
setStatusIdle() setStatusIdle()
}, },
negativeButtonText = context.stringResource(MR.strings.action_ok), negativeButtonText = context.stringResource(MR.strings.action_ok),
@ -53,31 +56,122 @@ fun SyncFavoritesProgressDialog(
) )
is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties( is FavoritesSyncStatus.CompleteWithErrors -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_done_errors), title = context.stringResource(SYMR.strings.favorites_sync_done_errors),
text = context.stringResource(SYMR.strings.favorites_sync_done_errors_message, status.message), text = context.stringResource(
canDismiss = false, SYMR.strings.favorites_sync_done_errors_message,
positiveButtonText = context.stringResource(MR.strings.action_ok), status.messages.joinToString(separator = "\n") {
positiveButton = setStatusIdle, when (it) {
) is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
is FavoritesSyncStatus.Error -> value = SyncFavoritesProgressProperties( context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
title = context.stringResource(SYMR.strings.favorites_sync_error), context.stringResource(
text = context.stringResource(SYMR.strings.favorites_sync_error_string, status.message), SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, it.reason,
canDismiss = false, )
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, it.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, it.title, it.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
}
}
),
positiveButtonText = context.stringResource(MR.strings.action_ok), positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle, positiveButton = setStatusIdle,
) )
is FavoritesSyncStatus.Idle -> value = null is FavoritesSyncStatus.Idle -> value = null
is FavoritesSyncStatus.Initializing, is FavoritesSyncStatus.Processing -> { is FavoritesSyncStatus.Initializing -> {
value = SyncFavoritesProgressProperties( value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing), title = context.stringResource(SYMR.strings.favorites_syncing),
text = status.message, text = context.stringResource(SYMR.strings.favorites_sync_initializing),
canDismiss = false,
) )
if (status is FavoritesSyncStatus.Processing && status.title != null) { }
is FavoritesSyncStatus.SyncError -> value = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_sync_error),
text = context.stringResource(
SYMR.strings.favorites_sync_error_string,
when (status) {
FavoritesSyncStatus.SyncError.NotLoggedInSyncError -> context.stringResource(SYMR.strings.please_login)
FavoritesSyncStatus.SyncError.FailedToFetchFavorites ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_featch)
is FavoritesSyncStatus.SyncError.UnknownSyncError ->
context.stringResource(SYMR.strings.favorites_sync_unknown_error, status.message)
is FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_error, status.title, status.reason,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail ->
context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) +
context.stringResource(
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, status.title, status.url,
)
is FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_add_to_remote, status.title, status.gid)
FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote ->
context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
}
),
positiveButtonText = context.stringResource(MR.strings.action_ok),
positiveButton = setStatusIdle,
)
is FavoritesSyncStatus.Processing -> {
val properties = SyncFavoritesProgressProperties(
title = context.stringResource(SYMR.strings.favorites_syncing),
text = when (status) {
FavoritesSyncStatus.Processing.VerifyingLibrary ->
context.stringResource(SYMR.strings.favorites_sync_verifying_library)
FavoritesSyncStatus.Processing.DownloadingFavorites ->
context.stringResource(SYMR.strings.favorites_sync_downloading)
FavoritesSyncStatus.Processing.CalculatingRemoteChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes)
FavoritesSyncStatus.Processing.CalculatingLocalChanges ->
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes)
FavoritesSyncStatus.Processing.SyncingCategoryNames ->
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names)
is FavoritesSyncStatus.Processing.RemovingRemoteGalleries ->
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, status.galleryCount)
is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
)
} else {
context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, status.index, status.total)
}
is FavoritesSyncStatus.Processing.RemovingGalleryFromLocal ->
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, status.index, status.total)
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
if (status.isThrottling) {
context.stringResource(
SYMR.strings.favorites_sync_processing_throttle,
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
)
} else {
context.stringResource(SYMR.strings.favorites_sync_add_to_local, status.index, status.total)
}
FavoritesSyncStatus.Processing.CleaningUp ->
context.stringResource(SYMR.strings.favorites_sync_cleaning_up)
},
)
value = properties
if (
status is FavoritesSyncStatus.Processing.AddingGalleryToRemote ||
status is FavoritesSyncStatus.Processing.AddingGalleryToLocal
) {
delay(5.seconds) delay(5.seconds)
value = SyncFavoritesProgressProperties( value = properties.copy(
title = context.stringResource(SYMR.strings.favorites_syncing), text = when (status) {
text = status.delayedMessage ?: status.message, is FavoritesSyncStatus.Processing.AddingGalleryToRemote ->
canDismiss = false, properties.text + "\n\n" + status.title
is FavoritesSyncStatus.Processing.AddingGalleryToLocal ->
properties.text + "\n\n" + status.title
else -> properties.text
},
) )
} }
} }
@ -112,8 +206,8 @@ fun SyncFavoritesProgressDialog(
} }
}, },
properties = DialogProperties( properties = DialogProperties(
dismissOnClickOutside = dialog.canDismiss, dismissOnClickOutside = false,
dismissOnBackPress = dialog.canDismiss, dismissOnBackPress = false,
), ),
) )
} }

View File

@ -335,7 +335,7 @@ data object LibraryTab : Tab {
SyncFavoritesProgressDialog( SyncFavoritesProgressDialog(
status = screenModel.favoritesSync.status.collectAsState().value, status = screenModel.favoritesSync.status.collectAsState().value,
setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) }, setStatusIdle = { screenModel.favoritesSync.status.value = FavoritesSyncStatus.Idle(context) },
openManga = { navigator.push(MangaScreen(it.id)) }, openManga = { navigator.push(MangaScreen(it)) },
) )
// SY <-- // SY <--

View File

@ -64,18 +64,18 @@ class FavoritesSyncHelper(val context: Context) {
?: EHentai(0, true, context) ?: EHentai(0, true, context)
} }
private val storage = LocalFavoritesStorage() private val storage by lazy { LocalFavoritesStorage() }
private val galleryAdder = GalleryAdder() private val galleryAdder by lazy { GalleryAdder() }
private val throttleManager = EHentaiThrottleManager() private val throttleManager by lazy { EHentaiThrottleManager() }
private var wifiLock: WifiManager.WifiLock? = null private var wifiLock: WifiManager.WifiLock? = null
private var wakeLock: PowerManager.WakeLock? = null private var wakeLock: PowerManager.WakeLock? = null
private val logger = xLog() private val logger by lazy { xLog() }
val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle(context)) val status: MutableStateFlow<FavoritesSyncStatus> = MutableStateFlow(FavoritesSyncStatus.Idle)
@Synchronized @Synchronized
fun runSync(scope: CoroutineScope) { fun runSync(scope: CoroutineScope) {
@ -83,7 +83,7 @@ class FavoritesSyncHelper(val context: Context) {
return return
} }
status.value = FavoritesSyncStatus.Initializing(context) status.value = FavoritesSyncStatus.Initializing
scope.launch(Dispatchers.IO) { beginSync() } scope.launch(Dispatchers.IO) { beginSync() }
} }
@ -91,13 +91,12 @@ class FavoritesSyncHelper(val context: Context) {
private suspend fun beginSync() { private suspend fun beginSync() {
// Check if logged in // Check if logged in
if (!prefs.enableExhentai().get()) { if (!prefs.enableExhentai().get()) {
status.value = FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.please_login)) status.value = FavoritesSyncStatus.SyncError.NotLoggedInSyncError
return return
} }
// Validate library state // Validate library state
status.value = status.value = FavoritesSyncStatus.Processing.VerifyingLibrary
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_verifying_library))
val libraryManga = getLibraryManga.await() val libraryManga = getLibraryManga.await()
val seenManga = HashSet<Long>(libraryManga.size) val seenManga = HashSet<Long>(libraryManga.size)
libraryManga.forEach { (manga) -> libraryManga.forEach { (manga) ->
@ -106,7 +105,7 @@ class FavoritesSyncHelper(val context: Context) {
if (manga.id in seenManga) { if (manga.id in seenManga) {
val inCategories = getCategories.await(manga.id) val inCategories = getCategories.await(manga.id)
status.value = FavoritesSyncStatus.BadLibraryState status.value = FavoritesSyncStatus.BadLibraryState
.MangaInMultipleCategories(manga, inCategories, context) .MangaInMultipleCategories(manga.id, manga.title, inCategories.map { it.name })
logger.w(context.stringResource(SYMR.strings.favorites_sync_gallery_multiple_categories_error, manga.id)) logger.w(context.stringResource(SYMR.strings.favorites_sync_gallery_multiple_categories_error, manga.id))
return return
@ -117,17 +116,15 @@ class FavoritesSyncHelper(val context: Context) {
// Download remote favorites // Download remote favorites
val favorites = try { val favorites = try {
status.value = status.value = FavoritesSyncStatus.Processing.DownloadingFavorites
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_downloading))
exh.fetchFavorites() exh.fetchFavorites()
} catch (e: Exception) { } catch (e: Exception) {
status.value = status.value = FavoritesSyncStatus.SyncError.FailedToFetchFavorites
FavoritesSyncStatus.Error(context.stringResource(SYMR.strings.favorites_sync_failed_to_featch))
logger.e(context.stringResource(SYMR.strings.favorites_sync_could_not_fetch), e) logger.e(context.stringResource(SYMR.strings.favorites_sync_could_not_fetch), e)
return return
} }
val errorList = mutableListOf<String>() val errorList = mutableListOf<FavoritesSyncStatus.SyncError.GallerySyncError>()
try { try {
// Take wake + wifi locks // Take wake + wifi locks
@ -157,23 +154,17 @@ class FavoritesSyncHelper(val context: Context) {
// Do not update galleries while syncing favorites // Do not update galleries while syncing favorites
EHentaiUpdateWorker.cancelBackground(context) EHentaiUpdateWorker.cancelBackground(context)
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.CalculatingRemoteChanges
context.stringResource(SYMR.strings.favorites_sync_calculating_remote_changes),
)
val remoteChanges = storage.getChangedRemoteEntries(favorites.first) val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
val localChanges = if (prefs.exhReadOnlySync().get()) { val localChanges = if (prefs.exhReadOnlySync().get()) {
null // Do not build local changes if they are not going to be applied null // Do not build local changes if they are not going to be applied
} else { } else {
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.CalculatingLocalChanges
context.stringResource(SYMR.strings.favorites_sync_calculating_local_changes),
)
storage.getChangedDbEntries() storage.getChangedDbEntries()
} }
// Apply remote categories // Apply remote categories
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.SyncingCategoryNames
context.stringResource(SYMR.strings.favorites_sync_syncing_category_names),
)
applyRemoteCategories(favorites.second) applyRemoteCategories(favorites.second)
// Apply change sets // Apply change sets
@ -182,8 +173,7 @@ class FavoritesSyncHelper(val context: Context) {
applyChangeSetToRemote(errorList, localChanges) applyChangeSetToRemote(errorList, localChanges)
} }
status.value = status.value = FavoritesSyncStatus.Processing.CleaningUp
FavoritesSyncStatus.Processing(context.stringResource(SYMR.strings.favorites_sync_cleaning_up))
storage.snapshotEntries() storage.snapshotEntries()
withUIContext { withUIContext {
@ -194,9 +184,7 @@ class FavoritesSyncHelper(val context: Context) {
logger.w(context.stringResource(SYMR.strings.favorites_sync_ignoring_exception), e) logger.w(context.stringResource(SYMR.strings.favorites_sync_ignoring_exception), e)
return return
} catch (e: Exception) { } catch (e: Exception) {
status.value = FavoritesSyncStatus.Error( status.value = FavoritesSyncStatus.SyncError.UnknownSyncError(e.message.orEmpty())
context.stringResource(SYMR.strings.favorites_sync_unknown_error, e.message.orEmpty()),
)
logger.e(context.stringResource(SYMR.strings.favorites_sync_sync_error), e) logger.e(context.stringResource(SYMR.strings.favorites_sync_sync_error), e)
return return
} finally { } finally {
@ -215,7 +203,7 @@ class FavoritesSyncHelper(val context: Context) {
} }
if (errorList.isEmpty()) { if (errorList.isEmpty()) {
status.value = FavoritesSyncStatus.Idle(context) status.value = FavoritesSyncStatus.Idle
} else { } else {
status.value = FavoritesSyncStatus.CompleteWithErrors(errorList) status.value = FavoritesSyncStatus.CompleteWithErrors(errorList)
} }
@ -249,7 +237,7 @@ class FavoritesSyncHelper(val context: Context) {
} }
} }
private suspend fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) { private suspend fun addGalleryRemote(errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>, gallery: FavoriteEntry) {
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav" val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
val request = POST( val request = POST(
@ -263,13 +251,16 @@ class FavoritesSyncHelper(val context: Context) {
) )
if (!explicitlyRetryExhRequest(10, request)) { if (!explicitlyRetryExhRequest(10, request)) {
val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!" val error = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToAddGalleryToRemote(
gallery.title,
gallery.gid
)
if (prefs.exhLenientSync().get()) { if (prefs.exhLenientSync().get()) {
errorList += errorString errorList += error
} else { } else {
status.value = FavoritesSyncStatus.Error(errorString) status.value = error
throw IgnoredException(errorString) throw IgnoredException(error)
} }
} }
} }
@ -293,12 +284,13 @@ class FavoritesSyncHelper(val context: Context) {
return success return success
} }
private suspend fun applyChangeSetToRemote(errorList: MutableList<String>, changeSet: ChangeSet) { private suspend fun applyChangeSetToRemote(
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
changeSet: ChangeSet,
) {
// Apply removals // Apply removals
if (changeSet.removed.isNotEmpty()) { if (changeSet.removed.isNotEmpty()) {
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.RemovingRemoteGalleries(changeSet.removed.size)
context.stringResource(SYMR.strings.favorites_sync_removing_galleries, changeSet.removed.size),
)
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
.add("ddact", "delete") .add("ddact", "delete")
@ -315,13 +307,11 @@ class FavoritesSyncHelper(val context: Context) {
) )
if (!explicitlyRetryExhRequest(10, request)) { if (!explicitlyRetryExhRequest(10, request)) {
val errorString = context.stringResource(SYMR.strings.favorites_sync_unable_to_delete)
if (prefs.exhLenientSync().get()) { if (prefs.exhLenientSync().get()) {
errorList += errorString errorList += FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
} else { } else {
status.value = FavoritesSyncStatus.Error(errorString) status.value = FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote
throw IgnoredException(errorString) throw IgnoredException(FavoritesSyncStatus.SyncError.GallerySyncError.UnableToDeleteFromRemote)
} }
} }
} }
@ -329,10 +319,10 @@ class FavoritesSyncHelper(val context: Context) {
// Apply additions // Apply additions
throttleManager.resetThrottle() throttleManager.resetThrottle()
changeSet.added.forEachIndexed { index, it -> changeSet.added.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.AddingGalleryToRemote(
message = context.stringResource(SYMR.strings.favorites_sync_adding_to_remote, index + 1, changeSet.added.size), index = index + 1,
isThrottle = needWarnThrottle(), total = changeSet.added.size,
context = context, isThrottling = needWarnThrottle(),
title = it.title, title = it.title,
) )
@ -342,14 +332,17 @@ class FavoritesSyncHelper(val context: Context) {
} }
} }
private suspend fun applyChangeSetToLocal(errorList: MutableList<String>, changeSet: ChangeSet) { private suspend fun applyChangeSetToLocal(
errorList: MutableList<FavoritesSyncStatus.SyncError.GallerySyncError>,
changeSet: ChangeSet,
) {
val removedManga = mutableListOf<Manga>() val removedManga = mutableListOf<Manga>()
// Apply removals // Apply removals
changeSet.removed.forEachIndexed { index, it -> changeSet.removed.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.RemovingGalleryFromLocal(
context.stringResource(SYMR.strings.favorites_sync_remove_from_local, index + 1, changeSet.removed.size), index = index + 1,
title = it.title, total = changeSet.removed.size,
) )
val url = it.getUrl() val url = it.getUrl()
@ -379,10 +372,10 @@ class FavoritesSyncHelper(val context: Context) {
// Apply additions // Apply additions
throttleManager.resetThrottle() throttleManager.resetThrottle()
changeSet.added.forEachIndexed { index, it -> changeSet.added.forEachIndexed { index, it ->
status.value = FavoritesSyncStatus.Processing( status.value = FavoritesSyncStatus.Processing.AddingGalleryToLocal(
message = context.stringResource(SYMR.strings.favorites_sync_add_to_local, index + 1, changeSet.added.size), index = index + 1,
isThrottle = needWarnThrottle(), total = changeSet.added.size,
context = context, isThrottling = needWarnThrottle(),
title = it.title, title = it.title,
) )
@ -405,24 +398,23 @@ class FavoritesSyncHelper(val context: Context) {
return@forEachIndexed return@forEachIndexed
} }
val errorString = context.stringResource(SYMR.strings.favorites_sync_failed_to_add_to_local) + val error = when (result) {
when (result) { is GalleryAddEvent.Fail.Error -> FavoritesSyncStatus.SyncError.GallerySyncError.GalleryAddFail(
is GalleryAddEvent.Fail.Error -> context.stringResource( it.title, result.logMessage,
SYMR.strings.favorites_sync_failed_to_add_to_local_error, it.title, result.logMessage, )
) is GalleryAddEvent.Fail.UnknownType -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
is GalleryAddEvent.Fail.UnknownType -> context.stringResource( it.title, result.galleryUrl,
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl, )
) is GalleryAddEvent.Fail.UnknownSource -> FavoritesSyncStatus.SyncError.GallerySyncError.InvalidGalleryFail(
is GalleryAddEvent.Fail.UnknownSource -> context.stringResource( it.title, result.galleryUrl,
SYMR.strings.favorites_sync_failed_to_add_to_local_unknown_type, it.title, result.galleryUrl, )
) }
}
if (prefs.exhLenientSync().get()) { if (prefs.exhLenientSync().get()) {
errorList += errorString errorList += error
} else { } else {
status.value = FavoritesSyncStatus.Error(errorString) status.value = error
throw IgnoredException(errorString) throw IgnoredException(error)
} }
} else if (result is GalleryAddEvent.Success) { } else if (result is GalleryAddEvent.Success) {
insertedMangaCategories += categories[it.category].id to result.manga insertedMangaCategories += categories[it.category].id to result.manga
@ -438,59 +430,74 @@ class FavoritesSyncHelper(val context: Context) {
private fun needWarnThrottle() = private fun needWarnThrottle() =
throttleManager.throttleTime >= THROTTLE_WARN throttleManager.throttleTime >= THROTTLE_WARN
class IgnoredException(message: String) : RuntimeException(message) class IgnoredException(message: FavoritesSyncStatus.SyncError.GallerySyncError) : RuntimeException(message.toString())
companion object { companion object {
private val THROTTLE_WARN = 1.seconds private val THROTTLE_WARN = 1.seconds
} }
} }
@Serializable
sealed class FavoritesSyncStatus { sealed class FavoritesSyncStatus {
abstract val message: String @Serializable
sealed class SyncError : FavoritesSyncStatus() {
data class Error(override val message: String) : FavoritesSyncStatus() @Serializable
data class Idle(override val message: String) : FavoritesSyncStatus() { data object NotLoggedInSyncError : SyncError()
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_waiting_for_start)) @Serializable
} data object FailedToFetchFavorites : SyncError()
sealed class BadLibraryState : FavoritesSyncStatus() { @Serializable
data class MangaInMultipleCategories( data class UnknownSyncError(val message: String) : SyncError()
val manga: Manga, @Serializable
val categories: List<Category>, sealed class GallerySyncError : SyncError() {
override val message: String, @Serializable
) : BadLibraryState() { data class UnableToAddGalleryToRemote(val title: String, val gid: String): GallerySyncError()
constructor(manga: Manga, categories: List<Category>, context: Context) : @Serializable
this( data object UnableToDeleteFromRemote : GallerySyncError()
manga = manga, @Serializable
categories = categories, data class GalleryAddFail(val title: String, val reason: String): GallerySyncError()
message = context.stringResource( @Serializable
SYMR.strings.favorites_sync_gallery_in_multiple_categories, manga.title, data class InvalidGalleryFail(val title: String, val url: String): GallerySyncError()
categories.joinToString {
it.name
},
),
)
} }
} }
data class Initializing(override val message: String) : FavoritesSyncStatus() { @Serializable
constructor(context: Context) : this(context.stringResource(SYMR.strings.favorites_sync_initializing)) data object Idle : FavoritesSyncStatus()
@Serializable
sealed class BadLibraryState : FavoritesSyncStatus() {
@Serializable
data class MangaInMultipleCategories(
val mangaId: Long,
val mangaTitle: String,
val categories: List<String>,
) : BadLibraryState()
} }
data class Processing( @Serializable
override val message: String, data object Initializing : FavoritesSyncStatus()
val title: String? = null, @Serializable
) : FavoritesSyncStatus() { sealed class Processing : FavoritesSyncStatus() {
constructor(message: String, isThrottle: Boolean, context: Context, title: String?) : data object VerifyingLibrary : Processing()
this( data object DownloadingFavorites : Processing()
if (isThrottle) { data object CalculatingRemoteChanges : Processing()
context.stringResource(SYMR.strings.favorites_sync_processing_throttle, message) data object CalculatingLocalChanges : Processing()
} else { data object SyncingCategoryNames : Processing()
message data class RemovingRemoteGalleries(val galleryCount: Int) : Processing()
}, data class AddingGalleryToRemote(
title, val index: Int,
) val total: Int,
val isThrottling: Boolean,
val delayedMessage get() = if (title != null) this.message + "\n\n" + title else null val title: String,
} ) : Processing()
data class CompleteWithErrors(val messages: List<String>) : FavoritesSyncStatus() { data class RemovingGalleryFromLocal(
override val message: String = messages.joinToString("\n") val index: Int,
val total: Int,
) : Processing()
data class AddingGalleryToLocal(
val index: Int,
val total: Int,
val isThrottling: Boolean,
val title: String,
) : Processing()
data object CleaningUp : Processing()
} }
@Serializable
data class CompleteWithErrors(val messages: List<SyncError.GallerySyncError>) : FavoritesSyncStatus()
} }

View File

@ -515,6 +515,7 @@
<string name="favorites_sync_removing_galleries">Removing %1$d galleries from remote server</string> <string name="favorites_sync_removing_galleries">Removing %1$d galleries from remote server</string>
<string name="favorites_sync_unable_to_delete">Unable to delete galleries from the remote servers!</string> <string name="favorites_sync_unable_to_delete">Unable to delete galleries from the remote servers!</string>
<string name="favorites_sync_adding_to_remote">Adding gallery %1$d of %2$d to remote server</string> <string name="favorites_sync_adding_to_remote">Adding gallery %1$d of %2$d to remote server</string>
<string name="favorites_sync_unable_to_add_to_remote">Unable to add gallery to remote server: '%1$s' (GID: %2$s)!</string>
<string name="favorites_sync_remove_from_local">Removing gallery %1$d of %2$d from local library</string> <string name="favorites_sync_remove_from_local">Removing gallery %1$d of %2$d from local library</string>
<string name="favorites_sync_add_to_local">Adding gallery %1$d of %2$d to local library</string> <string name="favorites_sync_add_to_local">Adding gallery %1$d of %2$d to local library</string>
<string name="favorites_sync_remote_not_exist">Remote gallery does not exist, skipping: %1$s!</string> <string name="favorites_sync_remote_not_exist">Remote gallery does not exist, skipping: %1$s!</string>