mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-07-31 04:22:40 +00:00
first compile-clean attempt to integrate the layered I/O subsystem with the existing UI
This commit is contained in:
@@ -7,18 +7,17 @@ import java.util.concurrent.Executors;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.crypto.SamplingCryptorDecorator;
|
||||
import org.cryptomator.crypto.aes256.CryptoModule;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemModule;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
|
||||
import org.cryptomator.ui.model.VaultObjectMapperProvider;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
import org.cryptomator.ui.util.DeferredCloser.Closer;
|
||||
import org.cryptomator.ui.util.SemVerComparator;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounter;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounterProvider;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -26,7 +25,7 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
|
||||
@Module(includes = CryptoModule.class)
|
||||
@Module(includes = CryptoFileSystemModule.class)
|
||||
class CryptomatorModule {
|
||||
|
||||
private final Application application;
|
||||
@@ -83,18 +82,11 @@ class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
WebDavServer provideWebDavServer() {
|
||||
final WebDavServer webDavServer = new WebDavServer();
|
||||
FrontendFactory provideFrontendFactory(WebDavServer webDavServer) {
|
||||
webDavServer.start();
|
||||
return closeLater(webDavServer, WebDavServer::stop);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("SamplingCryptor")
|
||||
Cryptor provideCryptor(Cryptor cryptor) {
|
||||
return SamplingCryptorDecorator.decorate(cryptor);
|
||||
}
|
||||
|
||||
private <T> T closeLater(T object, Closer<T> closer) {
|
||||
return deferredCloser.closeLater(object, closer).get().get();
|
||||
}
|
||||
|
||||
+71
-77
@@ -1,28 +1,17 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -107,75 +96,80 @@ public class ChangePasswordController extends AbstractFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickChangePasswordButton(ActionEvent event) {
|
||||
downloadsPageLink.setVisible(false);
|
||||
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
|
||||
// decrypt with old password:
|
||||
final CharSequence oldPassword = oldPasswordField.getCharacters();
|
||||
try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
|
||||
vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
|
||||
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException ex) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
return;
|
||||
} catch (WrongPasswordException e) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
Platform.runLater(oldPasswordField::requestFocus);
|
||||
return;
|
||||
} catch (UnsupportedKeyLengthException ex) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
return;
|
||||
} catch (UnsupportedVaultException e) {
|
||||
downloadsPageLink.setVisible(true);
|
||||
if (e.isVaultOlderThanSoftware()) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
} else if (e.isSoftwareOlderThanVault()) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
}
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
return;
|
||||
} finally {
|
||||
oldPasswordField.swipe();
|
||||
}
|
||||
|
||||
// when we reach this line, decryption was successful.
|
||||
|
||||
// encrypt with new password:
|
||||
final CharSequence newPassword = newPasswordField.getCharacters();
|
||||
try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
|
||||
vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
|
||||
messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
|
||||
Platform.runLater(this::didChangePassword);
|
||||
// At this point the backup is still using the old password.
|
||||
// It will be changed as soon as the user unlocks the vault the next time.
|
||||
// This way he can still restore the old password, if he doesn't remember the new one.
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
|
||||
this.restoreBackupQuietly();
|
||||
} finally {
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
}
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
/*
|
||||
* downloadsPageLink.setVisible(false);
|
||||
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
*
|
||||
* // decrypt with old password:
|
||||
* final CharSequence oldPassword = oldPasswordField.getCharacters();
|
||||
* try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
|
||||
* vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
|
||||
* Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
* } catch (IOException ex) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
* LOG.error("Decryption failed for technical reasons.", ex);
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } catch (WrongPasswordException e) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* Platform.runLater(oldPasswordField::requestFocus);
|
||||
* return;
|
||||
* } catch (UnsupportedKeyLengthException ex) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
* LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } catch (UnsupportedVaultException e) {
|
||||
* downloadsPageLink.setVisible(true);
|
||||
* if (e.isVaultOlderThanSoftware()) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
* } else if (e.isSoftwareOlderThanVault()) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
* }
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } finally {
|
||||
* oldPasswordField.swipe();
|
||||
* }
|
||||
*
|
||||
* // when we reach this line, decryption was successful.
|
||||
*
|
||||
* // encrypt with new password:
|
||||
* final CharSequence newPassword = newPasswordField.getCharacters();
|
||||
* try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
|
||||
* vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
|
||||
* messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
|
||||
* Platform.runLater(this::didChangePassword);
|
||||
* // At this point the backup is still using the old password.
|
||||
* // It will be changed as soon as the user unlocks the vault the next time.
|
||||
* // This way he can still restore the old password, if he doesn't remember the new one.
|
||||
* } catch (IOException ex) {
|
||||
* LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
|
||||
* this.restoreBackupQuietly();
|
||||
* } finally {
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
private void restoreBackupQuietly() {
|
||||
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
try {
|
||||
Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Restoring Backup failed.", ex);
|
||||
}
|
||||
/*
|
||||
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
* try {
|
||||
* Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
* } catch (IOException ex) {
|
||||
* LOG.error("Restoring Backup failed.", ex);
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
private void didChangePassword() {
|
||||
|
||||
@@ -9,14 +9,8 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -90,23 +84,11 @@ public class InitializeController extends AbstractFXMLViewController {
|
||||
@FXML
|
||||
protected void initializeVault(ActionEvent event) {
|
||||
setControlsDisabled(true);
|
||||
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
try (OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW)) {
|
||||
vault.getCryptor().randomizeMasterKey();
|
||||
vault.getCryptor().encryptMasterKey(masterKeyOutputStream, password);
|
||||
final String dataRootDir = vault.getCryptor().encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
|
||||
final Path dataRootPath = vault.getPath().resolve("d").resolve(dataRootDir);
|
||||
final Path metadataPath = vault.getPath().resolve("m");
|
||||
Files.createDirectories(dataRootPath);
|
||||
Files.createDirectories(metadataPath);
|
||||
if (listener != null) {
|
||||
listener.didInitialize(this);
|
||||
}
|
||||
final CharSequence passphrase = passwordField.getCharacters();
|
||||
try {
|
||||
vault.create(passphrase);
|
||||
} catch (FileAlreadyExistsException ex) {
|
||||
messageLabel.setText(resourceBundle.getString("initialize.messageLabel.alreadyInitialized"));
|
||||
} catch (InvalidPathException ex) {
|
||||
messageLabel.setText(resourceBundle.getString("initialize.messageLabel.invalidPath"));
|
||||
} catch (IOException ex) {
|
||||
LOG.error("I/O Exception", ex);
|
||||
} finally {
|
||||
|
||||
@@ -8,31 +8,22 @@
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.Comparator;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
|
||||
import org.cryptomator.crypto.exceptions.WrongPasswordException;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.frontend.FrontendCreationFailedException;
|
||||
import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.FXThreads;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
import org.cryptomator.ui.util.mount.WindowsDriveLetters;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -258,49 +249,30 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
setControlsDisabled(true);
|
||||
progressIndicator.setVisible(true);
|
||||
downloadsPageLink.setVisible(false);
|
||||
final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
final CharSequence password = passwordField.getCharacters();
|
||||
try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
|
||||
vault.getCryptor().decryptMasterKey(masterKeyInputStream, password);
|
||||
if (!vault.startServer()) {
|
||||
messageText.setText(resourceBundle.getString("unlock.messageLabel.startServerFailed"));
|
||||
vault.getCryptor().destroy();
|
||||
return;
|
||||
}
|
||||
// at this point we know for sure, that the masterkey can be decrypted, so lets make a backup:
|
||||
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
vault.setUnlocked(true);
|
||||
final Future<Boolean> futureMount = exec.submit(vault::mount);
|
||||
try {
|
||||
vault.activateFrontend(password);
|
||||
Future<Boolean> futureMount = exec.submit(vault::mount);
|
||||
FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
|
||||
} catch (IOException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
} catch (WrongPasswordException e) {
|
||||
} catch (InvalidPassphraseException e) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
|
||||
Platform.runLater(passwordField::requestFocus);
|
||||
} catch (UnsupportedKeyLengthException ex) {
|
||||
} catch (FrontendCreationFailedException ex) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
} catch (UnsupportedVaultException e) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
downloadsPageLink.setVisible(true);
|
||||
if (e.isVaultOlderThanSoftware()) {
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
} else if (e.isSoftwareOlderThanVault()) {
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
}
|
||||
} catch (DestroyFailedException e) {
|
||||
setControlsDisabled(false);
|
||||
progressIndicator.setVisible(false);
|
||||
LOG.error("Destruction of cryptor threw an exception.", e);
|
||||
messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
// } catch (UnsupportedVaultException e) {
|
||||
// setControlsDisabled(false);
|
||||
// progressIndicator.setVisible(false);
|
||||
// downloadsPageLink.setVisible(true);
|
||||
// if (e.isVaultOlderThanSoftware()) {
|
||||
// messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
// } else if (e.isSoftwareOlderThanVault()) {
|
||||
// messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
// }
|
||||
} finally {
|
||||
passwordField.swipe();
|
||||
}
|
||||
@@ -317,18 +289,9 @@ public class UnlockController extends AbstractFXMLViewController {
|
||||
progressIndicator.setVisible(false);
|
||||
setControlsDisabled(false);
|
||||
if (vault.isUnlocked() && !mountSuccess) {
|
||||
exec.submit(() -> {
|
||||
vault.stopServer();
|
||||
vault.setUnlocked(false);
|
||||
});
|
||||
exec.submit(vault::deactivateFrontend);
|
||||
} else if (vault.isUnlocked() && mountSuccess) {
|
||||
exec.submit(() -> {
|
||||
try {
|
||||
vault.reveal();
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.error("Failed to reveal mounted vault", e);
|
||||
}
|
||||
});
|
||||
exec.submit(vault::reveal);
|
||||
}
|
||||
if (mountSuccess && listener != null) {
|
||||
listener.didUnlock(this);
|
||||
|
||||
@@ -15,10 +15,8 @@ import java.util.concurrent.ExecutorService;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.cryptomator.crypto.CryptorIOSampling;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.util.ActiveWindowStyleSupport;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
@@ -30,7 +28,6 @@ import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.chart.LineChart;
|
||||
import javafx.scene.chart.NumberAxis;
|
||||
import javafx.scene.chart.XYChart.Data;
|
||||
import javafx.scene.chart.XYChart.Series;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.stage.Stage;
|
||||
@@ -81,30 +78,21 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickRevealVault(ActionEvent event) {
|
||||
exec.submit(() -> {
|
||||
try {
|
||||
vault.reveal();
|
||||
} catch (CommandFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
messageLabel.setText(resourceBundle.getString("unlocked.label.revealFailed"));
|
||||
});
|
||||
}
|
||||
});
|
||||
exec.submit(vault::reveal);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void didClickCloseVault(ActionEvent event) {
|
||||
exec.submit(() -> {
|
||||
try {
|
||||
vault.unmount();
|
||||
} catch (CommandFailedException e) {
|
||||
Platform.runLater(() -> {
|
||||
messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
|
||||
});
|
||||
return;
|
||||
}
|
||||
vault.stopServer();
|
||||
vault.setUnlocked(false);
|
||||
// try {
|
||||
vault.unmount();
|
||||
// } catch (CommandFailedException e) {
|
||||
// Platform.runLater(() -> {
|
||||
// messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
vault.deactivateFrontend();
|
||||
if (listener != null) {
|
||||
Platform.runLater(() -> {
|
||||
listener.didLock(this);
|
||||
@@ -129,7 +117,7 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
// IO Graph
|
||||
// ****************************************
|
||||
|
||||
private void startIoSampling(final CryptorIOSampling sampler) {
|
||||
private void startIoSampling(final Object sampler) {
|
||||
final Series<Number, Number> decryptedBytes = new Series<>();
|
||||
decryptedBytes.setName("decrypted");
|
||||
final Series<Number, Number> encryptedBytes = new Series<>();
|
||||
@@ -156,14 +144,14 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
private static final double BYTES_TO_MEGABYTES_FACTOR = 1.0 / IO_SAMPLING_INTERVAL / 1024.0 / 1024.0;
|
||||
private static final double SMOOTHING_FACTOR = 0.3;
|
||||
private static final long EFFECTIVELY_ZERO = 100000; // 100kb
|
||||
private final CryptorIOSampling sampler;
|
||||
private final Object sampler;
|
||||
private final Series<Number, Number> decryptedBytes;
|
||||
private final Series<Number, Number> encryptedBytes;
|
||||
private int step = 0;
|
||||
private long oldDecBytes = 0;
|
||||
private long oldEncBytes = 0;
|
||||
private final int step = 0;
|
||||
private final long oldDecBytes = 0;
|
||||
private final long oldEncBytes = 0;
|
||||
|
||||
public IoSamplingAnimationHandler(CryptorIOSampling sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
|
||||
public IoSamplingAnimationHandler(Object sampler, Series<Number, Number> decryptedBytes, Series<Number, Number> encryptedBytes) {
|
||||
this.sampler = sampler;
|
||||
this.decryptedBytes = decryptedBytes;
|
||||
this.encryptedBytes = encryptedBytes;
|
||||
@@ -171,28 +159,30 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
step++;
|
||||
|
||||
final long decBytes = sampler.pollDecryptedBytes(true);
|
||||
final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
|
||||
final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
|
||||
decryptedBytes.getData().add(new Data<Number, Number>(step, smoothedDecMb));
|
||||
if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
decryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
final long encBytes = sampler.pollEncryptedBytes(true);
|
||||
final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
|
||||
final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
|
||||
encryptedBytes.getData().add(new Data<Number, Number>(step, smoothedEncMb));
|
||||
if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
encryptedBytes.getData().remove(0);
|
||||
}
|
||||
|
||||
xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
|
||||
xAxis.setUpperBound(step);
|
||||
/*
|
||||
* step++;
|
||||
*
|
||||
* final long decBytes = sampler.pollDecryptedBytes(true);
|
||||
* final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
|
||||
* final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
* oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
|
||||
* decryptedBytes.getData().add(new Data<Number, Number>(step, smoothedDecMb));
|
||||
* if (decryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
* decryptedBytes.getData().remove(0);
|
||||
* }
|
||||
*
|
||||
* final long encBytes = sampler.pollEncryptedBytes(true);
|
||||
* final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
|
||||
* final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
|
||||
* oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
|
||||
* encryptedBytes.getData().add(new Data<Number, Number>(step, smoothedEncMb));
|
||||
* if (encryptedBytes.getData().size() > IO_SAMPLING_STEPS) {
|
||||
* encryptedBytes.getData().remove(0);
|
||||
* }
|
||||
*
|
||||
* xAxis.setLowerBound(step - IO_SAMPLING_STEPS);
|
||||
* xAxis.setUpperBound(step);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +206,14 @@ public class UnlockedController extends AbstractFXMLViewController {
|
||||
});
|
||||
|
||||
// sample crypto-throughput:
|
||||
stopIoSampling();
|
||||
if (vault.getCryptor() instanceof CryptorIOSampling) {
|
||||
startIoSampling((CryptorIOSampling) vault.getCryptor());
|
||||
} else {
|
||||
ioGraph.setVisible(false);
|
||||
}
|
||||
/*
|
||||
* stopIoSampling();
|
||||
* if (vault.getCryptor() instanceof CryptorIOSampling) {
|
||||
* startIoSampling((CryptorIOSampling) vault.getCryptor());
|
||||
* } else {
|
||||
* ioGraph.setVisible(false);
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
public LockListener getListener() {
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.Normalizer;
|
||||
@@ -11,62 +13,58 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
import org.cryptomator.filesystem.nio.NioFileSystem;
|
||||
import org.cryptomator.frontend.Frontend;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
import org.cryptomator.frontend.FrontendCreationFailedException;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.ui.util.DeferredClosable;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
import org.cryptomator.ui.util.FXThreads;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
import org.cryptomator.ui.util.mount.WebDavMount;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounter;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounter.MountParam;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import dagger.Lazy;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
public class Vault implements Serializable {
|
||||
public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
|
||||
private static final long serialVersionUID = 3754487289683599469L;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Vault.class);
|
||||
|
||||
@Deprecated
|
||||
public static final String VAULT_FILE_EXTENSION = ".cryptomator";
|
||||
|
||||
@Deprecated
|
||||
public static final String VAULT_MASTERKEY_FILE = "masterkey.cryptomator";
|
||||
public static final String VAULT_MASTERKEY_BACKUP_FILE = "masterkey.cryptomator.bkup";
|
||||
|
||||
private final Path path;
|
||||
private final WebDavServer server;
|
||||
private final Cryptor cryptor;
|
||||
private final WebDavMounter mounter;
|
||||
private final Lazy<FrontendFactory> frontendFactory;
|
||||
private final DeferredCloser closer;
|
||||
private final CryptoFileSystemFactory cryptoFileSystemFactory;
|
||||
private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
|
||||
private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
|
||||
private String mountName;
|
||||
private Character winDriveLetter;
|
||||
private DeferredClosable<ServletLifeCycleAdapter> webDavServlet = DeferredClosable.empty();
|
||||
private DeferredClosable<WebDavMount> webDavMount = DeferredClosable.empty();
|
||||
private DeferredClosable<Frontend> filesystemFrontend = DeferredClosable.empty();
|
||||
|
||||
/**
|
||||
* Package private constructor, use {@link VaultFactory}.
|
||||
*/
|
||||
Vault(final Path vaultDirectoryPath, final WebDavServer server, final Cryptor cryptor, final WebDavMounter mounter, final DeferredCloser closer) {
|
||||
Vault(Path vaultDirectoryPath, Lazy<FrontendFactory> frontendFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
this.path = vaultDirectoryPath;
|
||||
this.server = server;
|
||||
this.cryptor = cryptor;
|
||||
this.mounter = mounter;
|
||||
this.frontendFactory = frontendFactory;
|
||||
this.closer = closer;
|
||||
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
|
||||
|
||||
try {
|
||||
setMountName(getName());
|
||||
@@ -84,35 +82,34 @@ public class Vault implements Serializable {
|
||||
return Files.isRegularFile(masterKeyPath);
|
||||
}
|
||||
|
||||
public synchronized boolean startServer() {
|
||||
namesOfResourcesWithInvalidMac.clear();
|
||||
whitelistedResourcesWithInvalidMac.clear();
|
||||
Optional<ServletLifeCycleAdapter> o = webDavServlet.get();
|
||||
if (o.isPresent() && o.get().isRunning()) {
|
||||
return false;
|
||||
public void create(CharSequence passphrase) throws IOException {
|
||||
try {
|
||||
FileSystem fs = NioFileSystem.rootedAt(path);
|
||||
if (fs.children().count() > 0) {
|
||||
throw new FileAlreadyExistsException(null, null, "Vault location not empty.");
|
||||
}
|
||||
cryptoFileSystemFactory.get(fs, passphrase, this);
|
||||
} catch (UncheckedIOException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
ServletLifeCycleAdapter servlet = server.createServlet(path, cryptor, namesOfResourcesWithInvalidMac, whitelistedResourcesWithInvalidMac, mountName);
|
||||
if (servlet.start()) {
|
||||
webDavServlet = closer.closeLater(servlet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void stopServer() {
|
||||
public synchronized void activateFrontend(CharSequence passphrase) throws FrontendCreationFailedException {
|
||||
try {
|
||||
unmount();
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("Unmounting failed. Locking anyway...", e);
|
||||
FileSystem fs = NioFileSystem.rootedAt(path);
|
||||
FileSystem cryptoFs = cryptoFileSystemFactory.get(fs, passphrase, this);
|
||||
String contextPath = StringUtils.prependIfMissing(mountName, "/");
|
||||
Frontend frontend = frontendFactory.get().create(cryptoFs, contextPath);
|
||||
filesystemFrontend = closer.closeLater(frontend);
|
||||
setUnlocked(true);
|
||||
} catch (UncheckedIOException e) {
|
||||
throw new FrontendCreationFailedException(e);
|
||||
}
|
||||
webDavServlet.close();
|
||||
try {
|
||||
cryptor.destroy();
|
||||
} catch (DestroyFailedException e) {
|
||||
LOG.error("Destruction of cryptor throw an exception.", e);
|
||||
}
|
||||
whitelistedResourcesWithInvalidMac.clear();
|
||||
namesOfResourcesWithInvalidMac.clear();
|
||||
}
|
||||
|
||||
public synchronized void deactivateFrontend() {
|
||||
filesystemFrontend.close();
|
||||
setUnlocked(false);
|
||||
}
|
||||
|
||||
private Map<MountParam, Optional<String>> getMountParams() {
|
||||
@@ -123,32 +120,35 @@ public class Vault implements Serializable {
|
||||
}
|
||||
|
||||
public Boolean mount() {
|
||||
final ServletLifeCycleAdapter servlet = webDavServlet.get().orElse(null);
|
||||
if (servlet == null || !servlet.isRunning()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
webDavMount = closer.closeLater(mounter.mount(servlet.getServletUri(), getMountParams()));
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
LOG.warn("mount failed", e);
|
||||
// TODO exception handling
|
||||
Frontend frontend = filesystemFrontend.get().orElse(null);
|
||||
if (frontend == null) {
|
||||
return false;
|
||||
} else {
|
||||
return frontend.mount(getMountParams());
|
||||
}
|
||||
}
|
||||
|
||||
public void reveal() throws CommandFailedException {
|
||||
final WebDavMount mnt = webDavMount.get().orElse(null);
|
||||
if (mnt != null) {
|
||||
mnt.reveal();
|
||||
}
|
||||
public void reveal() {
|
||||
// TODO exception handling
|
||||
filesystemFrontend.get().ifPresent(Frontend::reveal);
|
||||
}
|
||||
|
||||
public void unmount() throws CommandFailedException {
|
||||
final WebDavMount mnt = webDavMount.get().orElse(null);
|
||||
if (mnt != null) {
|
||||
mnt.unmount();
|
||||
}
|
||||
webDavMount = DeferredClosable.empty();
|
||||
public void unmount() {
|
||||
// TODO exception handling
|
||||
filesystemFrontend.get().ifPresent(Frontend::unmount);
|
||||
}
|
||||
|
||||
/* Delegate Methods */
|
||||
|
||||
@Override
|
||||
public void authenticationFailed(String cleartextPath) {
|
||||
namesOfResourcesWithInvalidMac.add(cleartextPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldSkipAuthentication(String cleartextPath) {
|
||||
return namesOfResourcesWithInvalidMac.contains(cleartextPath);
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
@@ -164,9 +164,9 @@ public class Vault implements Serializable {
|
||||
return StringUtils.removeEnd(path.getFileName().toString(), VAULT_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
public Cryptor getCryptor() {
|
||||
return cryptor;
|
||||
}
|
||||
// public Cryptor getCryptor() {
|
||||
// return cryptor;
|
||||
// }
|
||||
|
||||
public ObjectProperty<Boolean> unlockedProperty() {
|
||||
return unlocked;
|
||||
|
||||
@@ -3,33 +3,31 @@ package org.cryptomator.ui.model;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.Cryptor;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounter;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
import org.cryptomator.ui.util.mount.WebDavMounter;
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
|
||||
import dagger.Lazy;
|
||||
|
||||
@Singleton
|
||||
public class VaultFactory {
|
||||
|
||||
private final WebDavServer server;
|
||||
private final Provider<Cryptor> cryptorProvider;
|
||||
private final WebDavMounter mounter;
|
||||
private final Lazy<FrontendFactory> frontendFactory;
|
||||
private final CryptoFileSystemFactory cryptoFileSystemFactory;
|
||||
private final DeferredCloser closer;
|
||||
|
||||
@Inject
|
||||
public VaultFactory(WebDavServer server, @Named("SamplingCryptor") Provider<Cryptor> cryptorProvider, WebDavMounter mounter, DeferredCloser closer) {
|
||||
this.server = server;
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
this.mounter = mounter;
|
||||
public VaultFactory(Lazy<FrontendFactory> frontendFactory, CryptoFileSystemFactory cryptoFileSystemFactory, WebDavMounter mounter, DeferredCloser closer) {
|
||||
this.frontendFactory = frontendFactory;
|
||||
this.cryptoFileSystemFactory = cryptoFileSystemFactory;
|
||||
this.closer = closer;
|
||||
}
|
||||
|
||||
public Vault createVault(Path path) {
|
||||
return new Vault(path, server, cryptorProvider.get(), mounter, closer);
|
||||
return new Vault(path, frontendFactory, cryptoFileSystemFactory, closer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch
|
||||
* Sebastian Stenzel - using Futures, lazy loading for out/err.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.command;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public final class CommandResult {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class);
|
||||
|
||||
private final Process process;
|
||||
private final String stdout;
|
||||
private final String stderr;
|
||||
private final CommandFailedException exception;
|
||||
|
||||
/**
|
||||
* Constructs a CommandResult from a terminated process and closes all its streams.
|
||||
* @param process An <strong>already finished</strong> process.
|
||||
*/
|
||||
CommandResult(Process process) {
|
||||
String out = null;
|
||||
String err = null;
|
||||
CommandFailedException ex = null;
|
||||
try {
|
||||
out = IOUtils.toString(process.getInputStream());
|
||||
err = IOUtils.toString(process.getErrorStream());
|
||||
} catch (IOException e) {
|
||||
ex = new CommandFailedException(e);
|
||||
} finally {
|
||||
this.process = process;
|
||||
this.stdout = out;
|
||||
this.stderr = err;
|
||||
this.exception = ex;
|
||||
IOUtils.closeQuietly(process.getInputStream());
|
||||
IOUtils.closeQuietly(process.getOutputStream());
|
||||
IOUtils.closeQuietly(process.getErrorStream());
|
||||
logDebugInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data written to STDOUT
|
||||
*/
|
||||
public String getStdOut() throws CommandFailedException {
|
||||
assertNoException();
|
||||
return stdout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Data written to STDERR
|
||||
*/
|
||||
public String getStdErr() throws CommandFailedException {
|
||||
assertNoException();
|
||||
return stderr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Exit value of the process
|
||||
*/
|
||||
public int getExitValue() {
|
||||
return process.exitValue();
|
||||
}
|
||||
|
||||
private void logDebugInfo() {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
if (Strings.isEmpty(stderr) && Strings.isEmpty(stdout)) {
|
||||
LOG.debug("Command execution finished. Exit code: {}", process.exitValue());
|
||||
} else if (Strings.isEmpty(stderr)) {
|
||||
LOG.debug("Command execution finished. Exit code: {}\nOutput: {}", process.exitValue(), stdout);
|
||||
} else if (Strings.isEmpty(stdout)) {
|
||||
LOG.debug("Command execution finished. Exit code: {}\nError: {}", process.exitValue(), stderr);
|
||||
} else {
|
||||
LOG.debug("Command execution finished. Exit code: {}\n Output: {}\nError: {}", process.exitValue(), stdout, stderr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void assertOk() throws CommandFailedException {
|
||||
assertNoException();
|
||||
int exitValue = getExitValue();
|
||||
if (exitValue != 0) {
|
||||
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, stdout, stderr));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertNoException() throws CommandFailedException {
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch
|
||||
* Sebastian Stenzel - Refactoring
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.command;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
|
||||
import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Runs commands using a system compatible CLI.
|
||||
* <p>
|
||||
* To detect the system type {@link SystemUtils} is used. The following CLIs are used by default:
|
||||
* <ul>
|
||||
* <li><i>{@link #WINDOWS_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_WINDOWS}
|
||||
* <li><i>{@link #UNIX_DEFAULT_CLI}</i> if {@link SystemUtils#IS_OS_UNIX}
|
||||
* </ul>
|
||||
* <p>
|
||||
* If the path to the executables differs from the default or the system can not be detected the Java system property
|
||||
* {@value #CLI_EXECUTABLE_PROPERTY} can be set to define it.
|
||||
* <p>
|
||||
* If a CLI executable can not be determined using these methods operation of {@link CommandRunner} will fail with
|
||||
* {@link IllegalStateException}s.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
final class CommandRunner {
|
||||
|
||||
public static final String CLI_EXECUTABLE_PROPERTY = "cryptomator.cli";
|
||||
public static final String WINDOWS_DEFAULT_CLI[] = {"cmd", "/C"};
|
||||
public static final String UNIX_DEFAULT_CLI[] = {"/bin/sh", "-c"};
|
||||
private static final Executor CMD_EXECUTOR = Executors.newCachedThreadPool();
|
||||
|
||||
/**
|
||||
* Executes all lines in the given script in the specified order. Stops as soon as the first command fails.
|
||||
*
|
||||
* @param script Script containing command lines and environment variables.
|
||||
* @return Result of the last command, if it exited successfully.
|
||||
* @throws CommandFailedException If one of the command lines in the given script fails.
|
||||
*/
|
||||
static CommandResult execute(Script script, long timeout, TimeUnit unit) throws CommandFailedException {
|
||||
try {
|
||||
final List<String> env = script.environment().entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).collect(Collectors.toList());
|
||||
CommandResult result = null;
|
||||
for (final String line : script.getLines()) {
|
||||
final String[] cmds = ArrayUtils.add(determineCli(), line);
|
||||
final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0]));
|
||||
result = run(proc, timeout, unit);
|
||||
result.assertOk();
|
||||
}
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new CommandFailedException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static CommandResult run(Process process, long timeout, TimeUnit unit) throws CommandFailedException {
|
||||
try {
|
||||
final FutureCommandResult futureCommandResult = new FutureCommandResult(process);
|
||||
CMD_EXECUTOR.execute(futureCommandResult);
|
||||
return futureCommandResult.get(timeout, unit);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
throw new CommandFailedException("Waiting time elapsed before command execution finished");
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] determineCli() {
|
||||
final String cliFromProperty = System.getProperty(CLI_EXECUTABLE_PROPERTY);
|
||||
if (cliFromProperty != null) {
|
||||
return cliFromProperty.split("");
|
||||
} else if (IS_OS_WINDOWS) {
|
||||
return WINDOWS_DEFAULT_CLI;
|
||||
} else if (IS_OS_UNIX) {
|
||||
return UNIX_DEFAULT_CLI;
|
||||
} else {
|
||||
throw new IllegalStateException(format("Failed to determine cli to use. Set Java system property %s to the executable.", CLI_EXECUTABLE_PROPERTY));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.command;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
|
||||
final class FutureCommandResult implements Future<CommandResult>, Runnable {
|
||||
|
||||
private final Process process;
|
||||
private final AtomicBoolean canceled = new AtomicBoolean();
|
||||
private final AtomicBoolean done = new AtomicBoolean();
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Condition doneCondition = lock.newCondition();
|
||||
|
||||
private CommandFailedException exception;
|
||||
|
||||
FutureCommandResult(Process process) {
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (done.get()) {
|
||||
return false;
|
||||
} else if (canceled.compareAndSet(false, true)) {
|
||||
if (mayInterruptIfRunning) {
|
||||
process.destroyForcibly();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return canceled.get();
|
||||
}
|
||||
|
||||
private void setDone() {
|
||||
lock.lock();
|
||||
try {
|
||||
done.set(true);
|
||||
doneCondition.signalAll();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return done.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult get() throws InterruptedException, ExecutionException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
doneCondition.await();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
if (exception != null) {
|
||||
throw new ExecutionException(exception);
|
||||
}
|
||||
return new CommandResult(process);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
lock.lock();
|
||||
try {
|
||||
while(!done.get()) {
|
||||
doneCondition.await(timeout, unit);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
if (exception != null) {
|
||||
throw new ExecutionException(exception);
|
||||
}
|
||||
return new CommandResult(process);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
process.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
exception = new CommandFailedException(e);
|
||||
} finally {
|
||||
setDone();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.command;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.cryptomator.ui.util.mount.CommandFailedException;
|
||||
|
||||
public final class Script {
|
||||
|
||||
private static final int DEFAULT_TIMEOUT_MILLISECONDS = 3000;
|
||||
|
||||
public static Script fromLines(String... commands) {
|
||||
return new Script(commands);
|
||||
}
|
||||
|
||||
private final String[] lines;
|
||||
private final Map<String, String> environment = new HashMap<>();
|
||||
|
||||
private Script(String[] lines) {
|
||||
this.lines = lines;
|
||||
setEnv(System.getenv());
|
||||
}
|
||||
|
||||
public String[] getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public CommandResult execute() throws CommandFailedException {
|
||||
return CommandRunner.execute(this, DEFAULT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public CommandResult execute(long timeout, TimeUnit unit) throws CommandFailedException {
|
||||
return CommandRunner.execute(this, timeout, unit);
|
||||
}
|
||||
|
||||
Map<String, String> environment() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
public Script setEnv(Map<String, String> environment) {
|
||||
this.environment.clear();
|
||||
addEnv(environment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Script addEnv(Map<String, String> environment) {
|
||||
this.environment.putAll(environment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Script addEnv(String name, String value) {
|
||||
environment.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
abstract class AbstractWebDavMount implements WebDavMount {
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
this.unmount();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
public class CommandFailedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 5784853630182321479L;
|
||||
|
||||
public CommandFailedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CommandFailedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A WebDavMounter acting as fallback if no other mounter works.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
final class FallbackWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmUp(int serverPort) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) {
|
||||
displayMountInstructions();
|
||||
return new AbstractWebDavMount() {
|
||||
@Override
|
||||
public void unmount() {
|
||||
displayUnmountInstructions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
displayRevealInstructions();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void displayMountInstructions() {
|
||||
// TODO display message to user pointing to cryptomator.org/mounting#mount which describes what to do
|
||||
// Machine-readable mount instructions: http://tools.ietf.org/html/rfc4709#page-5 :-)
|
||||
}
|
||||
|
||||
private void displayUnmountInstructions() {
|
||||
// TODO display message to user pointing to cryptomator.org/mounting#unmount which describes what to do
|
||||
}
|
||||
|
||||
private void displayRevealInstructions() {
|
||||
// TODO display message to user pointing to cryptomator.org/mounting#reveal which describes what to do
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
* Mohit Raju - Added fallback schema-name "webdav" when opening file managers
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.command.Script;
|
||||
|
||||
@Singleton
|
||||
final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Inject
|
||||
LinuxGvfsWebDavMounter() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
if (SystemUtils.IS_OS_LINUX) {
|
||||
final Script checkScripts = Script.fromLines("which gvfs-mount xdg-open");
|
||||
try {
|
||||
checkScripts.execute();
|
||||
return true;
|
||||
} catch (CommandFailedException e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmUp(int serverPort) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final Script mountScript = Script.fromLines(
|
||||
"set -x",
|
||||
"gvfs-mount \"dav:$DAV_SSP\"")
|
||||
.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
|
||||
mountScript.execute();
|
||||
return new LinuxGvfsWebDavMount(uri);
|
||||
}
|
||||
|
||||
private static class LinuxGvfsWebDavMount extends AbstractWebDavMount {
|
||||
private final URI webDavUri;
|
||||
private final Script testMountStillExistsScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private LinuxGvfsWebDavMount(URI webDavUri) {
|
||||
this.webDavUri = webDavUri;
|
||||
this.testMountStillExistsScript = Script.fromLines("set -x", "test `gvfs-mount --list | grep \"$DAV_SSP\" | wc -l` -eq 1").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
|
||||
this.unmountScript = Script.fromLines("set -x", "gvfs-mount -u \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
boolean mountStillExists;
|
||||
try {
|
||||
testMountStillExistsScript.execute();
|
||||
mountStillExists = true;
|
||||
} catch(CommandFailedException e) {
|
||||
mountStillExists = false;
|
||||
}
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (mountStillExists) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
try {
|
||||
openMountWithWebdavUri("dav:"+webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
} catch (CommandFailedException exception) {
|
||||
openMountWithWebdavUri("webdav:"+webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
}
|
||||
}
|
||||
|
||||
private Script openMountWithWebdavUri(String webdavUri){
|
||||
return Script.fromLines("set -x", "xdg-open \"$DAV_URI\"").addEnv("DAV_URI", webdavUri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation, strategy fine tuning
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.command.Script;
|
||||
|
||||
@Singleton
|
||||
final class MacOsXWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Inject
|
||||
MacOsXWebDavMounter() {}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
return SystemUtils.IS_OS_MAC_OSX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmUp(int serverPort) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final String mountName = mountParams.get(MountParam.MOUNT_NAME).orElseThrow(() -> {
|
||||
return new IllegalArgumentException("Missing mount parameter MOUNT_NAME.");
|
||||
});
|
||||
|
||||
// we don't use the uri to derive a path, as it *could* be longer than 255 chars.
|
||||
final String path = "/Volumes/Cryptomator_" + UUID.randomUUID().toString();
|
||||
final Script mountScript = Script.fromLines(
|
||||
"mkdir \"$MOUNT_PATH\"",
|
||||
"mount_webdav -S -v $MOUNT_NAME \"$DAV_AUTHORITY$DAV_PATH\" \"$MOUNT_PATH\"")
|
||||
.addEnv("DAV_AUTHORITY", uri.getRawAuthority())
|
||||
.addEnv("DAV_PATH", uri.getRawPath())
|
||||
.addEnv("MOUNT_PATH", path)
|
||||
.addEnv("MOUNT_NAME", mountName);
|
||||
mountScript.execute();
|
||||
return new MacWebDavMount(path);
|
||||
}
|
||||
|
||||
private static class MacWebDavMount extends AbstractWebDavMount {
|
||||
private final String mountPath;
|
||||
private final Script revealScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private MacWebDavMount(String mountPath) {
|
||||
this.mountPath = mountPath;
|
||||
this.revealScript = Script.fromLines("open \"$MOUNT_PATH\"").addEnv("MOUNT_PATH", mountPath);
|
||||
this.unmountScript = Script.fromLines("diskutil umount $MOUNT_PATH").addEnv("MOUNT_PATH", mountPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (Files.exists(FileSystems.getDefault().getPath(mountPath))) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
revealScript.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
class MountStrategies implements Collection<WebDavMounterStrategy> {
|
||||
|
||||
private final Collection<WebDavMounterStrategy> delegate;
|
||||
|
||||
@Inject
|
||||
MountStrategies(LinuxGvfsWebDavMounter linuxMounter, MacOsXWebDavMounter osxMounter, WindowsWebDavMounter winMounter) {
|
||||
delegate = unmodifiableList(asList(linuxMounter, osxMounter, winMounter));
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
public Iterator<WebDavMounterStrategy> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return delegate.toArray(a);
|
||||
}
|
||||
|
||||
public boolean add(WebDavMounterStrategy e) {
|
||||
return delegate.add(e);
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return delegate.containsAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends WebDavMounterStrategy> c) {
|
||||
return delegate.addAll(c);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return delegate.removeAll(c);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return delegate.retainAll(c);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
return delegate.equals(o);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
/**
|
||||
* A mounted webdav share.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
public interface WebDavMount extends AutoCloseable {
|
||||
|
||||
/**
|
||||
* Unmounts this {@code WebDavMount}.
|
||||
*
|
||||
* @throws CommandFailedException if the unmount operation fails
|
||||
*/
|
||||
void unmount() throws CommandFailedException;
|
||||
|
||||
/**
|
||||
* Reveals the mounted drive in the operating systems default file browser.
|
||||
*
|
||||
* @throws CommandFailedException if the reveal operation fails
|
||||
*/
|
||||
void reveal() throws CommandFailedException;
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
* Markus Kreusch - Refactored to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface WebDavMounter {
|
||||
|
||||
public static enum MountParam {MOUNT_NAME, WIN_DRIVE_LETTER}
|
||||
|
||||
/**
|
||||
* Tries to mount a given webdav share.
|
||||
*
|
||||
* @param uri URI of the webdav share
|
||||
* @param mountParams additional mount parameters, that might not get ignored by some OS-specific mounters.
|
||||
* @return a {@link WebDavMount} representing the mounted share
|
||||
* @throws CommandFailedException if the mount operation fails
|
||||
* @throws IllegalArgumentException if mountParams is missing expected options
|
||||
*/
|
||||
WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException;
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch - Refactored to use strategy pattern
|
||||
* Sebastian Stenzel - Refactored to use Guice provider, added warmup-phase for windows mounts.
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.webdav.WebDavServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class WebDavMounterProvider implements Provider<WebDavMounter> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WebDavMounterProvider.class);
|
||||
private final WebDavMounterStrategy choosenStrategy;
|
||||
|
||||
@Inject
|
||||
public WebDavMounterProvider(WebDavServer server, ExecutorService executorService, MountStrategies availableStrategies) {
|
||||
this.choosenStrategy = getStrategyWhichShouldWork(availableStrategies);
|
||||
executorService.execute(() -> {
|
||||
this.choosenStrategy.warmUp(server.getPort());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMounterStrategy get() {
|
||||
return this.choosenStrategy;
|
||||
}
|
||||
|
||||
private WebDavMounterStrategy getStrategyWhichShouldWork(Collection<WebDavMounterStrategy> availableStrategies) {
|
||||
WebDavMounterStrategy strategy = availableStrategies.stream().filter(WebDavMounterStrategy::shouldWork).findFirst().orElse(new FallbackWebDavMounter());
|
||||
LOG.info("Using {}", strategy.getClass().getSimpleName());
|
||||
return strategy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
* Sebastian Stenzel - minor strategy fine tuning
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
/**
|
||||
* A strategy able to mount a webdav share and display it to the user.
|
||||
*
|
||||
* @author Markus Kreusch
|
||||
*/
|
||||
interface WebDavMounterStrategy extends WebDavMounter {
|
||||
|
||||
/**
|
||||
* @return {@code false} if this {@code WebDavMounterStrategy} can not work on the local machine, {@code true} if it could work
|
||||
*/
|
||||
boolean shouldWork();
|
||||
|
||||
/**
|
||||
* Invoked when mounting strategy gets chosen. On some operating systems (we don't want to tell names here) mounting might be faster,
|
||||
* when certain things are prepared before the actual mount attempt.
|
||||
*/
|
||||
void warmUp(int serverPort);
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static java.util.stream.IntStream.rangeClosed;
|
||||
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
|
||||
@Singleton
|
||||
public final class WindowsDriveLetters {
|
||||
|
||||
private static final Set<Character> A_TO_Z = rangeClosed('A', 'Z').mapToObj(i -> (char) i).collect(toSet());
|
||||
|
||||
@Inject
|
||||
public WindowsDriveLetters() {
|
||||
}
|
||||
|
||||
public Set<Character> getOccupiedDriveLetters() {
|
||||
if (!SystemUtils.IS_OS_WINDOWS) {
|
||||
throw new UnsupportedOperationException("This method is only defined for Windows file systems");
|
||||
}
|
||||
Iterable<Path> rootDirs = FileSystems.getDefault().getRootDirectories();
|
||||
return StreamSupport.stream(rootDirs.spliterator(), false).map(Path::toString).map(CharUtils::toChar).map(Character::toUpperCase).collect(toSet());
|
||||
}
|
||||
|
||||
public Set<Character> getAvailableDriveLetters() {
|
||||
return Sets.difference(A_TO_Z, getOccupiedDriveLetters());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014 Sebastian Stenzel, Markus Kreusch
|
||||
* This file is licensed under the terms of the MIT license.
|
||||
* See the LICENSE.txt file for more info.
|
||||
*
|
||||
* Contributors:
|
||||
* Sebastian Stenzel - initial API and implementation, strategy fine tuning
|
||||
* Markus Kreusch - Refactored WebDavMounter to use strategy pattern
|
||||
******************************************************************************/
|
||||
package org.cryptomator.ui.util.mount;
|
||||
|
||||
import static org.cryptomator.ui.util.command.Script.fromLines;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.util.command.CommandResult;
|
||||
import org.cryptomator.ui.util.command.Script;
|
||||
|
||||
/**
|
||||
* A {@link WebDavMounterStrategy} utilizing the "net use" command.
|
||||
* <p>
|
||||
* Tested on Windows 7 but should also work on Windows 8.
|
||||
*/
|
||||
@Singleton
|
||||
final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
private static final Pattern WIN_MOUNT_DRIVELETTER_PATTERN = Pattern.compile("\\s*([A-Z]):\\s*");
|
||||
private static final int MAX_MOUNT_ATTEMPTS = 8;
|
||||
private static final char AUTO_ASSIGN_DRIVE_LETTER = '*';
|
||||
private final WindowsDriveLetters driveLetters;
|
||||
|
||||
@Inject
|
||||
WindowsWebDavMounter(WindowsDriveLetters driveLetters) {
|
||||
this.driveLetters = driveLetters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldWork() {
|
||||
return SystemUtils.IS_OS_WINDOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmUp(int serverPort) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebDavMount mount(URI uri, Map<MountParam, Optional<String>> mountParams) throws CommandFailedException {
|
||||
final Character driveLetter = mountParams.get(MountParam.WIN_DRIVE_LETTER).map(CharUtils::toCharacterObject).orElse(AUTO_ASSIGN_DRIVE_LETTER);
|
||||
if (driveLetters.getOccupiedDriveLetters().contains(driveLetter)) {
|
||||
throw new CommandFailedException("Drive letter occupied.");
|
||||
}
|
||||
|
||||
final String driveLetterStr = driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? CharUtils.toString(AUTO_ASSIGN_DRIVE_LETTER) : driveLetter + ":";
|
||||
final Script localhostMountScript = fromLines("net use %DRIVE_LETTER% \\\\localhost@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
localhostMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
localhostMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
localhostMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
CommandResult mountResult;
|
||||
try {
|
||||
mountResult = localhostMountScript.execute(5, TimeUnit.SECONDS);
|
||||
} catch (CommandFailedException ex) {
|
||||
final Script ipv6literaltMountScript = fromLines("net use %DRIVE_LETTER% \\\\0--1.ipv6-literal.net@%DAV_PORT%\\DavWWWRoot%DAV_UNC_PATH% /persistent:no");
|
||||
ipv6literaltMountScript.addEnv("DRIVE_LETTER", driveLetterStr);
|
||||
ipv6literaltMountScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
ipv6literaltMountScript.addEnv("DAV_UNC_PATH", uri.getRawPath().replace('/', '\\'));
|
||||
final Script proxyBypassScript = fromLines("reg add \"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\" /v \"ProxyOverride\" /d \"<local>;0--1.ipv6-literal.net;0--1.ipv6-literal.net:%DAV_PORT%\" /f");
|
||||
proxyBypassScript.addEnv("DAV_PORT", String.valueOf(uri.getPort()));
|
||||
mountResult = bypassProxyAndRetryMount(localhostMountScript, ipv6literaltMountScript, proxyBypassScript);
|
||||
}
|
||||
return new WindowsWebDavMount(driveLetter.charValue() == AUTO_ASSIGN_DRIVE_LETTER ? getDriveLetter(mountResult.getStdOut()) : driveLetter);
|
||||
}
|
||||
|
||||
private CommandResult bypassProxyAndRetryMount(Script localhostMountScript, Script ipv6literalMountScript, Script proxyBypassScript) throws CommandFailedException {
|
||||
CommandFailedException latestException = null;
|
||||
for (int i = 0; i < MAX_MOUNT_ATTEMPTS; i++) {
|
||||
try {
|
||||
// wait a moment before next attempt
|
||||
Thread.sleep(5000);
|
||||
proxyBypassScript.execute();
|
||||
// alternate localhost and 0--1.ipv6literal.net
|
||||
final Script mountScript = (i % 2 == 0) ? localhostMountScript : ipv6literalMountScript;
|
||||
return mountScript.execute(3, TimeUnit.SECONDS);
|
||||
} catch (CommandFailedException ex) {
|
||||
latestException = ex;
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CommandFailedException(ex);
|
||||
}
|
||||
}
|
||||
throw latestException;
|
||||
}
|
||||
|
||||
private Character getDriveLetter(String result) throws CommandFailedException {
|
||||
final Matcher matcher = WIN_MOUNT_DRIVELETTER_PATTERN.matcher(result);
|
||||
if (matcher.find()) {
|
||||
return CharUtils.toCharacterObject(matcher.group(1));
|
||||
} else {
|
||||
throw new CommandFailedException("Failed to get a drive letter from net use output.");
|
||||
}
|
||||
}
|
||||
|
||||
private class WindowsWebDavMount extends AbstractWebDavMount {
|
||||
private final Character driveLetter;
|
||||
private final Script openExplorerScript;
|
||||
private final Script unmountScript;
|
||||
|
||||
private WindowsWebDavMount(Character driveLetter) {
|
||||
this.driveLetter = driveLetter;
|
||||
this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":");
|
||||
this.unmountScript = fromLines("net use " + driveLetter + ": /delete").addEnv("DRIVE_LETTER", Character.toString(driveLetter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
// only attempt unmount if user didn't unmount manually:
|
||||
if (isVolumeMounted()) {
|
||||
unmountScript.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
openExplorerScript.execute();
|
||||
}
|
||||
|
||||
private boolean isVolumeMounted() {
|
||||
return driveLetters.getOccupiedDriveLetters().contains(driveLetter);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user