Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve device disconnect and reset behavior for K-Server #52

Merged
merged 2 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app-common/src/main/java/eu/darken/octi/common/debug/Bugs.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package eu.darken.octi.common.debug

import eu.darken.octi.common.BuildConfigWrap
import eu.darken.octi.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.octi.common.debug.logging.Logging.Priority.WARN
import eu.darken.octi.common.debug.logging.log
import eu.darken.octi.common.debug.logging.logTag

object Bugs {
var isDebug: Boolean = BuildConfigWrap.DEBUG
var reporter: AutomaticBugReporter? = null
fun report(exception: Exception) {
log(TAG, VERBOSE) { "Reporting $exception" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ class GDriveActionsFragment : BottomSheetDialogFragment2() {
setNegativeButton(R.string.general_cancel_action) { _, _ -> }
}.show()
}
ui.wipeAction.setOnClickListener {
ui.resetAction.setOnClickListener {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.sync_gdrive_wipe_confirmation_desc)
setPositiveButton(R.string.general_wipe_action) { _, _ ->
vm.wipe()
setMessage(R.string.sync_gdrive_reset_confirmation_desc)
setPositiveButton(R.string.general_reset_action) { _, _ ->
vm.reset()
}
setNegativeButton(R.string.general_cancel_action) { _, _ -> }
}.show()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ class GDriveActionsVM @Inject constructor(
popNavStack()
}

fun wipe() = launch {
log(TAG) { "wipe()" }
syncManager.wipe(navArgs.identifier)
fun reset() = launch {
log(TAG) { "reset()" }
syncManager.resetData(navArgs.identifier)
popNavStack()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ class JServerActionsFragment : BottomSheetDialogFragment2() {
}
}.show()
}
ui.wipeAction.setOnClickListener {
ui.resetAction.setOnClickListener {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.sync_jserver_wipe_confirmation_desc)
setPositiveButton(R.string.general_wipe_action) { _, _ ->
vm.wipe()
setMessage(R.string.sync_jserver_reset_confirmation_desc)
setPositiveButton(R.string.general_reset_action) { _, _ ->
vm.reset()
}
setNegativeButton(R.string.general_cancel_action) { _, _ ->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ class JServerActionsVM @Inject constructor(
navEvents.postValue(null)
}

fun wipe() = launch {
log(TAG) { "wipe()" }
syncManager.wipe(navArgs.identifier)
fun reset() = launch {
log(TAG) { "reset()" }
syncManager.resetData(navArgs.identifier)
navEvents.postValue(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ class KServerActionsFragment : BottomSheetDialogFragment2() {
}
}.show()
}
ui.wipeAction.setOnClickListener {
ui.resetAction.setOnClickListener {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.sync_kserver_wipe_confirmation_desc)
setPositiveButton(R.string.general_wipe_action) { _, _ ->
vm.wipe()
setMessage(R.string.sync_kserver_reset_confirmation_desc)
setPositiveButton(R.string.general_reset_action) { _, _ ->
vm.reset()
}
setNegativeButton(R.string.general_cancel_action) { _, _ ->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class KServerActionsVM @Inject constructor(
navEvents.postValue(null)
}

fun wipe() = launch {
log(TAG) { "wipe()" }
syncManager.wipe(navArgs.identifier)
fun reset() = launch {
log(TAG) { "reset()" }
syncManager.resetData(navArgs.identifier)
navEvents.postValue(null)
}

Expand Down
7 changes: 3 additions & 4 deletions app/src/main/res/layout/sync_actions_gdrive_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,11 @@
app:iconGravity="textStart" />

<com.google.android.material.button.MaterialButton
android:id="@+id/wipe_action"
style="@style/Widget.Material3.Button"
android:id="@+id/reset_action"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/general_wipe_action"
app:backgroundTint="@color/error"
android:text="@string/general_reset_action"
app:icon="@drawable/ic_baseline_delete_sweep_24"
app:iconGravity="textStart" />

Expand Down
7 changes: 3 additions & 4 deletions app/src/main/res/layout/sync_actions_jserver_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,11 @@
app:iconGravity="textStart" />

<com.google.android.material.button.MaterialButton
android:id="@+id/wipe_action"
style="@style/Widget.Material3.Button"
android:id="@+id/reset_action"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/general_wipe_action"
app:backgroundTint="@color/error"
android:text="@string/general_reset_action"
app:icon="@drawable/ic_baseline_delete_sweep_24"
app:iconGravity="textStart" />

Expand Down
7 changes: 3 additions & 4 deletions app/src/main/res/layout/sync_actions_kserver_fragment.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@
app:iconGravity="textStart" />

<com.google.android.material.button.MaterialButton
android:id="@+id/wipe_action"
style="@style/Widget.Material3.Button"
android:id="@+id/reset_action"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/general_wipe_action"
app:backgroundTint="@color/error"
android:text="@string/general_reset_action"
app:icon="@drawable/ic_baseline_delete_sweep_24"
app:iconGravity="textStart" />

Expand Down
5 changes: 2 additions & 3 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<string name="general_done_action">Listo</string>
<string name="general_copy_action">Copiar</string>
<string name="general_remove_action">Eliminar</string>
<string name="general_wipe_action">Limpiar</string>
<string name="general_cancel_action">Cancelar</string>
<string name="general_thanks_action">Gracias</string>
<string name="general_gotit_action">Entendido</string>
Expand Down Expand Up @@ -109,7 +108,7 @@
<string name="sync_gdrive_type_appdata_description">Los datos se colocan en Google Drive. Esta variante específicada utiliza el alcance "datos de la aplicación". Solo se otorga acceso a una nueva carpeta solo para esta aplicación. La carpeta no está visible en su disco. Es similar a cómo funcionan las copias de seguridad de WhatsApp.</string>
<string name="sync_gdrive_add_label">Agregar un nuevo Google Drive</string>
<string name="sync_gdrive_add_description">Haz clic en el botón de inicio de sesión a continuación para configurar el acceso a Google Drive.</string>
<string name="sync_gdrive_wipe_confirmation_desc">Restablezce todos los datos de Octi almacenados en Google Drive. Tu dispositivo permanece conectado a Google Drive.</string>
<string name="sync_gdrive_reset_confirmation_desc">Restablezce todos los datos de Octi almacenados en Google Drive. Tu dispositivo permanece conectado a Google Drive.</string>
<string name="sync_gdrive_disconnect_confirmation_desc">Cerrar la sesión en Google Drive y eliminar los datos de este dispositivo.</string>
<string name="sync_gdrive_error_no_account_on_device">Este dispositivo no está registrado en ninguna cuenta de Google.</string>

Expand All @@ -122,7 +121,7 @@
<string name="sync_jserver_about_desc">JServer es un servidor de sincronización de código abierto para Octi de Jakob Möller.</string>
<string name="sync_jserver_about_author_action">Autor</string>
<string name="sync_jserver_about_source_action">Código fuente</string>
<string name="sync_jserver_wipe_confirmation_desc">Restablece esta cuenta y elimine todos los datos almacenados. Tu dispositivo permanecerá vinculado a la cuenta.</string>
<string name="sync_jserver_reset_confirmation_desc">Restablece esta cuenta y elimine todos los datos almacenados. Tu dispositivo permanecerá vinculado a la cuenta.</string>
<string name="sync_jserver_disconnect_confirmation_desc">Esto eliminará este dispositivo de tu cuenta. Si este es el último dispositivo vinculado, tu cuenta será eliminada.</string>
<string name="sync_jserver_link_device_action">Vincular un nuevo dispositivo</string>
<string name="sync_jserver_link_code_label">Código del enlace</string>
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<string name="general_done_action">Done</string>
<string name="general_copy_action">Copy</string>
<string name="general_remove_action">Remove</string>
<string name="general_wipe_action">Wipe</string>
<string name="general_cancel_action">Cancel</string>
<string name="general_thanks_action">Thanks</string>
<string name="general_gotit_action">Got it</string>
Expand Down Expand Up @@ -82,6 +81,7 @@
<string name="device_uptime_x">Uptime: %s</string>
<string name="general_disconnect_action">Disconnect</string>
<string name="generic_info_action">Info</string>
<string name="general_reset_action">Reset</string>

<string name="sync_server_label">Server</string>
<string name="sync_account_label">Account</string>
Expand Down Expand Up @@ -115,7 +115,7 @@
<string name="sync_gdrive_type_appdata_description">Data is placed in Google Drive. This specific variant uses the "app-data" scope. Access is only granted to a new folder just for this app. The folder is not visible in your drive. It is similar to how WhatsApp backups work.</string>
<string name="sync_gdrive_add_label">Add new Google Drive</string>
<string name="sync_gdrive_add_description">Click the sign-in button below to setup Google Drive access.</string>
<string name="sync_gdrive_wipe_confirmation_desc">Reset all Octi data stored on Google Drive. Your device stays logged into Google Drive.</string>
<string name="sync_gdrive_reset_confirmation_desc">Reset all Octi data stored on Google Drive. Your device stays logged into Google Drive.</string>
<string name="sync_gdrive_disconnect_confirmation_desc">Log out from Google Drive and delete data related to this device.</string>
<string name="sync_gdrive_error_no_account_on_device">This device is not signed into any Google account.</string>

Expand All @@ -128,7 +128,7 @@
<string name="sync_jserver_about_desc">JServer is an open-source sync server for Octi by Jakob Möller.</string>
<string name="sync_jserver_about_author_action">Author</string>
<string name="sync_jserver_about_source_action">Source code</string>
<string name="sync_jserver_wipe_confirmation_desc">Reset this account and delete all stored data. Your device stays linked to the account.</string>
<string name="sync_jserver_reset_confirmation_desc">Reset this account and delete stored data. Your device stays linked to the account.</string>
<string name="sync_jserver_disconnect_confirmation_desc">This will remove this device from your account. If this is the last linked device, your account will be deleted.</string>
<string name="sync_jserver_link_device_action">Link new device</string>
<string name="sync_jserver_link_code_label">Link code</string>
Expand All @@ -154,7 +154,7 @@
<string name="sync_kserver_about_desc">KServer is an open-source sync server (in Kotlin) for Octi by darken.</string>
<string name="sync_kserver_about_author_action">Author</string>
<string name="sync_kserver_about_source_action">Source code</string>
<string name="sync_kserver_wipe_confirmation_desc">Reset this account and delete all stored data. Your device stays linked to the account.</string>
<string name="sync_kserver_reset_confirmation_desc">Clear device\'s data on this account. Devices stay linked but their stored data is removed.</string>
<string name="sync_kserver_disconnect_confirmation_desc">This will remove this device from your account. If this is the last linked device, your account will be deleted.</string>
<string name="sync_kserver_link_device_action">Link new device</string>
<string name="sync_kserver_link_code_label">Link code</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface SyncConnector {
/**
* Wipe all Octi data stored via this connector
*/
suspend fun deleteAll()
suspend fun resetData()

suspend fun deleteDevice(deviceId: DeviceId)

Expand Down
21 changes: 10 additions & 11 deletions sync-core/src/main/java/eu/darken/octi/sync/core/SyncManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -109,31 +109,30 @@ class SyncManager @Inject constructor(
log(TAG) { "write(data=$toWrite) done (${System.currentTimeMillis() - start}ms)" }
}

suspend fun wipe(identifier: ConnectorId) = withContext(NonCancellable) {
log(TAG) { "wipe(identifier=$identifier)" }
getConnectorById<SyncConnector>(identifier).first().deleteAll()
log(TAG) { "wipe(identifier=$identifier) done" }
suspend fun resetData(identifier: ConnectorId) = withContext(NonCancellable) {
log(TAG) { "resetData(identifier=$identifier)" }
getConnectorById<SyncConnector>(identifier).first().resetData()
log(TAG) { "resetData(identifier=$identifier) done" }
}

suspend fun disconnect(identifier: ConnectorId, wipe: Boolean = false) = withContext(NonCancellable) {
log(TAG) { "disconnect(identifier=$identifier, wipe=$wipe)" }
suspend fun disconnect(identifier: ConnectorId) = withContext(NonCancellable) {
log(TAG) { "disconnect(identifier=$identifier)" }

val connector = getConnectorById<SyncConnector>(identifier).first()

disabledConnectors.value = disabledConnectors.value + connector
disabledConnectors.value += connector

try {
hubs.first().filter { it.owns(identifier) }.forEach {
it.remove(identifier)
}
if (wipe) connector.deleteAll()
} catch (e: Exception) {
log(TAG, ERROR) { "disconnect($identifier,$wipe=$wipe) failed: ${e.asLog()}" }
log(TAG, ERROR) { "disconnect($identifier) failed: ${e.asLog()}" }
} finally {
disabledConnectors.value = disabledConnectors.value - connector
disabledConnectors.value -= connector
}

log(TAG) { "disconnect(connector=$connector, wipe=$wipe) done" }
log(TAG) { "disconnect(connector=$connector) done" }
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,41 @@ import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import eu.darken.octi.common.coroutine.AppScope
import eu.darken.octi.common.coroutine.DispatcherProvider
import eu.darken.octi.common.debug.logging.Logging.Priority.*
import eu.darken.octi.common.debug.logging.Logging.Priority.DEBUG
import eu.darken.octi.common.debug.logging.Logging.Priority.ERROR
import eu.darken.octi.common.debug.logging.Logging.Priority.INFO
import eu.darken.octi.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.octi.common.debug.logging.Logging.Priority.WARN
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.DynamicStateFlow
import eu.darken.octi.common.flow.setupCommonEventHandlers
import eu.darken.octi.common.network.NetworkStateProvider
import eu.darken.octi.module.core.ModuleId
import eu.darken.octi.sync.core.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import eu.darken.octi.sync.core.ConnectorId
import eu.darken.octi.sync.core.DeviceId
import eu.darken.octi.sync.core.SyncConnector
import eu.darken.octi.sync.core.SyncConnectorState
import eu.darken.octi.sync.core.SyncOptions
import eu.darken.octi.sync.core.SyncRead
import eu.darken.octi.sync.core.SyncWrite
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.time.Instant
import kotlin.math.max

Expand Down Expand Up @@ -88,8 +110,8 @@ class GDriveAppDataConnector @AssistedInject constructor(
writeQueue.emit(toWrite)
}

override suspend fun deleteAll() {
log(TAG, INFO) { "deleteAll()" }
override suspend fun resetData() {
log(TAG, INFO) { "resetData()" }
writeAction {
appDataRoot()
.listFiles()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,43 @@ import eu.darken.octi.common.collections.fromGzip
import eu.darken.octi.common.collections.toGzip
import eu.darken.octi.common.coroutine.AppScope
import eu.darken.octi.common.coroutine.DispatcherProvider
import eu.darken.octi.common.debug.logging.Logging.Priority.*
import eu.darken.octi.common.debug.logging.Logging.Priority.DEBUG
import eu.darken.octi.common.debug.logging.Logging.Priority.ERROR
import eu.darken.octi.common.debug.logging.Logging.Priority.INFO
import eu.darken.octi.common.debug.logging.Logging.Priority.VERBOSE
import eu.darken.octi.common.debug.logging.Logging.Priority.WARN
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.DynamicStateFlow
import eu.darken.octi.common.flow.setupCommonEventHandlers
import eu.darken.octi.common.network.NetworkStateProvider
import eu.darken.octi.module.core.ModuleId
import eu.darken.octi.sync.core.*
import eu.darken.octi.sync.core.ConnectorId
import eu.darken.octi.sync.core.DeviceId
import eu.darken.octi.sync.core.SyncConnector
import eu.darken.octi.sync.core.SyncConnectorState
import eu.darken.octi.sync.core.SyncOptions
import eu.darken.octi.sync.core.SyncRead
import eu.darken.octi.sync.core.SyncSettings
import eu.darken.octi.sync.core.SyncWrite
import eu.darken.octi.sync.core.encryption.PayloadEncryption
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.retry
import kotlinx.coroutines.plus
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import okio.ByteString
import retrofit2.HttpException
import java.time.Duration
Expand Down Expand Up @@ -101,13 +124,13 @@ class JServerConnector @AssistedInject constructor(
writeQueue.emit(toWrite)
}

override suspend fun deleteAll() = writeServerWrapper {
log(TAG, INFO) { "deleteAll()" }
override suspend fun resetData() = writeServerWrapper {
log(TAG, INFO) { "resetData()" }
val deviceIds = endpoint.listDevices()
log(TAG, VERBOSE) { "deleteAll(): Found devices: $deviceIds" }
log(TAG, VERBOSE) { "resetData(): Found devices: $deviceIds" }

deviceIds.forEach {
log(TAG, VERBOSE) { "deleteAll(): Deleting module data for $it" }
log(TAG, VERBOSE) { "resetData(): Deleting module data for $it" }
try {
endpoint.deleteModules(it)
} catch (e: Exception) {
Expand Down
Loading
Loading