diff --git a/build.gradle.kts b/build.gradle.kts index 10d1dab29ba1a521d92a7f756572275c71131820..006fc3bb7e2fcd38aedbbf8984a81b849721e73b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,8 +22,11 @@ repositories { dependencies { testImplementation(kotlin("test-testng")) implementation(compose.desktop.currentOs) + implementation("com.fasterxml.jackson.core:jackson-databind:2.10.0") implementation(project(path = ":briar:briar-core", configuration = "default")) implementation(project(path = ":briar:bramble-java", configuration = "default")) + val daggerVersion = "2.24" + kapt("com.google.dagger:dagger-compiler:$daggerVersion") } tasks.test { diff --git a/src/main/kotlin/main.kt b/src/main/kotlin/main.kt deleted file mode 100644 index 2faebd09932f5078d3b79fec926e1605ebf7bd72..0000000000000000000000000000000000000000 --- a/src/main/kotlin/main.kt +++ /dev/null @@ -1,52 +0,0 @@ -import androidx.compose.desktop.Window -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Text -import androidx.compose.material.Button -import androidx.compose.material.MaterialTheme -import androidx.compose.material.MaterialTheme.typography -import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.imageResource -import androidx.compose.ui.res.svgResource -import androidx.compose.ui.res.vectorXmlResource -import androidx.compose.ui.unit.dp - -fun main() = Window { - Column( - modifier = Modifier.padding(16.dp) - ) { - TheImage() - Spacer(Modifier.height(32.dp)) - TheText() - TheButton() - } -} - -@Composable -private fun TheButton() { - var text by remember { mutableStateOf("Start chatting") } - Button(onClick = { - text = "Sorry, not yet available" - }) { - Text(text) - } -} - -@Composable -private fun TheImage() { - Image( - painter = svgResource("images/logo_circle.svg"), - contentDescription = "Briar logo", - modifier = Modifier - .fillMaxWidth() - .clip(shape = RoundedCornerShape(400.dp)) - ) -} - -@Composable -private fun TheText() { - Text("Welcome to Briar") -} \ No newline at end of file diff --git a/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt b/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt new file mode 100644 index 0000000000000000000000000000000000000000..9a5912a716ec8774ec79bfd920785eb66fd815db --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/BriarService.kt @@ -0,0 +1,81 @@ +package org.briarproject.briar.compose + +//import com.github.ajalt.clikt.core.UsageError +//import com.github.ajalt.clikt.output.TermUi.echo +import org.briarproject.bramble.api.account.AccountManager +import org.briarproject.bramble.api.crypto.DecryptionException +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator +import org.briarproject.bramble.api.lifecycle.LifecycleManager +//import org.briarproject.briar.swing.AccountUtil.Companion.check +//import org.briarproject.briar.swing.dialogs.NewAccountPrompt +//import org.briarproject.briar.swing.dialogs.PasswordPrompt +import javax.annotation.concurrent.Immutable +import javax.inject.Inject +import javax.inject.Singleton +import javax.swing.JOptionPane +import kotlin.system.exitProcess + +interface BriarService { + fun start() + fun stop() +} + +@Immutable +@Singleton +internal class BriarServiceImpl +@Inject +constructor( + private val accountManager: AccountManager, + private val lifecycleManager: LifecycleManager, + private val passwordStrengthEstimator: PasswordStrengthEstimator +) : BriarService { + + override fun start() { + if (!accountManager.accountExists()) { + createAccount(); + } else { + while (true) { +// val password = PasswordPrompt.promptForPassword(); +// if (!password.isValid) { +// // this happens when dismissing the dialog or clicking 'cancel' +// exitProcess(1) +// } + val password = "sdifjasdjhfksjadf" + try { + accountManager.signIn(password) + break + } catch (e: DecryptionException) { + JOptionPane.showMessageDialog( + null, "Wrong password", + "Error", JOptionPane.ERROR_MESSAGE + ) + } + } + } + val dbKey = accountManager.databaseKey ?: throw AssertionError() + lifecycleManager.startServices(dbKey) + lifecycleManager.waitForStartup() + } + + override fun stop() { + lifecycleManager.stopServices() + lifecycleManager.waitForShutdown() + } + + private fun createAccount() { +// echo("No account found. Let's create one!\n\n") +// val result = NewAccountPrompt.promptForDetails(); +// if (!result.isValid) { +// echo("Error: Please enter a username and password") +// exitProcess(1) +// } +// try { +// check(passwordStrengthEstimator, result) +// } catch (e: UsageError) { +// return; +// } + + accountManager.createAccount("Nico", "sdifjasdjhfksjadf") + } + +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt b/src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt new file mode 100644 index 0000000000000000000000000000000000000000..74737afbc7c2dea9efb8d94cf437e3309ae5fa00 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/BriarSwingApp.kt @@ -0,0 +1,25 @@ +package org.briarproject.briar.compose + +import dagger.Component +import org.briarproject.bramble.BrambleCoreEagerSingletons +import org.briarproject.bramble.BrambleCoreModule +import org.briarproject.briar.BriarCoreEagerSingletons +import org.briarproject.briar.BriarCoreModule +import java.security.SecureRandom +import javax.inject.Singleton + +@Component( + modules = [ + BrambleCoreModule::class, + BriarCoreModule::class, + SwingModule::class + ] +) +@Singleton +internal interface BriarSwingApp : BrambleCoreEagerSingletons, BriarCoreEagerSingletons { + + fun getUI(): UI + + fun getSecureRandom(): SecureRandom + +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/MainUI.java b/src/main/kotlin/org/briarproject/briar/compose/MainUI.java new file mode 100644 index 0000000000000000000000000000000000000000..6b33d76cb2e14ffe96b835e600e233c0213fdc70 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/MainUI.java @@ -0,0 +1,215 @@ +package org.briarproject.briar.compose; + +import org.briarproject.bramble.api.account.AccountManager; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.event.ContactAddedEvent; +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.briar.api.conversation.ConversationManager; +import org.briarproject.briar.api.conversation.ConversationMessageHeader; +import org.briarproject.briar.api.introduction.IntroductionManager; +import org.briarproject.briar.api.messaging.MessagingManager; +import org.briarproject.briar.api.messaging.PrivateMessageFactory; +//import org.briarproject.briar.swing.actions.AboutAction; +//import org.briarproject.briar.swing.actions.AddContactAction; +//import org.briarproject.briar.swing.actions.ChangePasswordAction; +//import org.briarproject.briar.swing.actions.CloseChatAction; +//import org.briarproject.briar.swing.actions.QuitAction; +//import org.briarproject.briar.swing.actions.SettingsAction; +//import org.briarproject.briar.swing.chat.Chat; +import org.briarproject.briar.compose.config.Configuration; +//import org.briarproject.briar.swing.contactlist.ContactList; +//import org.briarproject.briar.swing.contactlist.ContactListModel; +//import org.briarproject.briar.swing.contactlist.ContactWithMeta; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.BorderLayout; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.util.Collection; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JToolBar; +import javax.swing.UIManager; + +//import de.topobyte.awt.util.GridBagConstraintsEditor; + +public class MainUI extends JFrame implements EventListener { + + final static Logger logger = LoggerFactory.getLogger(MainUI.class); + + private final BriarService briarService; + private final AccountManager accountManager; + private final ContactManager contactManager; + private final ConversationManager conversationManager; + private final MessagingManager messagingManager; + private final IntroductionManager introductionManager; + private final PrivateMessageFactory privateMessageFactory; + private final EventBus eventBus; + private final PasswordStrengthEstimator passwordStrengthEstimator; + private final Configuration configuration; + + private JPanel mainPanel; + private JPanel contentPanel; +// private ContactList contactList; +// private Chat chat; + +// private ContactListModel contactListModel; + + public MainUI(BriarService briarService, + AccountManager accountManager, + ContactManager contactManager, + ConversationManager conversationManager, + MessagingManager messagingManager, + IntroductionManager introductionManager, + PrivateMessageFactory privateMessageFactory, + EventBus eventBus, + PasswordStrengthEstimator passwordStrengthEstimator, + Configuration configuration) { + this.briarService = briarService; + this.accountManager = accountManager; + this.contactManager = contactManager; + this.conversationManager = conversationManager; + this.messagingManager = messagingManager; + this.introductionManager = introductionManager; + this.privateMessageFactory = privateMessageFactory; + this.eventBus = eventBus; + this.passwordStrengthEstimator = passwordStrengthEstimator; + this.configuration = configuration; + + String lookAndFeel = configuration.getLookAndFeel(); + if (lookAndFeel != null) { + try { + UIManager.setLookAndFeel(lookAndFeel); + } catch (Exception e) { + logger.error("error while setting look and feel", e); + } + } + + setTitle("Briar Swing"); +// setJMenuBar(new MainMenu(this)); + + JToolBar toolbar = new JToolBar(); + toolbar.setFloatable(false); +// toolbar.add(new AddContactAction(this)); +// toolbar.add(new ChangePasswordAction(this)); +// toolbar.add(new SettingsAction(this)); +// toolbar.add(new CloseChatAction(this)); +// toolbar.add(new QuitAction()); +// toolbar.add(new AboutAction(this)); + + mainPanel = new JPanel(new BorderLayout()); + setContentPane(mainPanel); + + contentPanel = new JPanel(new GridBagLayout()); + addComponents(); + + mainPanel.add(toolbar, BorderLayout.NORTH); + mainPanel.add(contentPanel, BorderLayout.CENTER); + +// EventBusConnector eventBusConnector = +// new EventBusConnector(messagingManager); +// eventBus.addListener(eventBusConnector); +// eventBus.addListener(this); + } + + public Configuration getConfiguration() { + return configuration; + } + + public AccountManager getAccountManager() { + return accountManager; + } + + public ContactManager getContactManager() { + return contactManager; + } + + public PasswordStrengthEstimator getPasswordStrengthEstimator() { + return passwordStrengthEstimator; + } + + private void addComponents() { + GridBagConstraints c = new GridBagConstraints(); +// GridBagConstraintsEditor ce = +// new GridBagConstraintsEditor(c); +// +// contactList = new ContactList(); +// chat = new Chat(conversationManager, messagingManager, +// introductionManager, privateMessageFactory, eventBus); +// +// ce.fill(GridBagConstraints.BOTH); +// +// ce.gridPos(0, 0); +// ce.weight(3, 1); +// contentPanel.add(contactList, c); +// +// ce.gridPos(1, 0); +// ce.weight(7, 1); +// contentPanel.add(chat, c); +// +// reloadContactList(); +// +// contactList.addContactSelectedListener(this::displayChatHistory); + } + + public void closeChat() { +// chat.displayNone(); +// contactList.selectNone(); + } + + private void displayChatHistory(Contact contact) { +// chat.displayHistory(contact); + } + + private void tryListContacts() { + try { + listContacts(); + } catch (DbException e) { + e.printStackTrace(); + } + } + + private void listContacts() throws DbException { +// Collection<Contact> contacts = contactManager.getContacts(); +// for (Contact contact : contacts) { +// contactListModel.add(new ContactWithMeta(contact), 0); +// } +// for (int i = 0; i < contactListModel.getSize(); i++) { +// ContactWithMeta entry = contactListModel.getElementAt(i); +// Contact contact = entry.getContact(); +// logger.debug( +// String.format("%s (%s)", contact.getAuthor().getName(), +// contact.getAlias())); +// Collection<ConversationMessageHeader> messageHeaders = +// conversationManager.getMessageHeaders(contact.getId()); +// int unread = 0; +// for (ConversationMessageHeader header : messageHeaders) { +// if (!header.isRead()) { +// unread += 1; +// } +// } +// entry.setNumUnreadMessages(unread); +// } + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactAddedEvent) { + reloadContactList(); + } + } + + private void reloadContactList() { +// contactListModel = new ContactListModel(); +// tryListContacts(); +// contactList.setModel(contactListModel); + } + +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt b/src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt new file mode 100644 index 0000000000000000000000000000000000000000..34154addc0b93817d4a0663b5eaddb1d668cbe31 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/SwingDatabaseConfig.kt @@ -0,0 +1,15 @@ +package org.briarproject.briar.compose + +import org.briarproject.bramble.api.crypto.KeyStrengthener +import org.briarproject.bramble.api.db.DatabaseConfig +import java.io.File + +internal class SwingDatabaseConfig(private val dbDir: File, private val keyDir: File) : + DatabaseConfig { + + override fun getDatabaseDirectory() = dbDir + + override fun getDatabaseKeyDirectory() = keyDir + + override fun getKeyStrengthener(): KeyStrengthener? = null +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt b/src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt new file mode 100644 index 0000000000000000000000000000000000000000..cfa67a1e1293c5d816bab8a3b74a30876c19d876 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/SwingModule.kt @@ -0,0 +1,82 @@ +package org.briarproject.briar.compose + +import com.fasterxml.jackson.databind.ObjectMapper +import dagger.Module +import dagger.Provides +import org.briarproject.bramble.account.AccountModule +import org.briarproject.bramble.api.FeatureFlags +import org.briarproject.bramble.api.db.DatabaseConfig +import org.briarproject.bramble.api.plugin.PluginConfig +import org.briarproject.bramble.api.plugin.TorDirectory +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 +import org.briarproject.bramble.socks.SocksModule +import org.briarproject.bramble.system.DefaultTaskSchedulerModule +import org.briarproject.bramble.system.DefaultWakefulIoExecutorModule +import org.briarproject.bramble.system.DesktopSecureRandomModule +import org.briarproject.bramble.system.JavaSystemModule +import org.briarproject.bramble.util.OsUtils.isLinux +import org.briarproject.bramble.util.OsUtils.isMac +import java.io.File +import java.util.Collections.emptyList +import javax.inject.Singleton + +@Module( + includes = [ + AccountModule::class, + CircumventionModule::class, + DefaultBatteryManagerModule::class, + DefaultEventExecutorModule::class, + DefaultTaskSchedulerModule::class, + DefaultWakefulIoExecutorModule::class, + DesktopSecureRandomModule::class, + JavaNetworkModule::class, + JavaSystemModule::class, + SocksModule::class + ] +) +internal class SwingModule(private val appDir: File) { + + @Provides + @Singleton + internal fun provideBriarService(briarService: BriarServiceImpl): BriarService = briarService + + @Provides + @Singleton + internal fun provideDatabaseConfig(): DatabaseConfig { + val dbDir = File(appDir, "db") + val keyDir = File(appDir, "key") + return SwingDatabaseConfig(dbDir, keyDir) + } + + @Provides + @TorDirectory + internal fun provideTorDirectory(): File { + return File(appDir, "tor") + } + + @Provides + internal fun providePluginConfig(tor: UnixTorPluginFactory): PluginConfig { + val duplex: List<DuplexPluginFactory> = + if (isLinux() || isMac()) listOf(tor) else emptyList() + return object : PluginConfig { + override fun getDuplexFactories(): Collection<DuplexPluginFactory> = duplex + override fun getSimplexFactories(): Collection<SimplexPluginFactory> = emptyList() + override fun shouldPoll(): Boolean = true + override fun getTransportPreferences(): Map<TransportId, List<TransportId>> = emptyMap() + } + } + + @Provides + @Singleton + internal fun provideObjectMapper() = ObjectMapper() + + @Provides + internal fun provideFeatureFlags() = FeatureFlags { false } +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/UI.kt b/src/main/kotlin/org/briarproject/briar/compose/UI.kt new file mode 100644 index 0000000000000000000000000000000000000000..cd10bb9c2e4dc1857ca9d4ebcb0bf4682ed1ace4 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/UI.kt @@ -0,0 +1,68 @@ +package org.briarproject.briar.compose + +import org.briarproject.bramble.api.account.AccountManager +import org.briarproject.bramble.api.contact.ContactManager +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator +import org.briarproject.bramble.api.event.EventBus +import org.briarproject.briar.api.conversation.ConversationManager +import org.briarproject.briar.api.introduction.IntroductionManager +import org.briarproject.briar.api.messaging.MessagingManager +import org.briarproject.briar.api.messaging.PrivateMessageFactory +import org.briarproject.briar.compose.config.Configuration +import java.util.logging.Logger.getLogger +import javax.annotation.concurrent.Immutable +import javax.inject.Inject +import javax.inject.Singleton +import javax.swing.JFrame + +private const val VERSION = "v1" +private const val WS = "/$VERSION/ws" + +@Immutable +@Singleton +internal class UI +@Inject +constructor( + private val briarService: BriarService, + private val accountManager: AccountManager, + private val contactManager: ContactManager, + private val messagingManager: MessagingManager, + private val introductionManager: IntroductionManager, + private val conversationManager: ConversationManager, + private val privateMessageFactory: PrivateMessageFactory, + private val eventBus: EventBus, + private val passwordStrengthEstimator: PasswordStrengthEstimator +) { + + private val logger = getLogger(UI::javaClass.name) + private val configuration = Configuration() + + internal fun startBriar() { + briarService.start(); + } + + internal fun startUI() { + val mainUI = MainUI( + briarService, + accountManager, + contactManager, + conversationManager, + messagingManager, + introductionManager, + privateMessageFactory, + eventBus, + passwordStrengthEstimator, + configuration + ); + + mainUI.defaultCloseOperation = JFrame.EXIT_ON_CLOSE; + mainUI.setSize(800, 600); + mainUI.setVisible(true); + } + + internal fun getContactManager(): ContactManager { + return contactManager + } + +} + diff --git a/src/main/kotlin/org/briarproject/briar/compose/config/Configuration.java b/src/main/kotlin/org/briarproject/briar/compose/config/Configuration.java new file mode 100644 index 0000000000000000000000000000000000000000..9822305f81c58c27d146968f82956394e92d7b2e --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/config/Configuration.java @@ -0,0 +1,15 @@ +package org.briarproject.briar.compose.config; + +public class Configuration { + + private String lookAndFeel; + + public String getLookAndFeel() { + return lookAndFeel; + } + + public void setLookAndFeel(String lookAndFeel) { + this.lookAndFeel = lookAndFeel; + } + +} diff --git a/src/main/kotlin/org/briarproject/briar/compose/main.kt b/src/main/kotlin/org/briarproject/briar/compose/main.kt new file mode 100644 index 0000000000000000000000000000000000000000..0278c0e628fd37a1cac83bdd1b8383315f4511d5 --- /dev/null +++ b/src/main/kotlin/org/briarproject/briar/compose/main.kt @@ -0,0 +1,92 @@ +package org.briarproject.briar.compose + +import androidx.compose.desktop.Window +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.material.Button +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.svgResource +import androidx.compose.ui.unit.dp +import org.briarproject.bramble.BrambleCoreEagerSingletons +import org.briarproject.briar.BriarCoreEagerSingletons +import java.io.File +import java.io.File.separator +import java.io.IOException +import java.lang.System.getProperty +import java.nio.file.Files.setPosixFilePermissions +import java.nio.file.attribute.PosixFilePermission +import java.util.logging.Level +import java.util.logging.LogManager + +fun main() = Window { + LogManager.getLogManager().getLogger("").level = Level.INFO + + val dataDir = getDataDir() + val app = + DaggerBriarSwingApp.builder().swingModule( + SwingModule( + dataDir + ) + ).build() + // We need to load the eager singletons directly after making the + // dependency graphs + BrambleCoreEagerSingletons.Helper.injectEagerSingletons(app) + BriarCoreEagerSingletons.Helper.injectEagerSingletons(app) + + app.getUI().startBriar() + app.getUI().startUI() + + Column( + modifier = Modifier.padding(16.dp) + ) { + TheImage() + Spacer(Modifier.height(32.dp)) + TheText() + TheButton() + } +} + +private fun getDataDir(): File { + val file = File(getProperty("user.home") + separator + ".briar") + if (!file.exists() && !file.mkdirs()) { + throw IOException("Could not create directory: ${file.absolutePath}") + } else if (!file.isDirectory) { + throw IOException("Data dir is not a directory: ${file.absolutePath}") + } + val perms = HashSet<PosixFilePermission>() + perms.add(PosixFilePermission.OWNER_READ) + perms.add(PosixFilePermission.OWNER_WRITE) + perms.add(PosixFilePermission.OWNER_EXECUTE) + setPosixFilePermissions(file.toPath(), perms) + return file +} + +@Composable +private fun TheButton() { + var text by remember { mutableStateOf("Start chatting") } + Button(onClick = { + text = "Sorry, not yet available" + }) { + Text(text) + } +} + +@Composable +private fun TheImage() { + Image( + painter = svgResource("images/logo_circle.svg"), + contentDescription = "Briar logo", + modifier = Modifier + .fillMaxWidth() + .clip(shape = RoundedCornerShape(400.dp)) + ) +} + +@Composable +private fun TheText() { + Text("Welcome to Briar") +} \ No newline at end of file