From b01478f0fab73959c849dc9e936be21ed47c8d9c Mon Sep 17 00:00:00 2001 From: Armin Schrenk Date: Wed, 26 Aug 2026 17:08:29 +0200 Subject: [PATCH] Remove VaultTemplate stuff including jdk.zipfs as dependency in jlink Signed-off-by: Armin Schrenk --- .github/workflows/appimage.yml | 2 +- .github/workflows/mac-dmg-x64.yml | 2 +- .github/workflows/mac-dmg.yml | 2 +- .github/workflows/win-exe.yml | 2 +- dist/linux/appimage/build.sh | 2 +- dist/linux/debian/rules | 2 +- .../org.cryptomator.Cryptomator.TEMPLATE.yaml | 2 +- dist/linux/makepkg/PKGBUILD.template | 2 +- dist/mac/dmg/build.sh | 2 +- dist/win/build.ps1 | 2 +- .../org/cryptomator/ui/common/FxmlFile.java | 2 - .../org/cryptomator/ui/dialogs/Dialogs.java | 11 - .../ui/fxapp/FxApplicationModule.java | 2 - .../ui/fxapp/FxApplicationWindows.java | 18 - .../ImportTemplateComponent.java | 34 -- .../ImportTemplateLocationController.java | 340 ------------------ .../importtemplate/ImportTemplateModule.java | 90 ----- .../importtemplate/ImportTemplateScoped.java | 13 - .../ImportTemplateSuccessController.java | 46 --- .../importtemplate/ImportTemplateWindow.java | 14 - .../importtemplate/ImportTemplateWindows.java | 114 ------ .../MalformedTemplateException.java | 23 -- .../ui/importtemplate/VaultTemplate.java | 121 ------- .../VaultTemplateExtractor.java | 200 ----------- .../fxml/import_template_location.fxml | 86 ----- .../fxml/import_template_success.fxml | 45 --- src/main/resources/i18n/strings.properties | 11 - .../ImportTemplateWindowsTest.java | 143 -------- .../VaultTemplateExtractorTest.java | 185 ---------- .../ui/importtemplate/VaultTemplateTest.java | 150 -------- 30 files changed, 10 insertions(+), 1658 deletions(-) delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateComponent.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateLocationController.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateModule.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateScoped.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateSuccessController.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindow.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindows.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/MalformedTemplateException.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/VaultTemplate.java delete mode 100644 src/main/java/org/cryptomator/ui/importtemplate/VaultTemplateExtractor.java delete mode 100644 src/main/resources/fxml/import_template_location.fxml delete mode 100644 src/main/resources/fxml/import_template_success.fxml delete mode 100644 src/test/java/org/cryptomator/ui/importtemplate/ImportTemplateWindowsTest.java delete mode 100644 src/test/java/org/cryptomator/ui/importtemplate/VaultTemplateExtractorTest.java delete mode 100644 src/test/java/org/cryptomator/ui/importtemplate/VaultTemplateTest.java diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 6f94c7a26..344a86e11 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -126,7 +126,7 @@ jobs: --verbose --output runtime --module-path "${JMOD_PATHS}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,jdk.zipfs,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler --strip-native-commands --no-header-files --no-man-pages diff --git a/.github/workflows/mac-dmg-x64.yml b/.github/workflows/mac-dmg-x64.yml index 4a64d5afa..96455be3d 100644 --- a/.github/workflows/mac-dmg-x64.yml +++ b/.github/workflows/mac-dmg-x64.yml @@ -131,7 +131,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.zipfs,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages diff --git a/.github/workflows/mac-dmg.yml b/.github/workflows/mac-dmg.yml index 94cd56497..2e2feafc8 100644 --- a/.github/workflows/mac-dmg.yml +++ b/.github/workflows/mac-dmg.yml @@ -129,7 +129,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.zipfs,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages diff --git a/.github/workflows/win-exe.yml b/.github/workflows/win-exe.yml index a12a5ac69..4a08409df 100644 --- a/.github/workflows/win-exe.yml +++ b/.github/workflows/win-exe.yml @@ -150,7 +150,7 @@ jobs: --verbose --output runtime --module-path "${{ steps.jep-493-check.outputs.jmod_paths }}" - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.zipfs,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.unsupported,jdk.accessibility,jdk.management.jfr,java.compiler --strip-native-commands --no-header-files --no-man-pages diff --git a/dist/linux/appimage/build.sh b/dist/linux/appimage/build.sh index f03b0f106..6c6cfd595 100755 --- a/dist/linux/appimage/build.sh +++ b/dist/linux/appimage/build.sh @@ -62,7 +62,7 @@ ${JAVA_HOME}/bin/jlink \ --verbose \ --output runtime \ --module-path "${JMOD_PATHS}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,jdk.zipfs,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/linux/debian/rules b/dist/linux/debian/rules index 64978cb16..d2acc4cd4 100755 --- a/dist/linux/debian/rules +++ b/dist/linux/debian/rules @@ -28,7 +28,7 @@ override_dh_auto_build: $(JAVA_HOME)/bin/jlink \ --output runtime \ --module-path "${JMODS_PATH}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,jdk.zipfs,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/linux/flatpak/org.cryptomator.Cryptomator.TEMPLATE.yaml b/dist/linux/flatpak/org.cryptomator.Cryptomator.TEMPLATE.yaml index 16dd61f28..d65f6f1ea 100644 --- a/dist/linux/flatpak/org.cryptomator.Cryptomator.TEMPLATE.yaml +++ b/dist/linux/flatpak/org.cryptomator.Cryptomator.TEMPLATE.yaml @@ -100,7 +100,7 @@ modules: - $JAVA_HOME/bin/jlink --output runtime --module-path $JMODS_PATH - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,jdk.zipfs,java.compiler + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler --no-header-files --no-man-pages --strip-debug diff --git a/dist/linux/makepkg/PKGBUILD.template b/dist/linux/makepkg/PKGBUILD.template index 34f83b221..0c4f66282 100644 --- a/dist/linux/makepkg/PKGBUILD.template +++ b/dist/linux/makepkg/PKGBUILD.template @@ -57,7 +57,7 @@ build() { "$JAVA_HOME/bin/jlink" \ --output runtime \ --module-path "$JMODS_PATH" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,jdk.zipfs,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.net,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/mac/dmg/build.sh b/dist/mac/dmg/build.sh index 0000ca5a4..b90d2b033 100755 --- a/dist/mac/dmg/build.sh +++ b/dist/mac/dmg/build.sh @@ -85,7 +85,7 @@ fi ${JAVA_HOME}/bin/jlink \ --output runtime \ --module-path "${JMOD_PATHS}" \ - --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,jdk.zipfs,java.compiler \ + --add-modules java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,javafx.base,javafx.graphics,javafx.controls,javafx.fxml,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.unsupported,jdk.security.auth,jdk.accessibility,jdk.management.jfr,java.compiler \ --strip-native-commands \ --no-header-files \ --no-man-pages \ diff --git a/dist/win/build.ps1 b/dist/win/build.ps1 index a5d624153..246360224 100644 --- a/dist/win/build.ps1 +++ b/dist/win/build.ps1 @@ -148,7 +148,7 @@ Invoke-CommandWithExitCheck -Command ` "--verbose", "--output", "runtime", "--module-path", $jmodPaths, - "--add-modules", "java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,jdk.zipfs,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml", + "--add-modules", "java.base,java.desktop,java.instrument,java.logging,java.naming,java.net.http,java.scripting,java.sql,java.xml,jdk.unsupported,jdk.accessibility,jdk.management.jfr,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.crypto.mscapi,java.compiler,javafx.base,javafx.graphics,javafx.controls,javafx.fxml", "--strip-native-commands", "--no-header-files", "--no-man-pages", diff --git a/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/src/main/java/org/cryptomator/ui/common/FxmlFile.java index c437db43a..b31245b4c 100644 --- a/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -13,8 +13,6 @@ public enum FxmlFile { CONVERTVAULT_HUBTOPASSWORD_CONVERT("/fxml/convertvault_hubtopassword_convert.fxml"), // CONVERTVAULT_HUBTOPASSWORD_SUCCESS("/fxml/convertvault_hubtopassword_success.fxml"), // DECRYPTNAMES("/fxml/decryptnames.fxml"), // - IMPORT_TEMPLATE_LOCATION("/fxml/import_template_location.fxml"), // - IMPORT_TEMPLATE_SUCCESS("/fxml/import_template_success.fxml"), // ERROR("/fxml/error.fxml"), // EVENT_VIEW("/fxml/eventview.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java index a55fdfbd7..4933cefc7 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -158,17 +158,6 @@ public class Dialogs { .setOkAction(Stage::close); } - public SimpleDialog.Builder prepareMalformedTemplateDialog(Stage window) { - return createDialogBuilder() // - .setOwner(window) // - .setTitleKey("importTemplate.title") // - .setMessageKey("importTemplate.malformedTemplate.message") // - .setDescriptionKey("importTemplate.malformedTemplate.description") // - .setIcon(FontAwesome5Icon.EXCLAMATION) // - .setOkButtonKey(BUTTON_KEY_CLOSE) // - .setOkAction(Stage::close); - } - public SimpleDialog.Builder prepareNoDDirectorySelectedDialog(Stage window) { return createDialogBuilder() // .setOwner(window) // diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java index 7598fd3fc..bb5af478c 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationModule.java @@ -12,7 +12,6 @@ import org.cryptomator.ui.decryptname.DecryptNameComponent; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.eventview.EventViewComponent; import org.cryptomator.ui.health.HealthCheckComponent; -import org.cryptomator.ui.importtemplate.ImportTemplateComponent; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.notification.NotificationComponent; @@ -32,7 +31,6 @@ import java.util.Optional; @Module(subcomponents = {TrayMenuComponent.class, // DecryptNameComponent.class, // - ImportTemplateComponent.class, // MainWindowComponent.class, // PreferencesComponent.class, // VaultOptionsComponent.class, // diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java index 80b53bf12..f80b6de0e 100644 --- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java +++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java @@ -9,7 +9,6 @@ import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.dialogs.SimpleDialog; import org.cryptomator.ui.error.ErrorComponent; import org.cryptomator.ui.eventview.EventViewComponent; -import org.cryptomator.ui.importtemplate.ImportTemplateWindows; import org.cryptomator.ui.lock.LockComponent; import org.cryptomator.ui.mainwindow.MainWindowComponent; import org.cryptomator.ui.notification.NotificationComponent; @@ -61,7 +60,6 @@ public class FxApplicationWindows { private final ExecutorService executor; private final VaultOptionsComponent.Factory vaultOptionsWindow; private final ShareVaultComponent.Factory shareVaultWindow; - private final ImportTemplateWindows importTemplateWindows; private final FilteredList visibleWindows; private final Dialogs dialogs; @@ -77,7 +75,6 @@ public class FxApplicationWindows { ErrorComponent.Factory errorWindowFactory, // VaultOptionsComponent.Factory vaultOptionsWindow, // ShareVaultComponent.Factory shareVaultWindow, // - ImportTemplateWindows importTemplateWindows, // EventViewComponent.Factory eventViewWindowFactory, // NotificationComponent.Factory notificationWindowFactory, // ExecutorService executor, // @@ -96,7 +93,6 @@ public class FxApplicationWindows { this.executor = executor; this.vaultOptionsWindow = vaultOptionsWindow; this.shareVaultWindow = shareVaultWindow; - this.importTemplateWindows = importTemplateWindows; this.visibleWindows = Window.getWindows().filtered(Window::isShowing); this.dialogs = dialogs; } @@ -148,20 +144,6 @@ public class FxApplicationWindows { CompletableFuture.runAsync(() -> shareVaultWindow.create(vault).showShareVaultWindow(), Platform::runLater); } - /** - * Shows the vault template import flow, which decides for itself whether the template can be imported at all. - * Unexpected failures (as opposed to an unusable template) surface in the generic error window. - */ - public CompletionStage showImportTemplateWindow(String name, byte[] template) { - return showMainWindow() // - .thenComposeAsync(_ -> importTemplateWindows.extractAndShowImportTemplateWindow(name, template), Platform::runLater) // - .exceptionallyAsync(e -> { - showErrorWindow(e, primaryStage, null); - return primaryStage; - }, Platform::runLater) // - .whenComplete(this::reportErrors); - } - public CompletionStage showVaultOptionsWindow(Vault vault, SelectedVaultOptionsTab tab) { return showMainWindow().thenApplyAsync(_ -> vaultOptionsWindow.create(vault).showVaultOptionsWindow(tab), Platform::runLater) // .whenComplete(this::reportErrors); diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateComponent.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateComponent.java deleted file mode 100644 index 6b799f6a5..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import dagger.BindsInstance; -import dagger.Lazy; -import dagger.Subcomponent; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; - -import javax.inject.Named; -import javafx.scene.Scene; -import javafx.stage.Stage; - -@ImportTemplateScoped -@Subcomponent(modules = {ImportTemplateModule.class}) -public interface ImportTemplateComponent { - - @ImportTemplateWindow - Stage window(); - - @FxmlScene(FxmlFile.IMPORT_TEMPLATE_LOCATION) - Lazy scene(); - - default void showImportTemplateWindow() { - Stage stage = window(); - stage.setScene(scene().get()); - stage.show(); - } - - @Subcomponent.Factory - interface Factory { - ImportTemplateComponent create(@BindsInstance @Named("vaultName") String name, @BindsInstance VaultTemplate template); - } - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateLocationController.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateLocationController.java deleted file mode 100644 index 1983876d5..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateLocationController.java +++ /dev/null @@ -1,340 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import dagger.Lazy; -import org.cryptomator.common.ObservableUtil; -import org.cryptomator.common.locationpresets.LocationPreset; -import org.cryptomator.common.locationpresets.LocationPresetsProvider; -import org.cryptomator.common.settings.Settings; -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.common.FxmlFile; -import org.cryptomator.ui.common.FxmlScene; -import org.cryptomator.ui.common.Tasks; -import org.cryptomator.ui.controls.FontAwesome5IconView; -import org.cryptomator.ui.fxapp.FxApplicationWindows; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javax.inject.Named; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import javafx.beans.binding.ObjectBinding; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.Node; -import javafx.scene.Scene; -import javafx.scene.control.ContentDisplay; -import javafx.scene.control.Label; -import javafx.scene.control.RadioButton; -import javafx.scene.control.Toggle; -import javafx.scene.control.ToggleGroup; -import javafx.scene.layout.HBox; -import javafx.scene.layout.VBox; -import javafx.stage.DirectoryChooser; -import javafx.stage.Stage; -import javafx.stage.WindowEvent; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.util.Objects; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.concurrent.ExecutorService; - -@ImportTemplateScoped -public class ImportTemplateLocationController implements FxController { - - private static final Logger LOG = LoggerFactory.getLogger(ImportTemplateLocationController.class); - private static final Path DEFAULT_CUSTOM_VAULT_PATH = Path.of(System.getProperty("user.home")); - private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator"; - - private final Stage window; - private final String vaultName; - private final VaultTemplate template; - private final ObjectProperty vaultPath; - private final ObjectProperty vault; - private final Lazy successScene; - private final FxApplicationWindows appWindows; - private final VaultListManager vaultListManager; - private final ExecutorService executor; - private final Settings settings; - private final ResourceBundle resourceBundle; - private final ObservableValue vaultPathStatus; - private final ObservableValue validVaultPath; - private final BooleanProperty usePresetPath; - private final BooleanProperty loadingPresetLocations = new SimpleBooleanProperty(false); - private final BooleanProperty processing = new SimpleBooleanProperty(false); - private final ObservableValue readyToImport; - private final ObjectBinding createButtonState; - private final ObservableList radioButtons; - private final ObservableList sortedRadioButtons; - - private final String hubUrl; - - private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH; - - //FXML - public ToggleGroup locationPresetsToggler; - public VBox radioButtonVBox; - public HBox customLocationRadioBtn; - public RadioButton customRadioButton; - public Label locationStatusLabel; - public FontAwesome5IconView goodLocation; - public FontAwesome5IconView badLocation; - - @Inject - ImportTemplateLocationController(@ImportTemplateWindow Stage window, // - @Named("vaultName") String vaultName, // - VaultTemplate template, // - ObjectProperty vaultPath, // - @ImportTemplateWindow ObjectProperty vault, // - @FxmlScene(FxmlFile.IMPORT_TEMPLATE_SUCCESS) Lazy successScene, // - FxApplicationWindows appWindows, // - VaultListManager vaultListManager, // - ExecutorService executor, // - Settings settings, // - ResourceBundle resourceBundle) { - this.window = window; - this.vaultName = vaultName; - this.template = template; - this.vaultPath = vaultPath; - this.vault = vault; - this.successScene = successScene; - this.appWindows = appWindows; - this.vaultListManager = vaultListManager; - this.executor = executor; - this.settings = settings; - this.resourceBundle = resourceBundle; - this.hubUrl = Objects.requireNonNullElseGet(template.hubUrl(), () -> resourceBundle.getString("importTemplate.hubUrl.none")); - this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message")); - this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false); - this.vaultPathStatus.addListener(this::updateStatusLabel); - this.usePresetPath = new SimpleBooleanProperty(); - this.readyToImport = Bindings.createBooleanBinding(() -> validVaultPath.getValue() && !processing.get(), validVaultPath, processing); - this.createButtonState = Bindings.when(processing).then(ContentDisplay.LEFT).otherwise(ContentDisplay.TEXT_ONLY); - this.radioButtons = FXCollections.observableArrayList(); - this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets); - - Path previouslyUsedDirectory = settings.previouslyUsedVaultDirectory.get(); - if (previouslyUsedDirectory != null) { - try { - if (Files.exists(previouslyUsedDirectory) && Files.isDirectory(previouslyUsedDirectory) && isActuallyWritable(previouslyUsedDirectory)) { - this.customVaultPath = previouslyUsedDirectory; - } - } catch (InvalidPathException | NullPointerException e) { - LOG.warn("Invalid previously used vault directory path: {}", previouslyUsedDirectory, e); - } - } - } - - private VaultPathStatus validatePath(Path p) throws NullPointerException { - if (!Files.exists(p.getParent())) { - return new VaultPathStatus(false, "addvaultwizard.new.locationDoesNotExist"); - } else if (!isActuallyWritable(p.getParent())) { - return new VaultPathStatus(false, "addvaultwizard.new.locationIsNotWritable"); - } else if (!Files.notExists(p)) { - return new VaultPathStatus(false, "addvaultwizard.new.fileAlreadyExists"); - } else { - return new VaultPathStatus(true, "addvaultwizard.new.locationIsOk"); - } - } - - private void updateStatusLabel(ObservableValue observable, VaultPathStatus oldValue, VaultPathStatus newValue) { - if (newValue.valid()) { - locationStatusLabel.setGraphic(goodLocation); - locationStatusLabel.getStyleClass().remove("label-red"); - locationStatusLabel.getStyleClass().add("label-muted"); - } else { - locationStatusLabel.setGraphic(badLocation); - locationStatusLabel.getStyleClass().remove("label-muted"); - locationStatusLabel.getStyleClass().add("label-red"); - } - this.locationStatusLabel.setText(resourceBundle.getString(newValue.localizationKey())); - } - - private boolean isActuallyWritable(Path p) { - Path tmpDir = null; - try { - tmpDir = Files.createTempDirectory(p, TEMP_FILE_PREFIX); - return true; - } catch (IOException e) { - return false; - } finally { - if (tmpDir != null) { - try { - Files.deleteIfExists(tmpDir); - } catch (IOException e) { - LOG.warn("Unable to delete temporary directory {}. Needs to be deleted manually.", tmpDir); - } - } - } - } - - @FXML - public void initialize() { - var task = executor.submit(this::loadLocationPresets); - window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> { - task.cancel(true); - template.close(); //discards the temporary extraction directory, whether or not the import completed - }); - locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation); - usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton)); - radioButtons.add(customLocationRadioBtn); - Bindings.bindContent(radioButtonVBox.getChildren(), sortedRadioButtons); //to prevent garbage collection of the binding, we bind explicitly to the sorted list - } - - private void loadLocationPresets() { - Platform.runLater(() -> loadingPresetLocations.set(true)); - try { - LocationPresetsProvider.loadAll(LocationPresetsProvider.class) // - .flatMap(LocationPresetsProvider::getLocations) //we do not use sorted(), because it evaluates the stream elements, blocking until all elements are gathered - .forEach(this::createRadioButtonFor); - } finally { - Platform.runLater(() -> loadingPresetLocations.set(false)); - } - } - - private void createRadioButtonFor(LocationPreset preset) { - Platform.runLater(() -> { - var btn = new RadioButton(preset.name()); - btn.setUserData(preset.path()); - radioButtons.add(btn); - locationPresetsToggler.getToggles().add(btn); - }); - } - - private int compareLocationPresets(Node left, Node right) { - if (customLocationRadioBtn.getId().equals(left.getId())) { - return 1; - } else if (customLocationRadioBtn.getId().equals(right.getId())) { - return -1; - } else { - return ((RadioButton) left).getText().compareToIgnoreCase(((RadioButton) right).getText()); - } - } - - private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { - var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath); - vaultPath.set(storagePath.resolve(vaultName)); - } - - @FXML - public void chooseCustomVaultPath() { - DirectoryChooser directoryChooser = new DirectoryChooser(); - directoryChooser.setTitle(resourceBundle.getString("addvaultwizard.new.directoryPickerTitle")); - if (Files.exists(customVaultPath)) { - directoryChooser.setInitialDirectory(customVaultPath.toFile()); - } else { - directoryChooser.setInitialDirectory(DEFAULT_CUSTOM_VAULT_PATH.toFile()); - } - final File file = directoryChooser.showDialog(window); - if (file != null) { - customVaultPath = file.toPath().toAbsolutePath(); - vaultPath.set(customVaultPath.resolve(vaultName)); - } - } - - @FXML - public void finish() { - if (!isReadyToImport()) { - return; - } - Path destination = vaultPath.get(); - processing.set(true); - // the template was already unpacked and validated when the deeplink arrived, so only the destination can still - // fail here - which the location picker has just checked, leaving races and hardware faults - Tasks.create(() -> { - template.moveTo(destination); - return vaultListManager.add(destination); - }).onSuccess(newVault -> { - vault.set(newVault); - rememberParentDirectory(destination); - window.setScene(successScene.get()); - }).onError(IOException.class, e -> { - LOG.error("Failed to import vault template.", e); - appWindows.showErrorWindow(e, window, window.getScene()); - }).andFinally(() -> processing.set(false)).runOnce(executor); - } - - private void rememberParentDirectory(Path destination) { - Path parentPath = destination.getParent(); - if (parentPath != null) { - settings.previouslyUsedVaultDirectory.setValue(parentPath); - } - } - - /* Internal classes */ - - private record VaultPathStatus(boolean valid, String localizationKey) { - - } - - /* Getter/Setter */ - - public String getVaultName() { - return vaultName; - } - - public String getHubUrl() { - return hubUrl; - } - - public Path getVaultPath() { - return vaultPath.get(); - } - - public ObjectProperty vaultPathProperty() { - return vaultPath; - } - - public boolean isLoadingPresetLocations() { - return loadingPresetLocations.getValue(); - } - - public BooleanProperty loadingPresetLocationsProperty() { - return loadingPresetLocations; - } - - public BooleanProperty usePresetPathProperty() { - return usePresetPath; - } - - public boolean isUsePresetPath() { - return usePresetPath.get(); - } - - public ObservableValue readyToImportProperty() { - return readyToImport; - } - - public boolean isReadyToImport() { - return readyToImport.getValue(); - } - - public ObjectBinding createButtonStateProperty() { - return createButtonState; - } - - public ContentDisplay getCreateButtonState() { - return createButtonState.get(); - } - - public BooleanBinding anyRadioButtonSelectedProperty() { - return locationPresetsToggler.selectedToggleProperty().isNotNull(); - } - - public boolean isAnyRadioButtonSelected() { - return anyRadioButtonSelectedProperty().get(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateModule.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateModule.java deleted file mode 100644 index df769e485..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateModule.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import dagger.Binds; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.IntoMap; -import org.cryptomator.common.vaults.Vault; -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 org.cryptomator.ui.fxapp.PrimaryStage; - -import javax.inject.Provider; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.Scene; -import javafx.stage.Modality; -import javafx.stage.Stage; -import java.nio.file.Path; -import java.util.Map; -import java.util.ResourceBundle; - -@Module -abstract class ImportTemplateModule { - - @Provides - @ImportTemplateWindow - @ImportTemplateScoped - static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { - return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle); - } - - @Provides - @ImportTemplateWindow - @ImportTemplateScoped - static Stage provideStage(StageFactory factory, @PrimaryStage Stage primaryStage, ResourceBundle resourceBundle) { - Stage stage = factory.create(); - stage.setResizable(false); - stage.initModality(Modality.WINDOW_MODAL); - stage.initOwner(primaryStage); - stage.setTitle(resourceBundle.getString("importTemplate.title")); - return stage; - } - - @Provides - @ImportTemplateScoped - static ObjectProperty provideVaultPath() { - return new SimpleObjectProperty<>(); - } - - @Provides - @ImportTemplateWindow - @ImportTemplateScoped - static ObjectProperty provideVault() { - return new SimpleObjectProperty<>(); - } - - // ------------------ - - @Provides - @FxmlScene(FxmlFile.IMPORT_TEMPLATE_LOCATION) - @ImportTemplateScoped - static Scene provideImportTemplateLocationScene(@ImportTemplateWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.IMPORT_TEMPLATE_LOCATION); - } - - @Provides - @FxmlScene(FxmlFile.IMPORT_TEMPLATE_SUCCESS) - @ImportTemplateScoped - static Scene provideImportTemplateSuccessScene(@ImportTemplateWindow FxmlLoaderFactory fxmlLoaders) { - return fxmlLoaders.createScene(FxmlFile.IMPORT_TEMPLATE_SUCCESS); - } - - // ------------------ - - @Binds - @IntoMap - @FxControllerKey(ImportTemplateLocationController.class) - abstract FxController bindImportTemplateLocationController(ImportTemplateLocationController controller); - - @Binds - @IntoMap - @FxControllerKey(ImportTemplateSuccessController.class) - abstract FxController bindImportTemplateSuccessController(ImportTemplateSuccessController controller); - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateScoped.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateScoped.java deleted file mode 100644 index 1864e23a0..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateScoped.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -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 ImportTemplateScoped { - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateSuccessController.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateSuccessController.java deleted file mode 100644 index c150ca513..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateSuccessController.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import org.cryptomator.common.vaults.Vault; -import org.cryptomator.ui.common.FxController; -import org.cryptomator.ui.fxapp.FxApplicationWindows; - -import javax.inject.Inject; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.ReadOnlyObjectProperty; -import javafx.fxml.FXML; -import javafx.stage.Stage; - -@ImportTemplateScoped -public class ImportTemplateSuccessController implements FxController { - - private final FxApplicationWindows appWindows; - private final Stage window; - private final ReadOnlyObjectProperty vault; - - @Inject - ImportTemplateSuccessController(FxApplicationWindows appWindows, @ImportTemplateWindow Stage window, @ImportTemplateWindow ObjectProperty vault) { - this.appWindows = appWindows; - this.window = window; - this.vault = vault; - } - - @FXML - public void unlockAndClose() { - close(); - appWindows.startUnlockWorkflow(vault.get(), window); - } - - @FXML - public void close() { - window.close(); - } - - public ReadOnlyObjectProperty vaultProperty() { - return vault; - } - - public Vault getVault() { - return vault.get(); - } - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindow.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindow.java deleted file mode 100644 index 8a7bcaac7..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindow.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -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 ImportTemplateWindow { - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindows.java b/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindows.java deleted file mode 100644 index 157aea313..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/ImportTemplateWindows.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import org.cryptomator.ui.dialogs.Dialogs; -import org.cryptomator.ui.fxapp.FxApplicationScoped; -import org.cryptomator.ui.fxapp.PrimaryStage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import javafx.application.Platform; -import javafx.stage.Stage; - -import java.io.IOException; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutorService; - -/** - * Entry point for showing the vault template import flow. - *

- * Everything the flow needs to decide - unpacking the archive, and choosing between the import dialog and the - * "malformed template" dialog. - */ -@FxApplicationScoped -public class ImportTemplateWindows { - - private static final Logger LOG = LoggerFactory.getLogger(ImportTemplateWindows.class); - - private final ImportTemplateComponent.Factory importTemplateWindow; - private final Dialogs dialogs; - private final Stage primaryStage; - private final ExecutorService executor; - - @Inject - ImportTemplateWindows(ImportTemplateComponent.Factory importTemplateWindow, // - Dialogs dialogs, // - @PrimaryStage Stage primaryStage, // - ExecutorService executor) { - this.importTemplateWindow = importTemplateWindow; - this.dialogs = dialogs; - this.primaryStage = primaryStage; - this.executor = executor; - } - - /** - * Unpacks the given archive off the FX thread and shows the import dialog for it. - *

- * A template that could never be imported is rejected before the user is asked to choose a storage location: in that - * case {@link #showMalformedTemplateDialog()} is shown instead and the returned stage is the primary stage. Failures - * that are not the template's fault (the file system refusing to unpack it) complete the stage - * exceptionally, leaving the generic error display to the caller. - * - * @param name the fixed vault name - * @param archive the ZIP archive bytes - * @return the import window, or the primary stage if the template was rejected - */ - public CompletionStage extractAndShowImportTemplateWindow(String name, byte[] archive) { - return extractAsync(archive) - .handleAsync((template, throwable) -> { - if (throwable != null) { - var cause = throwable.getCause() != null ? throwable.getCause() : throwable; - if (cause instanceof MalformedTemplateException) { - // template itself is invalid - LOG.error("Vault template is malformed.", cause); - return showMalformedTemplateDialog(); - } else { - LOG.error("Failed to unpack vault template.", cause); - throw new CompletionException(cause); - } - } - try { - var component = importTemplateWindow.create(name, template); - component.showImportTemplateWindow(); - return component.window(); - } catch (RuntimeException e) { - template.close(); //nothing took ownership, so the temporary directory is ours to discard - throw e; - } - }, Platform::runLater); - } - - /** - * Async Wrapper for {@link VaultTemplate#extract(byte[])} - *

- * Failures arrive as a {@link CompletionException} wrapping the cause: a {@link MalformedTemplateException} - * means the template itself is a dead end, any other {@link IOException} means unpacking failed for - * an environmental reason. - * - * @param archive the ZIP archive bytes - * @return the unpacked template, which the caller must close - * @see VaultTemplate#extract(byte[]) - */ - CompletionStage extractAsync(byte[] archive) { - return CompletableFuture.supplyAsync(()-> { - try { - return VaultTemplate.extract(archive); - } catch (IOException e) { - throw new CompletionException(e); - } - }, executor); - } - - /** - * Tells the user that the template cannot be imported. Must be called on the FX thread. - * - * @return the primary stage, which is what remains visible once the user dismisses the dialog - */ - public Stage showMalformedTemplateDialog() { - dialogs.prepareMalformedTemplateDialog(primaryStage).build().showAndWait(); - return primaryStage; - } - -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/MalformedTemplateException.java b/src/main/java/org/cryptomator/ui/importtemplate/MalformedTemplateException.java deleted file mode 100644 index bc51d3a64..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/MalformedTemplateException.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import java.io.IOException; - -/** - * Indicates that a vault template cannot be imported because the archive itself is unusable - as opposed to an - * {@link IOException} arising from the destination (already exists, not writable, ...). - *

- * Callers distinguish the two to decide what to show the user: a malformed template is a dead end, whereas a - * destination problem is recoverable by picking a different location. - */ -public class MalformedTemplateException extends IOException { - - private static final long serialVersionUID = 1L; - - public MalformedTemplateException(String message) { - super(message); - } - - public MalformedTemplateException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplate.java b/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplate.java deleted file mode 100644 index 1126d7919..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplate.java +++ /dev/null @@ -1,121 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import org.cryptomator.common.Constants; -import org.cryptomator.cryptofs.VaultConfig; -import org.cryptomator.cryptofs.VaultConfigLoadException; -import org.cryptomator.ui.keyloading.hub.HubConfig; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; - -/** - * A vault template that has been unpacked to a temporary directory and found sound. - *

- * Holding an instance means the template is valid: it was a readable ZIP within the entry and size - * limits, held a decodable {@value Constants#VAULTCONFIG_FILENAME} directly in its root and no entry escaped - * the extraction directory. - *

- * Because the vault lies in the archive root, the temporary directory is the vault directory: importing moves - * it wholesale to its destination. - *

- * The instance owns that temporary directory and must be {@link #close() closed}, whether or not the - * import completes. - *

- * {@link #hubUrl()} is read from the unverified vault config. - */ -public final class VaultTemplate implements AutoCloseable { - - private static final Logger LOG = LoggerFactory.getLogger(VaultTemplate.class); - - private final Path vaultDir; - private final @Nullable String hubUrl; - - private VaultTemplate(Path vaultDir, @Nullable String hubUrl) { - this.vaultDir = vaultDir; - this.hubUrl = hubUrl; - } - - /** - * Unpacks the given archive to a temporary directory and validates it. - *

- * This touches the file system and should not run on the FX thread. - * - * @param archive the ZIP archive bytes - * @return the unpacked template, which the caller must close - * @throws MalformedTemplateException if the archive is unusable - * @throws IOException if unpacking fails - */ - public static VaultTemplate extract(byte[] archive) throws IOException { - return extract(archive, null); - } - - @VisibleForTesting - static VaultTemplate extract(byte[] archive, @Nullable Path tempParent) throws IOException { - Path vaultDir = tempParent == null // - ? Files.createTempDirectory("vault-template-") // - : Files.createTempDirectory(tempParent, "vault-template-"); - try { - VaultTemplateExtractor.extractTo(archive, vaultDir); - var hubUrl = readHubUrl(vaultDir); - return new VaultTemplate(vaultDir, hubUrl); - } catch (IOException | RuntimeException e) { - VaultTemplateExtractor.deleteQuietly(vaultDir); - throw e; - } - } - - private static @Nullable String readHubUrl(Path vaultDir) throws IOException { - var token = Files.readString(vaultDir.resolve(Constants.VAULTCONFIG_FILENAME), StandardCharsets.US_ASCII).trim(); - HubConfig hubConfig; - try { - hubConfig = VaultConfig.decode(token).getHeader("hub", HubConfig.class); - } catch (VaultConfigLoadException e) { - throw new MalformedTemplateException("Template does not contain a decodable vault config.", e); - } - if (hubConfig == null) { - return null; // not a hub vault - allowed, the user just cannot be shown a Hub - } - try { - return hubConfig.getApiBaseUrl().toString(); - } catch (RuntimeException e) { - // hub header present but unusable (e.g. neither apiBaseUrl nor devicesResourceUrl set) - LOG.warn("Vault template declares an unusable hub config.", e); - return null; - } - } - - /** - * The Hub instance which generated the template, or {@code null} if its config declares none. - *

- * Unverified - see the class documentation. - */ - public @Nullable String hubUrl() { - return hubUrl; - } - - /** - * Moves the unpacked vault to its final location. - * - * @param destination the target vault directory, which must not yet exist - * @throws java.nio.file.FileAlreadyExistsException if {@code destination} already exists - * @throws IOException if the vault cannot be moved - */ - public void moveTo(Path destination) throws IOException { - VaultTemplateExtractor.moveToDestination(vaultDir, destination); - } - - /** - * Discards the unpacked vault. A no-op after a successful {@link #moveTo(Path)}, which relocates the directory - * this would otherwise delete. - */ - @Override - public void close() { - VaultTemplateExtractor.deleteQuietly(vaultDir); - } -} diff --git a/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplateExtractor.java b/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplateExtractor.java deleted file mode 100644 index 77bf46565..000000000 --- a/src/main/java/org/cryptomator/ui/importtemplate/VaultTemplateExtractor.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.cryptomator.ui.importtemplate; - -import org.cryptomator.common.Constants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Comparator; -import java.util.stream.Stream; -import java.util.zip.ZipException; - -/** - * Low-level mechanics of unpacking a vault template (a ZIP archive holding a ready-made vault). - *

- * Per the template spec the vault lies directly in the archive root, i.e. the archive holds - * {@value Constants#VAULTCONFIG_FILENAME} at its top level rather than inside an enclosing folder. The extraction - * directory therefore is the vault directory. Moving it to its final location is a separate step, so the vault - * only ever appears complete at its destination. - *

- * Lifecycle of the temporary directory is owned by {@link VaultTemplate}, not by this class. - */ -final class VaultTemplateExtractor { - - private static final Logger LOG = LoggerFactory.getLogger(VaultTemplateExtractor.class); - // On Windows, URI template (base64url encoded) is is given on command line argument. Windows API restrict the length - // to 32,767 chars (approx. ~24KB template size). - // Note that larger links are dropped by the OS anyway, but we also set the accepted size uniform across platforms - static final long MAX_TOTAL_BYTES = 24_000; // ~24 KB, see above - static final int MAX_ENTRIES = 30; // files + directories below the zip root - - private VaultTemplateExtractor() { - } - - /** - * Unpacks the given template into {@code targetDir}, which then holds the vault itself. - * - * @param template the ZIP archive bytes - * @param targetDir an existing, empty directory to unpack into - * @throws MalformedTemplateException if the template is not a readable ZIP, exceeds the entry or size limits, or - * does not hold {@value Constants#VAULTCONFIG_FILENAME} in its root - * @throws IOException if the files cannot be written - */ - static void extractTo(byte[] template, Path targetDir) throws IOException { - Path tmpZip = Files.createTempFile("vault-template-", ".zip"); - try { - Files.write(tmpZip, template); - unzip(tmpZip, targetDir); - } finally { - deleteQuietly(tmpZip); - } - } - - private static void unzip(Path zipFile, Path targetDir) throws IOException { - Path normalizedTarget = targetDir.normalize(); - try (FileSystem zipFs = FileSystems.newFileSystem(zipFile)) { - Path zipRoot = zipFs.getRootDirectories().iterator().next(); //we take the first available root and ignore others - Files.walkFileTree(zipRoot, new TemplateExtractionVisitor(zipRoot, normalizedTarget, MAX_ENTRIES, MAX_TOTAL_BYTES)); - } catch (ZipException e) { - throw new MalformedTemplateException("Template is not a readable ZIP archive.", e); - } - if (!Files.isRegularFile(normalizedTarget.resolve(Constants.VAULTCONFIG_FILENAME))) { - throw new MalformedTemplateException("Template does not hold " + Constants.VAULTCONFIG_FILENAME + " in its root directory."); - } - } - - static class TemplateExtractionVisitor extends SimpleFileVisitor { - - private final Path zipRoot; - private final Path target; - private final int maxEntries; - private final long maxSize; - - private int totalEntries = 0; - private long totalBytes = 0; - - TemplateExtractionVisitor(Path zipRoot, Path target, int maxEntries, long maxSize) { - this.zipRoot = zipRoot; - this.target = target; - this.maxEntries = maxEntries; - this.maxSize = maxSize; - } - - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - if (!dir.equals(zipRoot)) { //the zip root is not part of the template's content - countEntry(); - } - Files.createDirectories(resolveSafely(target, zipRoot, dir)); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - countEntry(); - totalBytes += attrs.size(); - if (totalBytes > maxSize) { - throw new MalformedTemplateException("Vault template exceeds the maximum allowed size of " + maxSize + " bytes."); - } - - Files.copy(file, resolveSafely(target, zipRoot, file), StandardCopyOption.REPLACE_EXISTING); - return FileVisitResult.CONTINUE; - } - - private void countEntry() throws IOException { - totalEntries++; - if ( totalEntries > maxEntries) { - throw new MalformedTemplateException("Vault template contains more than the maximum allowed " + maxEntries + " entries."); - } - } - } - - private static Path resolveSafely(Path targetDir, Path zipRoot, Path entry) throws IOException { - Path resolved = targetDir.resolve(zipRoot.relativize(entry).toString()).normalize(); - if (!resolved.startsWith(targetDir)) { - throw new MalformedTemplateException("Refusing to extract entry outside of target directory: " + entry); - } - return resolved; - } - - /** - * Moves an unpacked vault to its final location, creating missing parent directories. - * - * @param vaultRoot the unpacked vault directory - * @param destination the target vault directory, which must not yet exist - * @throws FileAlreadyExistsException if {@code destination} already exists - */ - static void moveToDestination(Path vaultRoot, Path destination) throws IOException { - if (Files.exists(destination)) { - throw new FileAlreadyExistsException(destination.toString()); - } - Path parent = destination.getParent(); - if (parent != null) { - Files.createDirectories(parent); - } - move(vaultRoot, destination); - } - - private static void move(Path source, Path destination) throws IOException { - try { - Files.move(source, destination, StandardCopyOption.ATOMIC_MOVE); - return; - } catch (AtomicMoveNotSupportedException | UnsupportedOperationException e) { - // provider without atomic move support - fall through to non-atomic strategies - } - try { - Files.move(source, destination); - } catch (IOException e) { - // likely a cross-store move of a non-empty directory: copy recursively, then delete the source - copyRecursively(source, destination); - deleteRecursively(source); - } - } - - private static void copyRecursively(Path source, Path target) throws IOException { - Files.walkFileTree(source, new SimpleFileVisitor<>() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - Files.createDirectories(target.resolve(source.relativize(dir).toString())); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.copy(file, target.resolve(source.relativize(file).toString()), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); - return FileVisitResult.CONTINUE; - } - }); - } - - static void deleteQuietly(Path path) { - try { - deleteRecursively(path); - } catch (IOException e) { - LOG.warn("Failed to clean up temporary path {}", path, e); - } - } - - private static void deleteRecursively(Path path) throws IOException { - if (!Files.exists(path)) { - return; - } - try (Stream stream = Files.walk(path)) { - var paths = stream.sorted(Comparator.reverseOrder()).toList(); - for (Path p : paths) { - Files.deleteIfExists(p); - } - } - } - -} diff --git a/src/main/resources/fxml/import_template_location.fxml b/src/main/resources/fxml/import_template_location.fxml deleted file mode 100644 index a580350d9..000000000 --- a/src/main/resources/fxml/import_template_location.fxml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fxml/import_template_success.fxml b/src/main/resources/fxml/import_template_success.fxml deleted file mode 100644 index 58e409114..000000000 --- a/src/main/resources/fxml/import_template_success.fxml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -