mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-25 14:10:20 +00:00
Merge branch 'feature/vault-version-4' into develop
This commit is contained in:
@@ -26,6 +26,7 @@ import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
@@ -78,6 +79,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
private final Provider<UnlockedController> unlockedControllerProvider;
|
||||
private final Lazy<ChangePasswordController> changePasswordController;
|
||||
private final Lazy<SettingsController> settingsController;
|
||||
private final Lazy<UpgradeStrategies> upgradeStrategies;
|
||||
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
@@ -90,7 +92,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
|
||||
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController) {
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies) {
|
||||
super(localization);
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -102,6 +104,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.unlockedControllerProvider = unlockedControllerProvider;
|
||||
this.changePasswordController = changePasswordController;
|
||||
this.settingsController = settingsController;
|
||||
this.upgradeStrategies = upgradeStrategies;
|
||||
this.vaults = FXCollections.observableList(settings.getDirectories());
|
||||
this.vaults.addListener((Change<? extends Vault> c) -> {
|
||||
settings.save();
|
||||
@@ -288,7 +291,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.showUnlockedView(newValue);
|
||||
} else if (!newValue.doesVaultDirectoryExist()) {
|
||||
this.showNotFoundView();
|
||||
} else if (newValue.isValidVaultDirectory() && newValue.needsUpgrade()) {
|
||||
} else if (newValue.isValidVaultDirectory() && upgradeStrategies.get().getUpgradeStrategy(newValue).isPresent()) {
|
||||
this.showUpgradeView();
|
||||
} else if (newValue.isValidVaultDirectory()) {
|
||||
this.showUnlockView();
|
||||
|
||||
@@ -7,8 +7,10 @@ import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.cryptomator.ui.model.UpgradeInstruction;
|
||||
import org.cryptomator.ui.model.UpgradeInstruction.UpgradeFailedException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy;
|
||||
import org.cryptomator.ui.model.UpgradeStrategy.UpgradeFailedException;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
@@ -16,7 +18,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -30,19 +31,24 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeController.class);
|
||||
|
||||
final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
final ObjectProperty<Optional<UpgradeStrategy>> strategy = new SimpleObjectProperty<>();
|
||||
private final UpgradeStrategies strategies;
|
||||
private final ExecutorService exec;
|
||||
private final Binding<Optional<UpgradeInstruction>> upgradeInstruction = EasyBind.monadic(vault).map(Vault::availableUpgrade);
|
||||
private Optional<UpgradeListener> listener = Optional.empty();
|
||||
|
||||
@Inject
|
||||
public UpgradeController(Localization localization, ExecutorService exec) {
|
||||
public UpgradeController(Localization localization, UpgradeStrategies strategies, ExecutorService exec) {
|
||||
super(localization);
|
||||
this.strategies = strategies;
|
||||
this.exec = exec;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private Label upgradeLabel;
|
||||
|
||||
@FXML
|
||||
private SecPasswordField passwordField;
|
||||
|
||||
@FXML
|
||||
private Button upgradeButton;
|
||||
|
||||
@@ -54,10 +60,12 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
@Override
|
||||
protected void initialize() {
|
||||
upgradeLabel.textProperty().bind(EasyBind.monadic(upgradeInstruction).map(instruction -> {
|
||||
upgradeLabel.textProperty().bind(EasyBind.monadic(strategy).map(instruction -> {
|
||||
return instruction.map(this::upgradeNotification).orElse("");
|
||||
}).orElse(""));
|
||||
|
||||
upgradeButton.disableProperty().bind(passwordField.textProperty().isEmpty().or(passwordField.disabledProperty()));
|
||||
|
||||
EasyBind.subscribe(vault, this::vaultDidChange);
|
||||
}
|
||||
|
||||
@@ -68,14 +76,18 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
private void vaultDidChange(Vault newVault) {
|
||||
errorLabel.setText(null);
|
||||
strategy.set(strategies.getUpgradeStrategy(newVault));
|
||||
// trigger "default" change to refresh key bindings:
|
||||
upgradeButton.setDefaultButton(false);
|
||||
upgradeButton.setDefaultButton(true);
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
// Upgrade label
|
||||
// ****************************************
|
||||
|
||||
private String upgradeNotification(UpgradeInstruction instruction) {
|
||||
return instruction.getNotification(vault.get(), localization);
|
||||
private String upgradeNotification(UpgradeStrategy instruction) {
|
||||
return instruction.getNotification(vault.get());
|
||||
}
|
||||
|
||||
// ****************************************
|
||||
@@ -84,36 +96,45 @@ public class UpgradeController extends LocalizedFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickUpgradeButton(ActionEvent event) {
|
||||
upgradeInstruction.getValue().ifPresent(this::upgrade);
|
||||
strategy.getValue().ifPresent(this::upgrade);
|
||||
}
|
||||
|
||||
private void upgrade(UpgradeInstruction instruction) {
|
||||
Vault v = vault.getValue();
|
||||
Objects.requireNonNull(v);
|
||||
private void upgrade(UpgradeStrategy instruction) {
|
||||
Vault v = Objects.requireNonNull(vault.getValue());
|
||||
passwordField.setDisable(true);
|
||||
progressIndicator.setVisible(true);
|
||||
upgradeButton.setDisable(true);
|
||||
exec.submit(() -> {
|
||||
if (!instruction.isApplicable(v)) {
|
||||
LOG.error("No upgrade needed for " + v.path().getValue());
|
||||
throw new IllegalStateException("No ugprade needed for " + v.path().getValue());
|
||||
}
|
||||
try {
|
||||
instruction.upgrade(v, localization);
|
||||
Platform.runLater(() -> {
|
||||
progressIndicator.setVisible(false);
|
||||
upgradeButton.setDisable(false);
|
||||
listener.ifPresent(UpgradeListener::didUpgrade);
|
||||
});
|
||||
instruction.upgrade(v, passwordField.getCharacters());
|
||||
Platform.runLater(this::showNextUpgrade);
|
||||
} catch (UpgradeFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText(e.getLocalizedMessage());
|
||||
});
|
||||
} finally {
|
||||
Platform.runLater(() -> {
|
||||
progressIndicator.setVisible(false);
|
||||
upgradeButton.setDisable(false);
|
||||
passwordField.setDisable(false);
|
||||
passwordField.swipe();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showNextUpgrade() {
|
||||
errorLabel.setText(null);
|
||||
Optional<UpgradeStrategy> nextStrategy = strategies.getUpgradeStrategy(vault.getValue());
|
||||
if (nextStrategy.isPresent()) {
|
||||
strategy.set(nextStrategy);
|
||||
} else {
|
||||
listener.ifPresent(UpgradeListener::didUpgrade);
|
||||
}
|
||||
}
|
||||
|
||||
/* callback */
|
||||
|
||||
public void setListener(UpgradeListener listener) {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
|
||||
public interface UpgradeInstruction {
|
||||
|
||||
static UpgradeInstruction[] AVAILABLE_INSTRUCTIONS = {new UpgradeVersion3DropBundleExtension()};
|
||||
|
||||
/**
|
||||
* @return Localized string to display to the user when an upgrade is needed.
|
||||
*/
|
||||
String getNotification(Vault vault, Localization localization);
|
||||
|
||||
/**
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
void upgrade(Vault vault, Localization localization) throws UpgradeFailedException;
|
||||
|
||||
/**
|
||||
* Determines in O(1), if an upgrade can be applied to a vault.
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
boolean isApplicable(Vault vault);
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
*/
|
||||
public class UpgradeFailedException extends Exception {
|
||||
|
||||
UpgradeFailedException() {
|
||||
}
|
||||
|
||||
UpgradeFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class UpgradeStrategies {
|
||||
|
||||
private final Collection<UpgradeStrategy> strategies;
|
||||
|
||||
@Inject
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2));
|
||||
}
|
||||
|
||||
public Optional<UpgradeStrategy> getUpgradeStrategy(Vault vault) {
|
||||
return strategies.stream().filter(strategy -> {
|
||||
return strategy.isApplicable(vault);
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class);
|
||||
|
||||
protected final Provider<Cryptor> cryptorProvider;
|
||||
protected final Localization localization;
|
||||
|
||||
UpgradeStrategy(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
this.localization = localization;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Localized string to display to the user when an upgrade is needed.
|
||||
*/
|
||||
public abstract String getNotification(Vault vault);
|
||||
|
||||
/**
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
// create backup, as soon as we know the password was correct:
|
||||
final Path masterkeyBackupFile = vault.path().getValue().resolve(Constants.MASTERKEY_BACKUP_FILENAME);
|
||||
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
// do stuff:
|
||||
upgrade(vault, cryptor);
|
||||
// write updated masterkey file:
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
final Path masterkeyFileAfterUpgrading = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrading, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException;
|
||||
|
||||
/**
|
||||
* Determines in O(1), if an upgrade can be applied to a vault.
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
public abstract boolean isApplicable(Vault vault);
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
*/
|
||||
public class UpgradeFailedException extends Exception {
|
||||
|
||||
UpgradeFailedException() {
|
||||
}
|
||||
|
||||
UpgradeFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,19 +4,36 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Platform;
|
||||
|
||||
class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
@Singleton
|
||||
class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3DropBundleExtension.class);
|
||||
private final Settings settings;
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3DropBundleExtension(Provider<Cryptor> cryptorProvider, Localization localization, Settings settings) {
|
||||
super(cryptorProvider, localization);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault, Localization localization) {
|
||||
public String getNotification(Vault vault) {
|
||||
String fmt = localization.getString("upgrade.version3dropBundleExtension.msg");
|
||||
Path path = vault.path().getValue();
|
||||
String oldVaultName = path.getFileName().toString();
|
||||
@@ -25,7 +42,26 @@ class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(Vault vault, Localization localization) throws UpgradeFailedException {
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
upgrade(vault, cryptor);
|
||||
// don't write new masterkey. this is a special case, as we were stupid and didn't increase the vault version with this upgrade...
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path path = vault.path().getValue();
|
||||
String oldVaultName = path.getFileName().toString();
|
||||
String newVaultName = StringUtils.removeEnd(oldVaultName, Vault.VAULT_FILE_EXTENSION);
|
||||
@@ -39,6 +75,7 @@ class UpgradeVersion3DropBundleExtension implements UpgradeInstruction {
|
||||
Files.move(path, path.resolveSibling(newVaultName));
|
||||
Platform.runLater(() -> {
|
||||
vault.setPath(newPath);
|
||||
settings.save();
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Vault migration failed", e);
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.FileVisitor;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion3to4.class);
|
||||
private static final Pattern BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN = Pattern.compile("^(([A-Z2-7]{8})*[A-Z2-7=]{8})_");
|
||||
private static final int FILE_MIN_SIZE = 88; // vault version 3 files have a header of 88 bytes (assuming no chunks at all)
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3to4(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
super(cryptorProvider, localization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault) {
|
||||
return localization.getString("upgrade.version3to4.msg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path dataDir = vault.path().get().resolve("d");
|
||||
if (!Files.isDirectory(dataDir)) {
|
||||
return; // empty vault. no migration needed.
|
||||
}
|
||||
try {
|
||||
Files.walkFileTree(dataDir, new FileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
migrate(file, attrs);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||||
throw exc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Migration failed.", e);
|
||||
throw new UpgradeFailedException(localization.getString("upgrade.version3to4.err.io"));
|
||||
}
|
||||
LOG.info("Migration finished.");
|
||||
}
|
||||
|
||||
private void migrate(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
String name = file.getFileName().toString();
|
||||
long size = attrs.size();
|
||||
Matcher m = BASE32_FOLLOWED_BY_UNDERSCORE_PATTERN.matcher(name);
|
||||
if (m.find(0) && size < FILE_MIN_SIZE) {
|
||||
String base32 = m.group(1);
|
||||
String suffix = name.substring(m.end());
|
||||
String renamed = "0" + base32 + (suffix.isEmpty() ? "" : " " + suffix);
|
||||
renameWithoutOverwriting(file, renamed);
|
||||
}
|
||||
}
|
||||
|
||||
private void renameWithoutOverwriting(Path path, String newName) throws IOException {
|
||||
Path newPath = path.resolveSibling(newName);
|
||||
for (int i = 2; Files.exists(newPath); i++) {
|
||||
newPath = path.resolveSibling(newName + " " + i);
|
||||
}
|
||||
Files.move(path, newPath);
|
||||
LOG.info("Renaming {} to {}", path, newPath.getFileName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), StandardCharsets.UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.Normalizer;
|
||||
import java.text.Normalizer.Form;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -64,9 +63,9 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
|
||||
|
||||
private final ObjectProperty<Path> path;
|
||||
private final DeferredCloser closer;
|
||||
private final ShorteningFileSystemFactory shorteningFileSystemFactory;
|
||||
private final CryptoFileSystemFactory cryptoFileSystemFactory;
|
||||
private final DeferredCloser closer;
|
||||
private final BooleanProperty unlocked = new SimpleBooleanProperty();
|
||||
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
@@ -82,9 +81,9 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
*/
|
||||
Vault(Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
|
||||
this.closer = closer;
|
||||
this.shorteningFileSystemFactory = shorteningFileSystemFactory;
|
||||
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
|
||||
this.closer = closer;
|
||||
|
||||
try {
|
||||
setMountName(name().getValue());
|
||||
@@ -167,16 +166,6 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
|
||||
}
|
||||
|
||||
public boolean needsUpgrade() {
|
||||
return availableUpgrade().isPresent();
|
||||
}
|
||||
|
||||
public Optional<UpgradeInstruction> availableUpgrade() {
|
||||
return Arrays.stream(UpgradeInstruction.AVAILABLE_INSTRUCTIONS).filter(instruction -> {
|
||||
return instruction.isApplicable(this);
|
||||
}).findAny();
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Delegate methods
|
||||
// ********************************************************************************/
|
||||
|
||||
@@ -11,17 +11,38 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
|
||||
<VBox prefWidth="400.0" prefHeight="400.0" spacing="24.0" alignment="CENTER" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
|
||||
<padding>
|
||||
<Insets top="24.0" right="12.0" bottom="24.0" left="12.0" />
|
||||
</padding>
|
||||
|
||||
<Label fx:id="upgradeLabel" textAlignment="CENTER" wrapText="true"/>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints percentWidth="38.2" />
|
||||
<ColumnConstraints percentWidth="61.8" />
|
||||
</columnConstraints>
|
||||
|
||||
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
|
||||
<children>
|
||||
<Label fx:id="upgradeLabel" wrapText="true" GridPane.rowIndex="0" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
|
||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
|
||||
|
||||
<Label fx:id="errorLabel" textAlignment="CENTER" wrapText="true"/>
|
||||
</VBox>
|
||||
<Pane prefHeight="12.0" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
</Pane>
|
||||
|
||||
<Label text="%unlock.label.password" cacheShape="true" cache="true" GridPane.rowIndex="2" GridPane.columnIndex="0" />
|
||||
<SecPasswordField fx:id="passwordField" cacheShape="true" cache="true" GridPane.rowIndex="2" GridPane.columnIndex="1" />
|
||||
|
||||
<HBox alignment="CENTER_RIGHT" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true">
|
||||
<Button fx:id="upgradeButton" text="%upgrade.button" prefWidth="150.0" onAction="#didClickUpgradeButton" cacheShape="true" cache="true" />
|
||||
</HBox>
|
||||
|
||||
<ProgressIndicator progress="-1" fx:id="progressIndicator" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" GridPane.rowIndex="4" GridPane.columnIndex="0" GridPane.columnSpan="2" />
|
||||
|
||||
<Label fx:id="errorLabel" wrapText="true" GridPane.rowIndex="5" GridPane.columnIndex="0" GridPane.columnSpan="2" cacheShape="true" cache="true" />
|
||||
</children>
|
||||
</GridPane>
|
||||
|
||||
@@ -26,7 +26,7 @@ initialize.label.password=Password
|
||||
initialize.label.retypePassword=Retype password
|
||||
initialize.button.ok=Create vault
|
||||
initialize.messageLabel.alreadyInitialized=Vault already initialized
|
||||
initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
|
||||
initialize.messageLabel.initializationFailed=Could not initialize vault. See log file for details.
|
||||
initialize.messageLabel.passwordStrength.0=Very weak
|
||||
initialize.messageLabel.passwordStrength.1=Weak
|
||||
initialize.messageLabel.passwordStrength.2=Fair
|
||||
@@ -43,6 +43,9 @@ upgrade.button=Upgrade vault
|
||||
upgrade.version3dropBundleExtension.msg=This vault needs to be migrated to a newer format.\n"%1$s" will be renamed to "%2$s".\nPlease make sure synchronization has finished before proceeding.
|
||||
upgrade.version3dropBundleExtension.err.alreadyExists=Automatic migration failed.\n"%s" already exists.
|
||||
|
||||
upgrade.version3to4.msg=This vault needs to be migrated to a newer format.\nEncrypted folder names will be updated.\nPlease make sure synchronization has finished before proceeding.
|
||||
upgrade.version3to4.err.io=Migration failed due to an I/O Exception. See log file for details.
|
||||
|
||||
# unlock.fxml
|
||||
unlock.label.password=Password
|
||||
unlock.label.mountName=Drive name
|
||||
@@ -54,7 +57,7 @@ unlock.button.advancedOptions.show=More options
|
||||
unlock.button.advancedOptions.hide=Less options
|
||||
unlock.choicebox.winDriveLetter.auto=Assign automatically
|
||||
unlock.errorMessage.wrongPassword=Wrong password
|
||||
unlock.errorMessage.mountingFailed=Mounting failed. See logfile for details.
|
||||
unlock.errorMessage.mountingFailed=Mounting failed. See log file for details.
|
||||
unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
|
||||
unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
|
||||
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
|
||||
|
||||
Reference in New Issue
Block a user