mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-18 02:31:27 +00:00
Merge branch 'release/1.1.4' into develop
This commit is contained in:
@@ -9,7 +9,7 @@ Priority: optional
|
||||
Architecture: APPLICATION_ARCH
|
||||
Provides: APPLICATION_PACKAGE
|
||||
Installed-Size: APPLICATION_INSTALLED_SIZE
|
||||
Depends: gvfs-bin, gvfs-backends, gvfs-fuse, xdg-utils
|
||||
Depends: gvfs-bin, gvfs-backends, gvfs-fuse
|
||||
Description: Multi-platform client-side encryption of your cloud files.
|
||||
Cryptomator provides free client-side AES encryption for your cloud files.
|
||||
Create encrypted vaults, which get mounted as virtual volumes. Whatever
|
||||
|
||||
@@ -85,11 +85,15 @@ final class ConflictResolver {
|
||||
} else {
|
||||
ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize);
|
||||
ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize);
|
||||
r1.read(beginOfFile1);
|
||||
r2.read(beginOfFile2);
|
||||
beginOfFile1.flip();
|
||||
beginOfFile2.flip();
|
||||
return beginOfFile1.equals(beginOfFile2);
|
||||
int bytesRead1 = r1.read(beginOfFile1);
|
||||
int bytesRead2 = r2.read(beginOfFile2);
|
||||
if (bytesRead1 == bytesRead2) {
|
||||
beginOfFile1.flip();
|
||||
beginOfFile2.flip();
|
||||
return beginOfFile1.equals(beginOfFile2);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,10 @@ public interface Frontend extends AutoCloseable {
|
||||
|
||||
void mount(Map<MountParam, Optional<String>> map) throws CommandFailedException;
|
||||
|
||||
void unmount() throws CommandFailedException;
|
||||
/**
|
||||
* Unmounts the file system and stops any file system handler threads.
|
||||
*/
|
||||
void close() throws Exception;
|
||||
|
||||
void reveal() throws CommandFailedException;
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ public interface FrontendFactory {
|
||||
* Provides a new frontend to access the given folder.
|
||||
*
|
||||
* @param root Root resource accessible through this frontend.
|
||||
* @param uniqueName Name of the frontend, i.e. used to create subresources for the different frontends inside of a common virtual drive.
|
||||
* @param id unique id of the frontend, i.e. used to generate a unique uri
|
||||
* @param name Name of the frontend, i.e. used to generate a readable/recognizable name of a common virtual drive
|
||||
* @return A new frontend
|
||||
* @throws FrontendCreationFailedException If creation was not possible.
|
||||
*/
|
||||
Frontend create(Folder root, String uniqueName) throws FrontendCreationFailedException;
|
||||
Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.cryptomator.frontend;
|
||||
|
||||
import static java.util.UUID.randomUUID;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
|
||||
public class FrontendId implements Serializable {
|
||||
|
||||
public static final String FRONTEND_ID_PATTERN = "[a-zA-Z0-9_-]{12}";
|
||||
|
||||
public static FrontendId generate() {
|
||||
return new FrontendId();
|
||||
}
|
||||
|
||||
public static FrontendId from(String value) {
|
||||
return new FrontendId(value);
|
||||
}
|
||||
|
||||
private final String value;
|
||||
|
||||
private FrontendId() {
|
||||
this(generateId());
|
||||
}
|
||||
|
||||
private FrontendId(String value) {
|
||||
if (!value.matches(FRONTEND_ID_PATTERN)) {
|
||||
throw new IllegalArgumentException("Invalid frontend id " + value);
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private static String generateId() {
|
||||
return asBase64String(nineBytesFrom(randomUUID()));
|
||||
}
|
||||
|
||||
private static String asBase64String(ByteBuffer bytes) {
|
||||
ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes);
|
||||
return new String(asByteArray(base64Buffer), StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static ByteBuffer nineBytesFrom(UUID uuid) {
|
||||
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
|
||||
uuidBuffer.putLong(uuid.getMostSignificantBits());
|
||||
uuidBuffer.put((byte) (uuid.getLeastSignificantBits() & 0xFF));
|
||||
uuidBuffer.flip();
|
||||
return uuidBuffer;
|
||||
}
|
||||
|
||||
private static byte[] asByteArray(ByteBuffer buffer) {
|
||||
if (buffer.hasArray()) {
|
||||
return buffer.array();
|
||||
} else {
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || obj.getClass() != getClass()) {
|
||||
return false;
|
||||
}
|
||||
return obj == this || internalEquals((FrontendId) obj);
|
||||
}
|
||||
|
||||
private boolean internalEquals(FrontendId obj) {
|
||||
return value.equals(obj.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.cryptomator.frontend.webdav;
|
||||
|
||||
import static java.lang.String.format;
|
||||
import static org.cryptomator.frontend.FrontendId.FRONTEND_ID_PATTERN;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
|
||||
class ContextPaths {
|
||||
|
||||
private static final Pattern SERVLET_PATH_WITH_FRONTEND_ID_PATTERN = Pattern.compile("^/(" + FRONTEND_ID_PATTERN + ")(/.*)?$");
|
||||
private static final int FRONTEND_ID_GROUP = 1;
|
||||
|
||||
public static String from(FrontendId id, String name) {
|
||||
return format("/%s/%s", id, name);
|
||||
}
|
||||
|
||||
public static Optional<FrontendId> extractFrontendId(String path) {
|
||||
Matcher matcher = SERVLET_PATH_WITH_FRONTEND_ID_PATTERN.matcher(path);
|
||||
if (matcher.matches()) {
|
||||
return Optional.of(FrontendId.from(matcher.group(FRONTEND_ID_GROUP)));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,24 +8,51 @@ package org.cryptomator.frontend.webdav;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
class Tarpit {
|
||||
|
||||
class Tarpit implements Serializable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Tarpit.class);
|
||||
private static final long DELAY_MS = 10000;
|
||||
|
||||
|
||||
private final Set<FrontendId> validFrontendIds = new HashSet<>();
|
||||
|
||||
@Inject
|
||||
public Tarpit() {}
|
||||
|
||||
public Tarpit() {
|
||||
}
|
||||
|
||||
public void register(FrontendId frontendId) {
|
||||
validFrontendIds.add(frontendId);
|
||||
}
|
||||
|
||||
public void unregister(FrontendId frontendId) {
|
||||
validFrontendIds.remove(frontendId);
|
||||
}
|
||||
|
||||
public void handle(HttpServletRequest req) {
|
||||
if (isRequestWithVaultId(req)) {
|
||||
if (isRequestWithInvalidVaultId(req)) {
|
||||
delayExecutionUninterruptibly();
|
||||
LOG.debug("Delayed request to " + req.getRequestURI() + " by " + DELAY_MS + "ms");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRequestWithInvalidVaultId(HttpServletRequest req) {
|
||||
Optional<FrontendId> frontendId = ContextPaths.extractFrontendId(req.getServletPath());
|
||||
return frontendId.isPresent() && !isValid(frontendId.get());
|
||||
}
|
||||
|
||||
private void delayExecutionUninterruptibly() {
|
||||
long expected = currentTimeMillis() + DELAY_MS;
|
||||
long sleepTime = DELAY_MS;
|
||||
@@ -38,9 +65,8 @@ class Tarpit {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRequestWithVaultId(HttpServletRequest req) {
|
||||
String path = req.getServletPath();
|
||||
return path.matches("^/[a-zA-Z0-9_-]{12}/.*$");
|
||||
private boolean isValid(FrontendId frontendId) {
|
||||
return validFrontendIds.contains(frontendId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,12 +24,15 @@ class WebDavFrontend implements Frontend {
|
||||
private final WebDavMounterProvider webdavMounterProvider;
|
||||
private final ServletContextHandler handler;
|
||||
private final URI uri;
|
||||
private final Runnable afterClose;
|
||||
|
||||
private WebDavMount mount;
|
||||
|
||||
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException {
|
||||
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri, Runnable afterUnmount) throws FrontendCreationFailedException {
|
||||
this.webdavMounterProvider = webdavMounterProvider;
|
||||
this.handler = handler;
|
||||
this.uri = uri;
|
||||
this.afterClose = afterUnmount;
|
||||
try {
|
||||
handler.start();
|
||||
} catch (Exception e) {
|
||||
@@ -39,8 +42,12 @@ class WebDavFrontend implements Frontend {
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
unmount();
|
||||
handler.stop();
|
||||
try {
|
||||
unmount();
|
||||
handler.stop();
|
||||
} finally {
|
||||
afterClose.run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,10 +55,10 @@ class WebDavFrontend implements Frontend {
|
||||
mount = webdavMounterProvider.chooseMounter(mountParams).mount(uri, mountParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() throws CommandFailedException {
|
||||
private void unmount() throws CommandFailedException {
|
||||
if (mount != null) {
|
||||
mount.unmount();
|
||||
mount = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.frontend.webdav;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
@@ -20,6 +22,7 @@ import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.frontend.Frontend;
|
||||
import org.cryptomator.frontend.FrontendCreationFailedException;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.frontend.webdav.mount.WebDavMounterProvider;
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
@@ -45,9 +48,10 @@ public class WebDavServer implements FrontendFactory {
|
||||
private final ContextHandlerCollection servletCollection;
|
||||
private final WebDavServletContextFactory servletContextFactory;
|
||||
private final WebDavMounterProvider webdavMounterProvider;
|
||||
private final Tarpit tarpit;
|
||||
|
||||
@Inject
|
||||
WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet) {
|
||||
WebDavServer(WebDavServletContextFactory servletContextFactory, WebDavMounterProvider webdavMounterProvider, DefaultServlet defaultServlet, Tarpit tarpit) {
|
||||
final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
|
||||
final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
|
||||
this.server = new Server(tp);
|
||||
@@ -55,7 +59,8 @@ public class WebDavServer implements FrontendFactory {
|
||||
this.servletCollection = new ContextHandlerCollection();
|
||||
this.servletContextFactory = servletContextFactory;
|
||||
this.webdavMounterProvider = webdavMounterProvider;
|
||||
|
||||
this.tarpit = tarpit;
|
||||
|
||||
servletCollection.addHandler(defaultServlet.createServletContextHandler());
|
||||
server.setConnectors(new Connector[] {localConnector});
|
||||
server.setHandler(servletCollection);
|
||||
@@ -103,10 +108,8 @@ public class WebDavServer implements FrontendFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Frontend create(Folder root, String contextPath) throws FrontendCreationFailedException {
|
||||
if (!contextPath.startsWith("/")) {
|
||||
throw new IllegalArgumentException("contextPath must begin with '/'");
|
||||
}
|
||||
public Frontend create(Folder root, FrontendId id, String name) throws FrontendCreationFailedException {
|
||||
String contextPath = format("/%s/%s", id, name);
|
||||
final URI uri;
|
||||
try {
|
||||
uri = new URI("http", null, "localhost", getPort(), contextPath, null, null);
|
||||
@@ -114,8 +117,9 @@ public class WebDavServer implements FrontendFactory {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
final ServletContextHandler handler = addServlet(root, uri);
|
||||
tarpit.register(id);
|
||||
LOG.info("Servlet available under " + uri);
|
||||
return new WebDavFrontend(webdavMounterProvider, handler, uri);
|
||||
return new WebDavFrontend(webdavMounterProvider, handler, uri, () -> tarpit.unregister(id));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,7 +69,8 @@ class ExclusiveSharedLockManager implements LockManager {
|
||||
}
|
||||
|
||||
String token = DavConstants.OPAQUE_LOCK_TOKEN_PREFIX + UUID.randomUUID();
|
||||
return lockedResources.computeIfAbsent(locator, loc -> new HashMap<>()).computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo));
|
||||
Map<String, ActiveLock> lockMap = Objects.requireNonNull(lockedResources.computeIfAbsent(locator, loc -> new HashMap<>()));
|
||||
return lockMap.computeIfAbsent(token, t -> new ExclusiveSharedLock(t, lockInfo));
|
||||
}
|
||||
|
||||
private void removedExpiredLocksInLocatorHierarchy(FileSystemResourceLocator locator) {
|
||||
|
||||
@@ -86,7 +86,7 @@ final class LinuxGvfsDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
Script.fromLines("set -x", "gvfs-open \"dav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
|
||||
|
||||
@Override
|
||||
public void reveal() throws CommandFailedException {
|
||||
Script.fromLines("set -x", "xdg-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
Script.fromLines("set -x", "gvfs-open \"webdav:$DAV_SSP\"").addEnv("DAV_SSP", webDavUri.getRawSchemeSpecificPart()).execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
package org.cryptomator.frontend.webdav.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.IntStream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -24,16 +24,21 @@ 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());
|
||||
|
||||
|
||||
private static final Set<Character> A_TO_Z;
|
||||
|
||||
static {
|
||||
try (IntStream stream = IntStream.rangeClosed('A', 'Z')) {
|
||||
A_TO_Z = stream.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");
|
||||
@@ -41,7 +46,7 @@ public final class WindowsDriveLetters {
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
|
||||
private WindowsWebDavMount(String driveLetter) {
|
||||
this.driveLetter = CharUtils.toCharacterObject(driveLetter);
|
||||
this.openExplorerScript = fromLines("start explorer.exe " + driveLetter + ":");
|
||||
this.unmountScript = fromLines("net use " + driveLetter + ": /delete");
|
||||
this.unmountScript = fromLines("net use " + driveLetter + ": /delete /no");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -61,13 +61,18 @@ final class CommandRunner {
|
||||
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());
|
||||
final String[] lines = script.getLines();
|
||||
if (lines.length == 0) {
|
||||
throw new IllegalArgumentException("Invalid script");
|
||||
}
|
||||
CommandResult result = null;
|
||||
for (final String line : script.getLines()) {
|
||||
for (final String line : lines) {
|
||||
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();
|
||||
}
|
||||
assert result != null;
|
||||
return result;
|
||||
} catch (IOException e) {
|
||||
throw new CommandFailedException(e);
|
||||
|
||||
@@ -23,6 +23,8 @@ 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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
@@ -34,6 +36,7 @@ import javafx.stage.Stage;
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
|
||||
class CryptomatorModule {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CryptomatorModule.class);
|
||||
private final Application application;
|
||||
private final Stage mainWindow;
|
||||
|
||||
@@ -59,7 +62,13 @@ class CryptomatorModule {
|
||||
@Singleton
|
||||
DeferredCloser provideDeferredCloser() {
|
||||
DeferredCloser closer = new DeferredCloser();
|
||||
Cryptomator.addShutdownTask(closer::close);
|
||||
Cryptomator.addShutdownTask(() -> {
|
||||
try {
|
||||
closer.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error during shutdown.", e);
|
||||
}
|
||||
});
|
||||
return closer;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
import org.cryptomator.ui.controllers.MainController;
|
||||
@@ -126,7 +127,11 @@ public class MainApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
closer.close();
|
||||
try {
|
||||
closer.close();
|
||||
} catch (ExecutionException e) {
|
||||
LOG.error("Error closing ressources", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -117,12 +117,11 @@ public class UnlockedController extends LocalizedFXMLViewController {
|
||||
@FXML
|
||||
private void didClickLockVault(ActionEvent event) {
|
||||
asyncTaskService.asyncTaskOf(() -> {
|
||||
vault.get().unmount();
|
||||
vault.get().deactivateFrontend();
|
||||
}).onError(CommandFailedException.class, () -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
||||
}).andFinally(() -> {
|
||||
}).onSuccess(() -> {
|
||||
listener.ifPresent(listener -> listener.didLock(this));
|
||||
}).onError(Exception.class, () -> {
|
||||
messageLabel.setText(localization.getString("unlocked.label.unmountFailed"));
|
||||
}).run();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.cryptomator.frontend.Frontend;
|
||||
import org.cryptomator.frontend.Frontend.MountParam;
|
||||
import org.cryptomator.frontend.FrontendCreationFailedException;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.DeferredClosable;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
@@ -73,7 +74,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
|
||||
private final AtomicReference<FileSystem> nioFileSystem = new AtomicReference<>();
|
||||
private final String id;
|
||||
|
||||
|
||||
private String mountName;
|
||||
private Character winDriveLetter;
|
||||
private Optional<StatsFileSystem> statsFileSystem = Optional.empty();
|
||||
@@ -81,7 +82,8 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
|
||||
/**
|
||||
* Package private constructor, use {@link VaultFactory}.
|
||||
* @param string
|
||||
*
|
||||
* @param string
|
||||
*/
|
||||
Vault(String id, Path vaultDirectoryPath, ShorteningFileSystemFactory shorteningFileSystemFactory, CryptoFileSystemFactory cryptoFileSystemFactory, DeferredCloser closer) {
|
||||
this.path = new SimpleObjectProperty<Path>(vaultDirectoryPath);
|
||||
@@ -133,7 +135,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
FileSystem normalizingFs = new NormalizedNameFileSystem(cryptoFs, SystemUtils.IS_OS_MAC_OSX ? Form.NFD : Form.NFC);
|
||||
StatsFileSystem statsFs = new StatsFileSystem(normalizingFs);
|
||||
statsFileSystem = Optional.of(statsFs);
|
||||
Frontend frontend = frontendFactory.create(statsFs, contextPath());
|
||||
Frontend frontend = frontendFactory.create(statsFs, FrontendId.from(id), stripStart(mountName, "/"));
|
||||
filesystemFrontend = closer.closeLater(frontend);
|
||||
frontend.mount(getMountParams(settings));
|
||||
success = true;
|
||||
@@ -146,11 +148,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private String contextPath() {
|
||||
return String.format("/%s/%s", id, stripStart(mountName, "/"));
|
||||
}
|
||||
|
||||
public synchronized void deactivateFrontend() {
|
||||
public synchronized void deactivateFrontend() throws Exception {
|
||||
filesystemFrontend.close();
|
||||
statsFileSystem = Optional.empty();
|
||||
Platform.runLater(() -> unlocked.set(false));
|
||||
@@ -170,10 +168,6 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal);
|
||||
}
|
||||
|
||||
public void unmount() throws CommandFailedException {
|
||||
Optionals.ifPresent(filesystemFrontend.get(), Frontend::unmount);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
// Delegate methods
|
||||
// ********************************************************************************/
|
||||
@@ -312,7 +306,7 @@ public class Vault implements CryptoFileSystemDelegate {
|
||||
public void setWinDriveLetter(Character winDriveLetter) {
|
||||
this.winDriveLetter = winDriveLetter;
|
||||
}
|
||||
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -8,18 +8,14 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import static java.util.UUID.randomUUID;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
|
||||
@Singleton
|
||||
@@ -41,34 +37,7 @@ public class VaultFactory {
|
||||
}
|
||||
|
||||
public Vault createVault(Path path) {
|
||||
return createVault(generateId(), path);
|
||||
}
|
||||
|
||||
private String generateId() {
|
||||
return asBase64String(nineBytesFrom(randomUUID()));
|
||||
}
|
||||
|
||||
private String asBase64String(ByteBuffer bytes) {
|
||||
ByteBuffer base64Buffer = Base64.getUrlEncoder().encode(bytes);
|
||||
return new String(asByteArray(base64Buffer));
|
||||
}
|
||||
|
||||
private ByteBuffer nineBytesFrom(UUID uuid) {
|
||||
ByteBuffer uuidBuffer = ByteBuffer.allocate(9);
|
||||
uuidBuffer.putLong(uuid.getMostSignificantBits());
|
||||
uuidBuffer.put((byte)(uuid.getLeastSignificantBits() & 0xFF));
|
||||
uuidBuffer.flip();
|
||||
return uuidBuffer;
|
||||
}
|
||||
|
||||
private byte[] asByteArray(ByteBuffer buffer) {
|
||||
if (buffer.hasArray()) {
|
||||
return buffer.array();
|
||||
} else {
|
||||
byte[] bytes = new byte[buffer.remaining()];
|
||||
buffer.get(bytes);
|
||||
return bytes;
|
||||
}
|
||||
return createVault(FrontendId.generate().toString(), path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,12 +28,6 @@ public interface DeferredClosable<T> extends AutoCloseable {
|
||||
*/
|
||||
public Optional<T> get();
|
||||
|
||||
/**
|
||||
* Quietly closes the Object. If the object was closed before, nothing
|
||||
* happens.
|
||||
*/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* @return an empty object.
|
||||
*/
|
||||
|
||||
@@ -13,12 +13,10 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.cryptomator.common.ConsumerThrowingException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
@@ -31,7 +29,7 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
*
|
||||
* <p>
|
||||
* If you have a {@link DeferredCloser} instance present, call
|
||||
* {@link #closeLater(Object, Closer)} immediately after you have opened the
|
||||
* {@link #closeLater(Object, ConsumerThrowingException)} immediately after you have opened the
|
||||
* resource and return a resource handle. If {@link #close()} is called, the
|
||||
* resource will be closed. Calling {@link DeferredClosable#close()} on the resource
|
||||
* handle will also close the resource and prevent a second closing by
|
||||
@@ -42,8 +40,6 @@ import com.google.common.annotations.VisibleForTesting;
|
||||
*/
|
||||
public class DeferredCloser implements AutoCloseable {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeferredCloser.class);
|
||||
|
||||
@VisibleForTesting
|
||||
final Map<Long, ManagedResource<?>> cleanups = new ConcurrentSkipListMap<>();
|
||||
|
||||
@@ -51,33 +47,32 @@ public class DeferredCloser implements AutoCloseable {
|
||||
final AtomicLong counter = new AtomicLong();
|
||||
|
||||
private class ManagedResource<T> implements DeferredClosable<T> {
|
||||
|
||||
private final long number = counter.incrementAndGet();
|
||||
|
||||
private final AtomicReference<T> object = new AtomicReference<>();
|
||||
private final T object;
|
||||
private final ConsumerThrowingException<T, Exception> closer;
|
||||
private boolean closed = false;
|
||||
|
||||
public ManagedResource(T object, ConsumerThrowingException<T, Exception> closer) {
|
||||
super();
|
||||
this.object.set(object);
|
||||
this.closer = closer;
|
||||
this.object = Objects.requireNonNull(object);
|
||||
this.closer = Objects.requireNonNull(closer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
final T oldObject = object.getAndSet(null);
|
||||
if (oldObject != null) {
|
||||
cleanups.remove(number);
|
||||
try {
|
||||
closer.accept(oldObject);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Closing resource failed.", e);
|
||||
}
|
||||
}
|
||||
public synchronized void close() throws Exception {
|
||||
closer.accept(object);
|
||||
cleanups.remove(number);
|
||||
closed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<T> get() throws IllegalStateException {
|
||||
return Optional.ofNullable(object.get());
|
||||
if (closed) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,11 +80,23 @@ public class DeferredCloser implements AutoCloseable {
|
||||
* Closes all added objects which have not been closed before and releases references.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
public void close() throws ExecutionException {
|
||||
ExecutionException exception = null;
|
||||
for (Iterator<ManagedResource<?>> iterator = cleanups.values().iterator(); iterator.hasNext();) {
|
||||
final ManagedResource<?> closableProvider = iterator.next();
|
||||
closableProvider.close();
|
||||
iterator.remove();
|
||||
try {
|
||||
closableProvider.close();
|
||||
iterator.remove();
|
||||
} catch (Exception e) {
|
||||
if (exception == null) {
|
||||
exception = new ExecutionException(e);
|
||||
} else {
|
||||
exception.addSuppressed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (exception != null) {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user