瀏覽代碼

several refactorings, especially concerning LOCK operations on windows

Sebastian Stenzel 9 年之前
父節點
當前提交
1a81b3a781

+ 7 - 2
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java

@@ -19,6 +19,7 @@ import java.util.Arrays;
 import java.util.List;
 
 import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavResource;
 import org.apache.jackrabbit.webdav.DavResourceLocator;
@@ -196,7 +197,7 @@ abstract class AbstractEncryptedNode implements DavResource {
 			return null;
 		}
 
-		final String parentResource = FilenameUtils.getPathNoEndSeparator(locator.getResourcePath());
+		final String parentResource = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(locator.getResourcePath()), "/");
 		final DavResourceLocator parentLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), parentResource);
 		try {
 			return getFactory().createResource(parentLocator, session);
@@ -255,7 +256,11 @@ abstract class AbstractEncryptedNode implements DavResource {
 	@Override
 	public ActiveLock[] getLocks() {
 		final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
-		return new ActiveLock[] {exclusiveWriteLock};
+		if (exclusiveWriteLock != null) {
+			return new ActiveLock[] {exclusiveWriteLock};
+		} else {
+			return new ActiveLock[0];
+		}
 	}
 
 	@Override

+ 14 - 12
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java

@@ -12,27 +12,30 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
 	private final String pathPrefix;
 
 	public CleartextLocatorFactory(String pathPrefix) {
-		this.pathPrefix = pathPrefix;
+		this.pathPrefix = StringUtils.removeEnd(pathPrefix, "/");
 	}
 
 	// resourcePath == repositoryPath. No encryption here.
 
 	@Override
 	public DavResourceLocator createResourceLocator(String prefix, String href) {
-		final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/";
+		final String fullPrefix = StringUtils.removeEnd(prefix, "/");
 		final String relativeHref = StringUtils.removeStart(href, fullPrefix);
 
-		final String relativeCleartextPath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/"));
+		final String relativeCleartextPath = EncodeUtil.unescape(relativeHref);
+		assert relativeCleartextPath.startsWith("/");
 		return new CleartextLocator(relativeCleartextPath);
 	}
 
 	@Override
 	public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
+		assert resourcePath.startsWith("/");
 		return new CleartextLocator(resourcePath);
 	}
 
 	@Override
 	public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
+		assert path.startsWith("/");
 		return new CleartextLocator(path);
 	}
 
@@ -41,7 +44,7 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
 		private final String relativeCleartextPath;
 
 		private CleartextLocator(String relativeCleartextPath) {
-			this.relativeCleartextPath = FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true);
+			this.relativeCleartextPath = StringUtils.prependIfMissing(FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true), "/");
 		}
 
 		@Override
@@ -76,20 +79,19 @@ public class CleartextLocatorFactory implements DavLocatorFactory {
 
 		@Override
 		public String getHref(boolean isCollection) {
-			final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath());
-			final String fullPrefix = pathPrefix.endsWith("/") ? pathPrefix : pathPrefix + "/";
-			final String href = fullPrefix.concat(encodedResourcePath);
-			assert href.equals(fullPrefix) || !href.endsWith("/");
-			if (isCollection) {
-				return href.concat("/");
+			final String encodedResourcePath = EncodeUtil.escapePath(relativeCleartextPath);
+			if (isRootLocation()) {
+				return pathPrefix + "/";
+			} else if (isCollection) {
+				return pathPrefix + encodedResourcePath + "/";
 			} else {
-				return href;
+				return pathPrefix + encodedResourcePath;
 			}
 		}
 
 		@Override
 		public boolean isRootLocation() {
-			return Strings.isEmpty(relativeCleartextPath);
+			return "/".equals(relativeCleartextPath);
 		}
 
 		@Override

+ 10 - 7
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java

@@ -181,10 +181,11 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
 
 	/**
 	 * @return Absolute file path for a given cleartext file resourcePath.
-	 * @throws NonExistingParentException If one ancestor of the enrypted path is missing
+	 * @throws NonExistingParentException If one ancestor of the encrypted path is missing
 	 */
 	Path getEncryptedFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
+		assert relativeCleartextPath.startsWith("/");
+		final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
 		final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
 		final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
 		try {
@@ -197,10 +198,11 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
 
 	/**
 	 * @return Absolute file path for a given cleartext file resourcePath.
-	 * @throws NonExistingParentException If one ancestor of the enrypted path is missing
+	 * @throws NonExistingParentException If one ancestor of the encrypted path is missing
 	 */
 	Path getEncryptedDirectoryFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
+		assert relativeCleartextPath.startsWith("/");
+		final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
 		final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
 		final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
 		try {
@@ -217,15 +219,16 @@ public class CryptoResourceFactory implements DavResourceFactory, FileConstants
 	 * @throws NonExistingParentException if one ancestor directory is missing.
 	 */
 	private Path getEncryptedDirectoryPath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		assert Strings.isEmpty(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
+		assert relativeCleartextPath.startsWith("/");
+		assert "/".equals(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
 		try {
 			final Path result;
-			if (Strings.isEmpty(relativeCleartextPath)) {
+			if ("/".equals(relativeCleartextPath)) {
 				// root level
 				final String fixedRootDirectory = cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
 				result = dataRoot.resolve(fixedRootDirectory);
 			} else {
-				final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath);
+				final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
 				final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
 				final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
 				final String encryptedFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);

+ 56 - 17
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java

@@ -15,10 +15,14 @@ import java.nio.channels.FileChannel;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.AtomicMoveNotSupportedException;
 import java.nio.file.DirectoryStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
 import java.nio.file.StandardCopyOption;
 import java.nio.file.StandardOpenOption;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.time.Instant;
 import java.util.ArrayList;
@@ -38,12 +42,12 @@ import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.DavSession;
 import org.apache.jackrabbit.webdav.io.InputContext;
 import org.apache.jackrabbit.webdav.io.OutputContext;
+import org.apache.jackrabbit.webdav.lock.ActiveLock;
 import org.apache.jackrabbit.webdav.lock.LockManager;
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
 import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
 import org.apache.jackrabbit.webdav.property.ResourceType;
 import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.CounterOverflowException;
 import org.cryptomator.crypto.exceptions.DecryptFailedException;
 import org.cryptomator.crypto.exceptions.EncryptFailedException;
 import org.cryptomator.webdav.exceptions.DavRuntimeException;
@@ -92,6 +96,11 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 		}
 		return directoryPath;
 	}
+	
+	@Override
+	public boolean exists() {
+		return Files.exists(filePath) && Files.exists(getDirectoryPath());
+	}
 
 	@Override
 	public boolean isCollection() {
@@ -159,14 +168,10 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 			final Path filePath = dirPath.resolve(ciphertextFilename);
 			final Path tmpFilePath = Files.createTempFile(dirPath, null, null);
 			// encrypt to tmp file:
-			try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
-					final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, false)) {
-				cryptor.encryptFile(inputContext.getInputStream(), c);
+			try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.DSYNC)) {
+				long asd = cryptor.encryptFile(inputContext.getInputStream(), c);
 			} catch (SecurityException e) {
 				throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
-			} catch (CounterOverflowException e) {
-				// lets indicate this to the client as a "file too big" error
-				throw new DavException(DavServletResponse.SC_INSUFFICIENT_SPACE_ON_RESOURCE, e);
 			} catch (EncryptFailedException e) {
 				LOG.error("Encryption failed for unknown reasons.", e);
 				throw new IllegalStateException("Encryption failed for unknown reasons.", e);
@@ -188,18 +193,17 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 
 	@Override
 	public DavResourceIterator getMembers() {
-		try {
-			final Path dirPath = getDirectoryPath();
-			if (dirPath == null) {
-				throw new DavException(DavServletResponse.SC_NOT_FOUND);
-			}
-			final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER);
+		final Path dirPath = getDirectoryPath();
+		if (dirPath == null) {
+			throw new DavRuntimeException(new DavException(DavServletResponse.SC_NOT_FOUND));
+		}
+		
+		try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER)) {	
 			final List<DavResource> result = new ArrayList<>();
-
 			for (final Path childPath : directoryStream) {
 				try {
 					final String cleartextFilename = filenameTranslator.getCleartextFilename(childPath.getFileName().toString());
-					final String cleartextFilepath = FilenameUtils.concat(getResourcePath(), cleartextFilename);
+					final String cleartextFilepath = locator.isRootLocation() ? '/' + cleartextFilename : locator.getResourcePath() + '/' + cleartextFilename;
 					final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), cleartextFilepath);
 					final DavResource resource;
 					if (StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), DIR_EXT)) {
@@ -208,7 +212,9 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 						assert StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), FILE_EXT);
 						resource = factory.createChildFileResource(childLocator, session, childPath);
 					}
-					result.add(resource);
+					if (resource.exists()) {
+						result.add(resource);
+					}
 				} catch (DecryptFailedException e) {
 					LOG.warn("Decryption of resource failed: " + childPath);
 					continue;
@@ -238,6 +244,11 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 		if (dirPath == null) {
 			throw new DavException(DavServletResponse.SC_NOT_FOUND);
 		}
+		// https://tools.ietf.org/html/rfc4918#section-9.6
+		// we must unlock anything we want to delete
+		for (ActiveLock lock : member.getLocks()) {
+			member.unlock(lock.getToken());
+		}
 		try {
 			final String cleartextFilename = FilenameUtils.getName(member.getResourcePath());
 			final String ciphertextFilename;
@@ -250,7 +261,7 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 				}
 				final Path subDirPath = subDir.getDirectoryPath();
 				if (subDirPath != null) {
-					Files.deleteIfExists(subDirPath);
+					Files.walkFileTree(subDirPath, new DeletingFileVisitor());
 				}
 				ciphertextFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
 			} else {
@@ -340,5 +351,33 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 	public void spool(OutputContext outputContext) throws IOException {
 		// do nothing
 	}
+	
+	/**
+	 * Deletes all files and folders, it visits.
+	 */
+	private static class DeletingFileVisitor extends SimpleFileVisitor<Path> {
+
+		@Override
+		public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
+			if (attributes.isRegularFile()) {
+				Files.delete(file);
+			}
+			return FileVisitResult.CONTINUE;
+		}
+
+		@Override
+		public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+			Files.delete(dir);
+			return FileVisitResult.CONTINUE;
+		}
+
+		@Override
+		public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
+			LOG.error("Failed to delete file " + file.toString(), exc);
+			return FileVisitResult.TERMINATE;
+		}
+
+	}
+
 
 }

+ 0 - 10
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CounterOverflowException.java

@@ -1,10 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class CounterOverflowException extends EncryptFailedException {
-	private static final long serialVersionUID = 380066751064534731L;
-
-	public CounterOverflowException(String msg) {
-		super(msg);
-	}
-
-}

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java

@@ -185,7 +185,8 @@ public class MainController extends AbstractFXMLViewController implements Initia
 	 * @param path non-null, writable, existing directory
 	 */
 	public void addVault(final Path path, boolean select) {
-		if (path == null || !Files.isWritable(path)) {
+		// TODO: Files.isWritable is broken on windows. Fix in Java 8u72, see https://bugs.openjdk.java.net/browse/JDK-8034057
+		if (path == null) {
 			return;
 		}