Merge pull request #3289 from cryptomator/feature/share-vault

Feature: Introduce 'Share Vault' Functionality
This commit is contained in:
mindmonk
2024-01-26 16:08:06 +01:00
committed by GitHub
19 changed files with 380 additions and 5 deletions

View File

@@ -45,6 +45,7 @@ public enum FxmlFile {
RECOVERYKEY_RESET_PASSWORD_SUCCESS("/fxml/recoverykey_reset_password_success.fxml"), //
RECOVERYKEY_SUCCESS("/fxml/recoverykey_success.fxml"), //
REMOVE_VAULT("/fxml/remove_vault.fxml"), //
SHARE_VAULT("/fxml/share_vault.fxml"), //
UPDATE_REMINDER("/fxml/update_reminder.fxml"), //
UNLOCK_ENTER_PASSWORD("/fxml/unlock_enter_password.fxml"),
UNLOCK_REQUIRES_RESTART("/fxml/unlock_requires_restart.fxml"), //

View File

@@ -47,6 +47,7 @@ public enum FontAwesome5Icon {
QUESTION_CIRCLE("\uf059"), //
REDO("\uF01E"), //
SEARCH("\uF002"), //
SHARE("\uF064"), //
SPINNER("\uF110"), //
STETHOSCOPE("\uF0f1"), //
SYNC("\uF021"), //

View File

@@ -13,6 +13,7 @@ import org.cryptomator.ui.lock.LockComponent;
import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.traymenu.TrayMenuComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -22,7 +23,17 @@ import javafx.scene.image.Image;
import java.io.IOException;
import java.io.InputStream;
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, MainWindowComponent.class, PreferencesComponent.class, VaultOptionsComponent.class, UnlockComponent.class, LockComponent.class, QuitComponent.class, ErrorComponent.class, HealthCheckComponent.class, UpdateReminderComponent.class})
@Module(includes = {UpdateCheckerModule.class}, subcomponents = {TrayMenuComponent.class, //
MainWindowComponent.class, //
PreferencesComponent.class, //
VaultOptionsComponent.class, //
UnlockComponent.class, //
LockComponent.class, //
QuitComponent.class, //
ErrorComponent.class, //
HealthCheckComponent.class, //
UpdateReminderComponent.class, //
ShareVaultComponent.class})
abstract class FxApplicationModule {
private static Image createImageFromResource(String resourceName) throws IOException {

View File

@@ -11,6 +11,7 @@ import org.cryptomator.ui.mainwindow.MainWindowComponent;
import org.cryptomator.ui.preferences.PreferencesComponent;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.quit.QuitComponent;
import org.cryptomator.ui.sharevault.ShareVaultComponent;
import org.cryptomator.ui.unlock.UnlockComponent;
import org.cryptomator.ui.unlock.UnlockWorkflow;
import org.cryptomator.ui.updatereminder.UpdateReminderComponent;
@@ -51,6 +52,7 @@ public class FxApplicationWindows {
private final ErrorComponent.Factory errorWindowFactory;
private final ExecutorService executor;
private final VaultOptionsComponent.Factory vaultOptionsWindow;
private final ShareVaultComponent.Factory shareVaultWindow;
private final FilteredList<Window> visibleWindows;
@Inject
@@ -64,6 +66,7 @@ public class FxApplicationWindows {
LockComponent.Factory lockWorkflowFactory, //
ErrorComponent.Factory errorWindowFactory, //
VaultOptionsComponent.Factory vaultOptionsWindow, //
ShareVaultComponent.Factory shareVaultWindow, //
ExecutorService executor) {
this.primaryStage = primaryStage;
this.trayIntegration = trayIntegration;
@@ -76,6 +79,7 @@ public class FxApplicationWindows {
this.errorWindowFactory = errorWindowFactory;
this.executor = executor;
this.vaultOptionsWindow = vaultOptionsWindow;
this.shareVaultWindow = shareVaultWindow;
this.visibleWindows = Window.getWindows().filtered(Window::isShowing);
}
@@ -122,6 +126,10 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> preferencesWindow.get().showPreferencesWindow(selectedTab), Platform::runLater).whenComplete(this::reportErrors);
}
public void showShareVaultWindow(Vault vault) {
CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater);
}
public CompletionStage<Stage> showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) {
return showMainWindow().thenApplyAsync((window) -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater).whenComplete(this::reportErrors);
}

View File

@@ -44,6 +44,11 @@ public class VaultDetailLockedController implements FxController {
appWindows.startUnlockWorkflow(vault.get(), mainWindow);
}
@FXML
public void share() {
appWindows.showShareVaultWindow(vault.get());
}
@FXML
public void showVaultOptions() {
vaultOptionsWindow.create(vault.get()).showVaultOptionsWindow(SelectedVaultOptionsTab.ANY);

View File

@@ -0,0 +1,34 @@
package org.cryptomator.ui.sharevault;
import dagger.BindsInstance;
import dagger.Lazy;
import dagger.Subcomponent;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlScene;
import javafx.scene.Scene;
import javafx.stage.Stage;
@ShareVaultScoped
@Subcomponent(modules = {ShareVaultModule.class})
public interface ShareVaultComponent {
@ShareVaultWindow
Stage window();
@FxmlScene(FxmlFile.SHARE_VAULT)
Lazy<Scene> scene();
default void showShareVaultWindow(){
Stage stage = window();
stage.setScene(scene().get());
stage.show();
}
@Subcomponent.Factory
interface Factory {
ShareVaultComponent create(@BindsInstance @ShareVaultWindow Vault vault);
}
}

View File

@@ -0,0 +1,76 @@
package org.cryptomator.ui.sharevault;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.keyloading.hub.HubKeyLoadingStrategy;
import javax.inject.Inject;
import javafx.application.Application;
import javafx.fxml.FXML;
import javafx.stage.Stage;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.net.URISyntaxException;
@ShareVaultScoped
public class ShareVaultController implements FxController {
private static final String SCHEME_PREFIX = "hub+";
private static final String VISIT_HUB_URL = "https://cryptomator.org/hub/";
private static final String BEST_PRACTICES_URL = "https://docs.cryptomator.org/en/latest/security/best-practices/#sharing-of-vaults";
private final Stage window;
private final Lazy<Application> application;
private final Vault vault;
private final Boolean hubVault;
@Inject
ShareVaultController(@ShareVaultWindow Stage window, //
Lazy<Application> application, //
@ShareVaultWindow Vault vault) {
this.window = window;
this.application = application;
this.vault = vault;
var vaultScheme = vault.getVaultConfigCache().getUnchecked().getKeyId().getScheme();
this.hubVault = (vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTP) || vaultScheme.equals(HubKeyLoadingStrategy.SCHEME_HUB_HTTPS));
}
@FXML
public void close() {
window.close();
}
@FXML
public void visitHub() {
application.get().getHostServices().showDocument(VISIT_HUB_URL);
}
@FXML
public void openHub() {
application.get().getHostServices().showDocument(getHubUri(vault).toString());
}
@FXML
public void visitBestPractices() {
application.get().getHostServices().showDocument(BEST_PRACTICES_URL);
}
private static URI getHubUri(Vault vault) {
try {
var keyID = new URI(vault.getVaultConfigCache().get().getKeyId().toString());
assert keyID.getScheme().startsWith(SCHEME_PREFIX);
return new URI(keyID.getScheme().substring(SCHEME_PREFIX.length()) + "://" + keyID.getHost() + "/app/vaults");
} catch (IOException e) {
throw new UncheckedIOException(e);
} catch (URISyntaxException e) {
throw new IllegalStateException("URI constructed from params known to be valid", e);
}
}
public boolean isHubVault() {
return hubVault;
}
}

View File

@@ -0,0 +1,54 @@
package org.cryptomator.ui.sharevault;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageFactory;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.util.Map;
import java.util.ResourceBundle;
@Module
abstract class ShareVaultModule {
@Provides
@ShareVaultWindow
@ShareVaultScoped
static FxmlLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
@Provides
@ShareVaultWindow
@ShareVaultScoped
static Stage provideStage(StageFactory factory, ResourceBundle resourceBundle) {
Stage stage = factory.create();
stage.setResizable(false);
stage.initModality(Modality.APPLICATION_MODAL);
stage.setTitle(resourceBundle.getString("shareVault.title"));
return stage;
}
@Provides
@FxmlScene(FxmlFile.SHARE_VAULT)
@ShareVaultScoped
static Scene provideShareVaultScene(@ShareVaultWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.SHARE_VAULT);
}
@Binds
@IntoMap
@FxControllerKey(ShareVaultController.class)
abstract FxController bindShareVaultController(ShareVaultController controller);
}

View File

@@ -0,0 +1,13 @@
package org.cryptomator.ui.sharevault;
import javax.inject.Scope;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
@interface ShareVaultScoped {
}

View File

@@ -0,0 +1,14 @@
package org.cryptomator.ui.sharevault;
import javax.inject.Qualifier;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Qualifier
@Documented
@Retention(RUNTIME)
@interface ShareVaultWindow {
}