From afe311517ba49b76afa965988ce75910dbf7ecdb Mon Sep 17 00:00:00 2001 From: darken Date: Thu, 29 Aug 2024 16:53:51 +0200 Subject: [PATCH] Handle lock-out for Google Drive and show a user readable error --- .../gdrive/ui/actions/GDriveActionsVM.kt | 19 +++++++++---------- .../eu/darken/octi/sync/core/SyncManager.kt | 18 ++++++++++++++++-- .../octi/syncs/gdrive/core/GDriveHub.kt | 9 ++++++++- .../gdrive/core/UserLockedOutException.kt | 16 ++++++++++++++++ syncs-gdrive/src/main/res/values/strings.xml | 2 ++ 5 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/UserLockedOutException.kt diff --git a/app/src/main/java/eu/darken/octi/syncs/gdrive/ui/actions/GDriveActionsVM.kt b/app/src/main/java/eu/darken/octi/syncs/gdrive/ui/actions/GDriveActionsVM.kt index 1a7d5849..9f552462 100644 --- a/app/src/main/java/eu/darken/octi/syncs/gdrive/ui/actions/GDriveActionsVM.kt +++ b/app/src/main/java/eu/darken/octi/syncs/gdrive/ui/actions/GDriveActionsVM.kt @@ -8,6 +8,7 @@ import eu.darken.octi.common.debug.logging.logTag import eu.darken.octi.common.navigation.navArgs import eu.darken.octi.common.uix.ViewModel3 import eu.darken.octi.sync.core.getConnectorById +import eu.darken.octi.syncs.gdrive.core.GDriveAppDataConnector import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -25,16 +26,14 @@ class GDriveActionsVM @Inject constructor( val account: eu.darken.octi.syncs.gdrive.core.GoogleAccount ) - val state = - syncManager.getConnectorById(navArgs.identifier) - .map { - State(it.account) - } - .catch { - if (it is NoSuchElementException) popNavStack() - else throw it - } - .asLiveData2() + val state = syncManager.getConnectorById(navArgs.identifier) + .map { + State(it.account) + } + .catch { + if (it is NoSuchElementException) popNavStack() else throw it + } + .asLiveData2() fun disconnct() = launch { log(TAG) { "disconnct()" } diff --git a/sync-core/src/main/java/eu/darken/octi/sync/core/SyncManager.kt b/sync-core/src/main/java/eu/darken/octi/sync/core/SyncManager.kt index 90c1da8c..03b01f1b 100644 --- a/sync-core/src/main/java/eu/darken/octi/sync/core/SyncManager.kt +++ b/sync-core/src/main/java/eu/darken/octi/sync/core/SyncManager.kt @@ -9,8 +9,21 @@ import eu.darken.octi.common.debug.logging.logTag import eu.darken.octi.common.flow.setupCommonEventHandlers import eu.darken.octi.common.flow.shareLatest import eu.darken.octi.sync.core.cache.SyncCache -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.plus +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -128,6 +141,7 @@ class SyncManager @Inject constructor( } } catch (e: Exception) { log(TAG, ERROR) { "disconnect($identifier) failed: ${e.asLog()}" } + throw e } finally { disabledConnectors.value -= connector } diff --git a/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/GDriveHub.kt b/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/GDriveHub.kt index fd73b300..28c26787 100644 --- a/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/GDriveHub.kt +++ b/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/GDriveHub.kt @@ -1,14 +1,18 @@ package eu.darken.octi.syncs.gdrive.core +import com.google.android.gms.auth.UserRecoverableAuthException import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException import eu.darken.octi.common.coroutine.AppScope import eu.darken.octi.common.coroutine.DispatcherProvider +import eu.darken.octi.common.debug.logging.Logging.Priority.ERROR +import eu.darken.octi.common.debug.logging.asLog import eu.darken.octi.common.debug.logging.log import eu.darken.octi.common.debug.logging.logTag import eu.darken.octi.common.flow.setupCommonEventHandlers import eu.darken.octi.common.flow.shareLatest import eu.darken.octi.sync.core.ConnectorHub import eu.darken.octi.sync.core.ConnectorId +import eu.darken.octi.sync.core.SyncConnector import eu.darken.octi.sync.core.SyncSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.NonCancellable @@ -36,7 +40,7 @@ class GDriveHub @Inject constructor( .setupCommonEventHandlers(TAG) { "connectors" } .shareLatest(scope + dispatcherProvider.Default) - override val connectors: Flow> = _connectors + override val connectors: Flow> = _connectors override suspend fun owns(connectorId: ConnectorId): Boolean { return _connectors.first().any { it.identifier == connectorId } @@ -46,9 +50,12 @@ class GDriveHub @Inject constructor( log(TAG) { "remove(id=$connectorId)" } val connector = _connectors.first().single { it.identifier == connectorId } try { + throw UserRecoverableAuthIOException(UserRecoverableAuthException("", null)) connector.deleteDevice(syncSettings.deviceId) } catch (e: UserRecoverableAuthIOException) { // User was locked out + log(TAG, ERROR) { "Failed to delete device, access was locked out:\n${e.asLog()}" } + throw UserLockedOutException(e) } accountRepo.remove(connector.account.id) } diff --git a/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/UserLockedOutException.kt b/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/UserLockedOutException.kt new file mode 100644 index 00000000..d9763b52 --- /dev/null +++ b/syncs-gdrive/src/main/java/eu/darken/octi/syncs/gdrive/core/UserLockedOutException.kt @@ -0,0 +1,16 @@ +package eu.darken.octi.syncs.gdrive.core + +import android.content.Context +import eu.darken.octi.common.error.HasLocalizedError +import eu.darken.octi.common.error.LocalizedError +import eu.darken.octi.syncs.gdrive.R + +class UserLockedOutException( + cause: Exception, +) : IllegalStateException(cause), HasLocalizedError { + override fun getLocalizedError(context: Context): LocalizedError = LocalizedError( + throwable = this, + label = context.getString(R.string.gdrive_account_lockout_error_label), + description = context.getString(R.string.gdrive_account_lockout_error_description) + ) +} \ No newline at end of file diff --git a/syncs-gdrive/src/main/res/values/strings.xml b/syncs-gdrive/src/main/res/values/strings.xml index 3bc4a3bb..26bb2838 100644 --- a/syncs-gdrive/src/main/res/values/strings.xml +++ b/syncs-gdrive/src/main/res/values/strings.xml @@ -1,4 +1,6 @@ Permission error Octi has not been granted the required access scope. Try again and check that permissions are being granted. + Lock out error + This device is locked out and can\'t access Google Drive anymore. It\'s access to your Google account has been revoked. Re-grant access and try again. \ No newline at end of file