diff --git a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt index 634780f9c938b0e62cdcee96d2b11d093474e63f..9501212b6c92e77f1d8d40e012dd44af79a6503c 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/DesktopModule.kt @@ -32,6 +32,7 @@ import org.briarproject.bramble.util.OsUtils.isLinux import org.briarproject.bramble.util.OsUtils.isMac import org.briarproject.briar.desktop.ui.BriarUi import org.briarproject.briar.desktop.ui.BriarUiImpl +import org.briarproject.briar.desktop.viewmodel.UiExecutor import org.briarproject.briar.desktop.viewmodel.ViewModelModule import java.io.File import java.nio.file.Path @@ -75,9 +76,12 @@ internal class DesktopModule( @Provides @Singleton @EventExecutor - fun provideEventExecutor(): Executor { - return Dispatchers.Swing.asExecutor() - } + fun provideEventExecutor(): Executor = provideUiExecutor() + + @Provides + @Singleton + @UiExecutor + fun provideUiExecutor(): Executor = Dispatchers.Swing.asExecutor() @Provides @TorDirectory diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt index cf26aea936405ab5111b443068d6462939197ceb..3e9063e88c16213b63a53f0956477e697788470d 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactListViewModel.kt @@ -7,11 +7,16 @@ import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.event.ContactAliasChangedEvent +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.api.conversation.event.ConversationMessageTrackedEvent import org.briarproject.briar.desktop.conversation.ConversationMessagesReadEvent +import org.briarproject.briar.desktop.viewmodel.UiExecutor +import java.util.concurrent.Executor import javax.inject.Inject class ContactListViewModel @@ -20,8 +25,14 @@ constructor( contactManager: ContactManager, conversationManager: ConversationManager, connectionRegistry: ConnectionRegistry, + @UiExecutor uiExecutor: Executor, + @DatabaseExecutor dbExecutor: Executor, + lifecycleManager: LifecycleManager, + db: TransactionManager, eventBus: EventBus, -) : ContactsViewModel(contactManager, conversationManager, connectionRegistry, eventBus) { +) : ContactsViewModel( + contactManager, conversationManager, connectionRegistry, uiExecutor, dbExecutor, lifecycleManager, db, eventBus +) { companion object { private val LOG = KotlinLogging.logger {} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt index 46a6c68684cdc70754470313ec1659a4c83a2a2c..edff9df35df560a8d13e145651ab2fda83f75007 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/contact/ContactsViewModel.kt @@ -7,21 +7,30 @@ import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.event.ContactAddedEvent import org.briarproject.bramble.api.contact.event.ContactRemovedEvent +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.desktop.utils.removeFirst import org.briarproject.briar.desktop.utils.replaceFirst -import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel +import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel +import org.briarproject.briar.desktop.viewmodel.UiExecutor +import java.util.concurrent.Executor abstract class ContactsViewModel( protected val contactManager: ContactManager, private val conversationManager: ConversationManager, private val connectionRegistry: ConnectionRegistry, + @UiExecutor uiExecutor: Executor, + @DatabaseExecutor dbExecutor: Executor, + lifecycleManager: LifecycleManager, + db: TransactionManager, eventBus: EventBus, -) : BriarEventListenerViewModel(eventBus) { +) : EventListenerDbViewModel(uiExecutor, dbExecutor, lifecycleManager, db, eventBus) { companion object { private val LOG = KotlinLogging.logger {} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt index 79e76b2c13219fd237d0c5af753091aa839a2908..e8fd64aad690255b0631d4f4db29cc10faa9822a 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/conversation/ConversationViewModel.kt @@ -10,10 +10,13 @@ import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.contact.ContactId import org.briarproject.bramble.api.contact.ContactManager import org.briarproject.bramble.api.contact.event.ContactRemovedEvent +import org.briarproject.bramble.api.db.DatabaseExecutor import org.briarproject.bramble.api.db.DbException import org.briarproject.bramble.api.db.NoSuchContactException +import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.Event import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent import org.briarproject.bramble.api.sync.MessageId @@ -33,8 +36,10 @@ import org.briarproject.briar.desktop.contact.ContactItem import org.briarproject.briar.desktop.utils.KLoggerUtils.logDuration import org.briarproject.briar.desktop.utils.replaceIf import org.briarproject.briar.desktop.utils.replaceIfIndexed -import org.briarproject.briar.desktop.viewmodel.BriarEventListenerViewModel +import org.briarproject.briar.desktop.viewmodel.EventListenerDbViewModel +import org.briarproject.briar.desktop.viewmodel.UiExecutor import java.util.Date +import java.util.concurrent.Executor import javax.inject.Inject class ConversationViewModel @@ -45,8 +50,12 @@ constructor( private val conversationManager: ConversationManager, private val messagingManager: MessagingManager, private val privateMessageFactory: PrivateMessageFactory, + @UiExecutor uiExecutor: Executor, + @DatabaseExecutor dbExecutor: Executor, + lifecycleManager: LifecycleManager, + db: TransactionManager, private val eventBus: EventBus, -) : BriarEventListenerViewModel(eventBus) { +) : EventListenerDbViewModel(uiExecutor, dbExecutor, lifecycleManager, db, eventBus) { companion object { private val LOG = KotlinLogging.logger {} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt index c57773fb6156f3273b9e5da884c63d987a2cae93..4dbbc93e42e0e5ab2712030cffa9a09613636b1e 100644 --- a/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt +++ b/src/main/kotlin/org/briarproject/briar/desktop/introduction/IntroductionViewModel.kt @@ -4,10 +4,15 @@ import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import org.briarproject.bramble.api.connection.ConnectionRegistry import org.briarproject.bramble.api.contact.ContactManager +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.TransactionManager import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.lifecycle.LifecycleManager import org.briarproject.briar.api.conversation.ConversationManager import org.briarproject.briar.desktop.contact.ContactItem import org.briarproject.briar.desktop.contact.ContactsViewModel +import org.briarproject.briar.desktop.viewmodel.UiExecutor +import java.util.concurrent.Executor import javax.inject.Inject class IntroductionViewModel @@ -16,8 +21,14 @@ constructor( contactManager: ContactManager, conversationManager: ConversationManager, connectionRegistry: ConnectionRegistry, + @UiExecutor uiExecutor: Executor, + @DatabaseExecutor dbExecutor: Executor, + lifecycleManager: LifecycleManager, + db: TransactionManager, eventBus: EventBus, -) : ContactsViewModel(contactManager, conversationManager, connectionRegistry, eventBus) { +) : ContactsViewModel( + contactManager, conversationManager, connectionRegistry, uiExecutor, dbExecutor, lifecycleManager, db, eventBus +) { private val _firstContact = mutableStateOf<ContactItem?>(null) private val _secondContact = mutableStateOf<ContactItem?>(null) diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarEventListenerViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarEventListenerViewModel.kt deleted file mode 100644 index 7c7b2908fd97c9d4e399959c0d2bf21874f4ac00..0000000000000000000000000000000000000000 --- a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/BriarEventListenerViewModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.briarproject.briar.desktop.viewmodel - -import org.briarproject.bramble.api.event.EventBus -import org.briarproject.bramble.api.event.EventListener - -abstract class BriarEventListenerViewModel( - private val eventBus: EventBus -) : ViewModel, EventListener { - - override fun onInit() { - eventBus.addListener(this) - } - - override fun onCleared() { - eventBus.removeListener(this) - } -} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..8c4c5d361d893d22cdcf3fb8c98b833796ccb20a --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/DbViewModel.kt @@ -0,0 +1,94 @@ +package org.briarproject.briar.desktop.viewmodel + +import mu.KotlinLogging +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.DbCallable +import org.briarproject.bramble.api.db.DbException +import org.briarproject.bramble.api.db.DbRunnable +import org.briarproject.bramble.api.db.TransactionManager +import org.briarproject.bramble.api.event.EventListener +import org.briarproject.bramble.api.lifecycle.LifecycleManager +import java.util.concurrent.Executor + +abstract class DbViewModel( + @UiExecutor private val uiExecutor: Executor, + @DatabaseExecutor private val dbExecutor: Executor, + private val lifecycleManager: LifecycleManager, + private val db: TransactionManager +) : ViewModel, EventListener { + + companion object { + private val LOG = KotlinLogging.logger {} + } + + /** + * Waits for the DB to open and runs the given task on the [DatabaseExecutor]. + * + * The [Runnable] has to handle all potential exceptions, e.g. [DbException]s, + * in a thread-safe way itself. + * For convenience, consider using [runOnDbThreadWithTransaction] instead. + */ + protected fun runOnDbThread(task: Runnable) { + dbExecutor.execute { + try { + lifecycleManager.waitForDatabase() + task.run() + } catch (e: InterruptedException) { + LOG.warn("Interrupted while waiting for database") + Thread.currentThread().interrupt() + } + } + } + + /** + * Waits for the DB to open and runs the given task on the [DatabaseExecutor]. + * + * All exceptions thrown inside the [DbRunnable] are passed to the [UiExecutor] + * using the [onError] callback. + */ + protected fun runOnDbThreadWithTransaction( + readOnly: Boolean, + task: DbRunnable<Exception>, + @UiExecutor onError: (Exception) -> Unit + ) { + dbExecutor.execute { + try { + lifecycleManager.waitForDatabase() + db.transaction(readOnly, task) + } catch (e: InterruptedException) { + LOG.warn("Interrupted while waiting for database") + Thread.currentThread().interrupt() + } catch (e: Exception) { + uiExecutor.execute { onError(e) } + } + } + } + + /** + * Waits for the DB to open and runs the given task on the [DatabaseExecutor], + * providing the result to the [onResult] callback in the UI thread. + * + * All exceptions thrown inside the [DbRunnable] are passed to the [UiExecutor] + * using the [onError] callback. + */ + protected fun <T> loadOnDbThreadWithTransaction( + task: DbCallable<T, Exception>, + @UiExecutor onResult: (T) -> Unit, + @UiExecutor onError: (Exception) -> Unit + ) { + dbExecutor.execute { + try { + lifecycleManager.waitForDatabase() + db.transaction<Exception>(true) { txn -> + val t = task.call(txn) + txn.attach { onResult(t) } + } + } catch (e: InterruptedException) { + LOG.warn("Interrupted while waiting for database") + Thread.currentThread().interrupt() + } catch (e: Exception) { + uiExecutor.execute { onError(e) } + } + } + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..166d6ff3ff4167e4c159abf08120e01caf1f6954 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/EventListenerDbViewModel.kt @@ -0,0 +1,25 @@ +package org.briarproject.briar.desktop.viewmodel + +import org.briarproject.bramble.api.db.DatabaseExecutor +import org.briarproject.bramble.api.db.TransactionManager +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.bramble.api.event.EventListener +import org.briarproject.bramble.api.lifecycle.LifecycleManager +import java.util.concurrent.Executor + +abstract class EventListenerDbViewModel( + @UiExecutor uiExecutor: Executor, + @DatabaseExecutor dbExecutor: Executor, + lifecycleManager: LifecycleManager, + db: TransactionManager, + private val eventBus: EventBus +) : EventListener, DbViewModel(uiExecutor, dbExecutor, lifecycleManager, db) { + + override fun onInit() { + eventBus.addListener(this) + } + + override fun onCleared() { + eventBus.removeListener(this) + } +} diff --git a/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt new file mode 100644 index 0000000000000000000000000000000000000000..e8e19ef8374bf92c07d5bd9b8e1ee19962e3c7d4 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/desktop/viewmodel/UiExecutor.kt @@ -0,0 +1,18 @@ +package org.briarproject.briar.desktop.viewmodel + +import javax.inject.Qualifier + +/** + * Annotation for injecting the executor for tasks that should be run on the UI thread. + * Also used for annotating methods that should run on the UI executor. + */ +@Qualifier +@Target( + AnnotationTarget.FIELD, + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER, + AnnotationTarget.VALUE_PARAMETER +) +@Retention(AnnotationRetention.RUNTIME) +annotation class UiExecutor diff --git a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt index cad4b989717dd9430d352a158b39542f4a37ad97..121ed1a98396050c0f5a66de6dc76688c8301d55 100644 --- a/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt +++ b/src/test/kotlin/org/briarproject/briar/desktop/DesktopTestModule.kt @@ -2,9 +2,13 @@ package org.briarproject.briar.desktop import dagger.Module import dagger.Provides +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.swing.Swing import org.briarproject.bramble.account.AccountModule import org.briarproject.bramble.api.FeatureFlags import org.briarproject.bramble.api.db.DatabaseConfig +import org.briarproject.bramble.api.event.EventExecutor import org.briarproject.bramble.api.plugin.PluginConfig import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_CONTROL_PORT import org.briarproject.bramble.api.plugin.TorConstants.DEFAULT_SOCKS_PORT @@ -15,7 +19,6 @@ import org.briarproject.bramble.api.plugin.TransportId import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory import org.briarproject.bramble.battery.DefaultBatteryManagerModule -import org.briarproject.bramble.event.DefaultEventExecutorModule import org.briarproject.bramble.network.JavaNetworkModule import org.briarproject.bramble.plugin.tor.CircumventionModule import org.briarproject.bramble.plugin.tor.UnixTorPluginFactory @@ -32,11 +35,13 @@ import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreator import org.briarproject.briar.desktop.testdata.DeterministicTestDataCreatorImpl import org.briarproject.briar.desktop.ui.BriarUi import org.briarproject.briar.desktop.ui.BriarUiImpl +import org.briarproject.briar.desktop.viewmodel.UiExecutor import org.briarproject.briar.desktop.viewmodel.ViewModelModule import org.briarproject.briar.test.TestModule import java.io.File import java.nio.file.Path import java.util.Collections.emptyList +import java.util.concurrent.Executor import javax.inject.Singleton @Module( @@ -45,7 +50,6 @@ import javax.inject.Singleton CircumventionModule::class, ClockModule::class, DefaultBatteryManagerModule::class, - DefaultEventExecutorModule::class, DefaultTaskSchedulerModule::class, DefaultWakefulIoExecutorModule::class, DesktopSecureRandomModule::class, @@ -74,6 +78,16 @@ internal class DesktopTestModule( return DesktopDatabaseConfig(dbDir, keyDir) } + @Provides + @Singleton + @EventExecutor + fun provideEventExecutor(): Executor = provideUiExecutor() + + @Provides + @Singleton + @UiExecutor + fun provideUiExecutor(): Executor = Dispatchers.Swing.asExecutor() + @Provides @TorDirectory internal fun provideTorDirectory(): File {