diff --git a/src/main/java/org/cryptomator/common/vaults/Vault.java b/src/main/java/org/cryptomator/common/vaults/Vault.java index fec8203b9..f857d6ba1 100644 --- a/src/main/java/org/cryptomator/common/vaults/Vault.java +++ b/src/main/java/org/cryptomator/common/vaults/Vault.java @@ -44,6 +44,7 @@ import javafx.beans.property.SimpleBooleanProperty; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.ReadOnlyFileSystemException; import java.util.EnumSet; import java.util.Objects; import java.util.Set; @@ -111,7 +112,15 @@ public class Vault { private CryptoFileSystem createCryptoFileSystem(MasterkeyLoader keyLoader) throws IOException, MasterkeyLoadingFailedException { Set flags = EnumSet.noneOf(FileSystemFlags.class); - if (vaultSettings.usesReadOnlyMode.get()) { + var createReadOnly = vaultSettings.usesReadOnlyMode.get(); + try { + FileSystemCapabilityChecker.assertWriteAccess(getPath()); + } catch (FileSystemCapabilityChecker.MissingCapabilityException e) { + if (!createReadOnly) { + throw new ReadOnlyFileSystemException(); + } + } + if (createReadOnly) { flags.add(FileSystemFlags.READONLY); } else if (vaultSettings.maxCleartextFilenameLength.get() == -1) { LOG.debug("Determining cleartext filename length limitations..."); diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index f222d92c4..c8b7bee22 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -40,7 +40,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.util.Optional; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; @@ -50,7 +49,7 @@ public class CreateNewVaultLocationController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); - private static final String TEMP_FILE_FORMAT = ".locationTest.cryptomator.tmp"; + private static final String TEMP_FILE_PREFIX = ".locationTest.cryptomator"; private final Stage window; private final Lazy chooseNameScene; @@ -126,16 +125,19 @@ public class CreateNewVaultLocationController implements FxController { private boolean isActuallyWritable(Path p) { - Path tmpFile = p.resolve(TEMP_FILE_FORMAT); - try (var chan = Files.newByteChannel(tmpFile, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE, StandardOpenOption.DELETE_ON_CLOSE)) { + Path tmpDir = null; + try { + tmpDir = Files.createTempDirectory(p, TEMP_FILE_PREFIX ); return true; } catch (IOException e) { return false; } finally { - try { - Files.deleteIfExists(tmpFile); - } catch (IOException e) { - LOG.warn("Unable to delete temporary file {}. Needs to be deleted manually.", tmpFile); + if (tmpDir != null) { + try { + Files.deleteIfExists(tmpDir); + } catch (IOException e) { + LOG.warn("Unable to delete temporary directory {}. Needs to be deleted manually.", tmpDir); + } } } } diff --git a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java index 7fb170dc3..b6066b655 100644 --- a/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java +++ b/src/main/java/org/cryptomator/ui/dialogs/Dialogs.java @@ -72,4 +72,16 @@ public class Dialogs { .setCancelAction(cancelAction); } + public SimpleDialog.Builder prepareRetryIfReadonlyDialog(Stage window, Consumer okAction) { + return createDialogBuilder() // + .setOwner(window) // + .setTitleKey("retryIfReadonly.title") // + .setMessageKey("retryIfReadonly.message") // + .setDescriptionKey("retryIfReadonly.description") // + .setIcon(FontAwesome5Icon.EXCLAMATION) // + .setOkButtonKey("retryIfReadonly.retry") // + .setCancelButtonKey("generic.button.close") // + .setOkAction(okAction) // + .setCancelAction(Stage::close); + } } diff --git a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java index 804f4cd67..4b78bf693 100644 --- a/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java +++ b/src/main/java/org/cryptomator/ui/unlock/UnlockWorkflow.java @@ -10,6 +10,7 @@ import org.cryptomator.integrations.mount.MountFailedException; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.common.VaultService; +import org.cryptomator.ui.dialogs.Dialogs; import org.cryptomator.ui.fxapp.FxApplicationWindows; import org.cryptomator.ui.fxapp.PrimaryStage; import org.cryptomator.ui.keyloading.KeyLoadingStrategy; @@ -25,6 +26,8 @@ import javafx.scene.Scene; import javafx.stage.Screen; import javafx.stage.Stage; import java.io.IOException; +import java.nio.file.ReadOnlyFileSystemException; +import java.util.concurrent.TimeUnit; /** * A multi-step task that consists of background activities as well as user interaction. @@ -46,6 +49,7 @@ public class UnlockWorkflow extends Task { private final FxApplicationWindows appWindows; private final KeyLoadingStrategy keyLoadingStrategy; private final ObjectProperty illegalMountPointException; + private final Dialogs dialogs; @Inject UnlockWorkflow(@PrimaryStage Stage mainWindow, // @@ -57,7 +61,8 @@ public class UnlockWorkflow extends Task { @FxmlScene(FxmlFile.UNLOCK_REQUIRES_RESTART) Lazy restartRequiredScene, // FxApplicationWindows appWindows, // @UnlockWindow KeyLoadingStrategy keyLoadingStrategy, // - @UnlockWindow ObjectProperty illegalMountPointException) { + @UnlockWindow ObjectProperty illegalMountPointException, // + Dialogs dialogs) { this.mainWindow = mainWindow; this.window = window; this.vault = vault; @@ -68,6 +73,7 @@ public class UnlockWorkflow extends Task { this.appWindows = appWindows; this.keyLoadingStrategy = keyLoadingStrategy; this.illegalMountPointException = illegalMountPointException; + this.dialogs = dialogs; } @Override @@ -144,11 +150,36 @@ public class UnlockWorkflow extends Task { switch (throwable) { case IllegalMountPointException e -> handleIllegalMountPointError(e); case ConflictingMountServiceException _ -> handleConflictingMountServiceException(); + case ReadOnlyFileSystemException _ -> handleReadOnlyFileSystem(); default -> handleGenericError(throwable); } vault.stateProperty().transition(VaultState.Value.PROCESSING, VaultState.Value.LOCKED); } + private void handleReadOnlyFileSystem() { + var readOnlyDialog = dialogs.prepareRetryIfReadonlyDialog(mainWindow, stage -> { + stage.close(); + this.retry(); + }).build(); + + Platform.runLater(readOnlyDialog::showAndWait); + } + + private void retry() { + try { + vault.getVaultSettings().usesReadOnlyMode.set(true); + var isLocked = vault.stateProperty().awaitState(VaultState.Value.LOCKED, 5, TimeUnit.SECONDS); + if (!isLocked) { + LOG.error("Vault did not changed to LOCKED state within 5 seconds. Aborting unlock retry."); + } else { + appWindows.startUnlockWorkflow(vault, mainWindow); + } + } catch (InterruptedException e) { + LOG.error("Waiting for LOCKED vault state was interrupted. Aborting unlock retry.", e); + Thread.currentThread().interrupt(); + } + } + @Override protected void cancelled() { LOG.debug("Unlock of '{}' canceled.", vault.getDisplayName()); diff --git a/src/main/resources/i18n/strings.properties b/src/main/resources/i18n/strings.properties index 7d3a95430..a4829138a 100644 --- a/src/main/resources/i18n/strings.properties +++ b/src/main/resources/i18n/strings.properties @@ -553,6 +553,12 @@ dokanySupportEnd.message=Support end for Dokany dokanySupportEnd.description=The volume type Dokany is no longer supported by Cryptomator. Your settings are adjusted to use the default volume type now. You can view the default type in the preferences. dokanySupportEnd.preferencesBtn=Open Preferences +#Retry If Readonly +retryIfReadonly.title=Restricted Vault Access +retryIfReadonly.message=No write access to vault directory +retryIfReadonly.description=Cryptomator cannot write to the vault directory. You can change the vault to be read-only and try again. This option can be disabled in the vault options. +retryIfReadonly.retry=Change and Retry + # Share Vault shareVault.title=Share Vault shareVault.message=Would you like to share your vault with others?