Ver código fonte

webdav move/copy/delete

Sebastian Stenzel 9 anos atrás
pai
commit
0254569826

+ 4 - 0
main/filesystem-api/src/main/java/org/cryptomator/filesystem/File.java

@@ -71,4 +71,8 @@ public interface File extends Node, Comparable<File> {
 		Copier.copy(this, destination);
 	}
 
+	default void moveTo(File destination) {
+		Mover.move(this, destination);
+	}
+
 }

+ 11 - 0
main/filesystem-api/src/main/java/org/cryptomator/filesystem/Mover.java

@@ -0,0 +1,11 @@
+package org.cryptomator.filesystem;
+
+class Mover {
+
+	public static void move(File source, File destination) {
+		try (OpenFiles openFiles = DeadlockSafeFileOpener.withWritable(source).andWritable(destination).open()) {
+			openFiles.writable(source).moveTo(openFiles.writable(destination));
+		}
+	}
+
+}

+ 2 - 3
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java

@@ -140,9 +140,8 @@ class CryptoFolder extends CryptoNode implements Folder {
 
 		target.physicalFile().parent().get().create(FolderCreateMode.INCLUDING_PARENTS);
 		assert target.physicalFile().parent().get().exists();
-		try (WritableFile src = this.physicalFile().openWritable(); WritableFile dst = target.physicalFile().openWritable()) {
-			src.moveTo(dst);
-		}
+		this.physicalFile().moveTo(target.physicalFile());
+
 		// directoryId is now used by target, we must no longer use the same id
 		// (we'll generate a new one when needed)
 		directoryId.set(null);

+ 6 - 1
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/fs/CryptoReadableFile.java

@@ -71,7 +71,12 @@ class CryptoReadableFile implements ReadableFile {
 
 	@Override
 	public void copyTo(WritableFile other) {
-		file.copyTo(other);
+		if (other instanceof CryptoWritableFile) {
+			CryptoWritableFile dst = (CryptoWritableFile) other;
+			file.copyTo(dst.file);
+		} else {
+			throw new IllegalArgumentException("Can not move CryptoFile to conventional File.");
+		}
 	}
 
 	@Override

+ 8 - 3
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/fs/CryptoWritableFile.java

@@ -17,14 +17,14 @@ import org.cryptomator.io.ByteBuffers;
 
 class CryptoWritableFile implements WritableFile {
 
+	final WritableFile file;
 	private final ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
 	private final FileContentEncryptor encryptor;
-	private final WritableFile file;
 	private final Future<Void> writeTask;
 
 	public CryptoWritableFile(FileContentCryptor cryptor, WritableFile file) {
-		this.encryptor = cryptor.createFileContentEncryptor(Optional.empty());
 		this.file = file;
+		this.encryptor = cryptor.createFileContentEncryptor(Optional.empty());
 		writeHeader();
 		this.writeTask = executorService.submit(new Writer());
 	}
@@ -55,7 +55,12 @@ class CryptoWritableFile implements WritableFile {
 
 	@Override
 	public void moveTo(WritableFile other) {
-		file.moveTo(other);
+		if (other instanceof CryptoWritableFile) {
+			CryptoWritableFile dst = (CryptoWritableFile) other;
+			file.moveTo(dst.file);
+		} else {
+			throw new IllegalArgumentException("Can not move CryptoFile to conventional File.");
+		}
 	}
 
 	@Override

+ 12 - 4
main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java

@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
 import java.nio.file.FileAlreadyExistsException;
 import java.time.Instant;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.stream.Stream;
@@ -101,15 +102,22 @@ class InMemoryFolder extends InMemoryNode implements Folder {
 
 	@Override
 	public void delete() {
-		// delete subfolder recursively:
-		folders().forEach(Folder::delete);
-		// delete direct children (this deletes files):
-		this.children.clear();
 		// remove ourself from parent:
 		parent.children.computeIfPresent(name, (k, v) -> {
 			// returning null removes the entry.
 			return null;
 		});
+		// delete all children:
+		for (Iterator<Map.Entry<String, InMemoryNode>> iterator = children.entrySet().iterator(); iterator.hasNext();) {
+			Map.Entry<String, InMemoryNode> entry = iterator.next();
+			iterator.remove();
+			// recursively on folders:
+			if (entry.getValue() instanceof InMemoryFolder) {
+				InMemoryFolder subFolder = (InMemoryFolder) entry.getValue();
+				// this will try to itself from our children, which is ok as we're using an iterator here.
+				subFolder.delete();
+			}
+		}
 		assert!this.exists();
 	}
 

+ 34 - 0
main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/filters/UriNormalizationFilter.java

@@ -20,6 +20,7 @@ public class UriNormalizationFilter implements HttpFilter {
 
 	private static final String[] FILE_METHODS = {"PUT"};
 	private static final String[] DIRECTORY_METHODS = {"MKCOL"};
+	private static final String MOVE = "MOVE";
 
 	@Override
 	public void init(FilterConfig filterConfig) throws ServletException {
@@ -32,6 +33,8 @@ public class UriNormalizationFilter implements HttpFilter {
 			chain.doFilter(new FileUriRequest(request), response);
 		} else if (ArrayUtils.contains(DIRECTORY_METHODS, request.getMethod().toUpperCase())) {
 			chain.doFilter(new DirectoryUriRequest(request), response);
+		} else if (MOVE.equalsIgnoreCase(request.getMethod())) {
+			chain.doFilter(new CanonicalMoveRequest(request), response);
 		} else {
 			chain.doFilter(request, response);
 		}
@@ -42,6 +45,37 @@ public class UriNormalizationFilter implements HttpFilter {
 		// no-op
 	}
 
+	/**
+	 * Makes the destination header end on "/" if moving a directory and remove additional "/" if moving a file.
+	 */
+	private static class CanonicalMoveRequest extends HttpServletRequestWrapper {
+
+		private static String DESTINATION_HEADER = "Destination";
+
+		public CanonicalMoveRequest(HttpServletRequest request) {
+			super(request);
+		}
+
+		@Override
+		public String getHeader(String name) {
+			if (name.equalsIgnoreCase(DESTINATION_HEADER)) {
+				return sameSuffixAsUri(super.getHeader(name));
+			} else {
+				return super.getHeader(name);
+			}
+		}
+
+		private String sameSuffixAsUri(String str) {
+			final String uri = this.getRequestURI();
+			if (uri.endsWith("/")) {
+				return StringUtils.appendIfMissing(str, "/");
+			} else {
+				return StringUtils.removeEnd(str, "/");
+			}
+		}
+
+	}
+
 	/**
 	 * HTTP request, whose URI never ends on "/".
 	 */

+ 12 - 4
main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFile.java

@@ -72,14 +72,22 @@ class DavFile extends DavNode<File> {
 
 	@Override
 	public void move(DavResource destination) throws DavException {
-		// TODO Auto-generated method stub
-
+		if (destination instanceof DavFile) {
+			DavFile dst = (DavFile) destination;
+			node.moveTo(dst.node);
+		} else {
+			throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
+		}
 	}
 
 	@Override
 	public void copy(DavResource destination, boolean shallow) throws DavException {
-		// TODO Auto-generated method stub
-
+		if (destination instanceof DavFile) {
+			DavFile dst = (DavFile) destination;
+			node.copyTo(dst.node);
+		} else {
+			throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
+		}
 	}
 
 	@Override

+ 40 - 9
main/jackrabbit-filesystem-adapter/src/main/java/org/cryptomator/webdav/jackrabbit/DavFolder.java

@@ -22,6 +22,7 @@ import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavResource;
 import org.apache.jackrabbit.webdav.DavResourceIterator;
 import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
+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;
@@ -32,6 +33,7 @@ import org.apache.jackrabbit.webdav.property.ResourceType;
 import org.cryptomator.filesystem.File;
 import org.cryptomator.filesystem.Folder;
 import org.cryptomator.filesystem.FolderCreateMode;
+import org.cryptomator.filesystem.Node;
 import org.cryptomator.filesystem.WritableFile;
 import org.cryptomator.webdav.jackrabbit.DavPathFactory.DavPath;
 
@@ -83,37 +85,66 @@ class DavFolder extends DavNode<Folder> {
 
 	@Override
 	public DavResourceIterator getMembers() {
-		final Stream<DavFolder> folders = node.folders().map(this::getMemberFolder);
-		final Stream<DavFile> files = node.files().map(this::getMemberFile);
+		final Stream<DavFolder> folders = node.folders().map(this::folderToDavFolder);
+		final Stream<DavFile> files = node.files().map(this::fileToDavFile);
 		return new DavResourceIteratorImpl(Stream.concat(folders, files).collect(Collectors.toList()));
 	}
 
-	private DavFolder getMemberFolder(Folder memberFolder) {
+	private DavFolder folderToDavFolder(Folder memberFolder) {
 		final DavPath subFolderLocator = path.getChild(memberFolder.name() + '/');
 		return factory.createFolder(memberFolder, subFolderLocator, session);
 	}
 
-	private DavFile getMemberFile(File memberFile) {
+	private DavFile fileToDavFile(File memberFile) {
 		final DavPath subFolderLocator = path.getChild(memberFile.name());
 		return factory.createFile(memberFile, subFolderLocator, session);
 	}
 
 	@Override
 	public void removeMember(DavResource member) throws DavException {
-		// TODO Auto-generated method stub
+		final Node child = getMemberNode(member.getDisplayName());
+		if (child instanceof Folder) {
+			Folder folder = (Folder) child;
+			folder.delete();
+		} else if (child instanceof File) {
+			File file = (File) child;
+			try (WritableFile writable = file.openWritable()) {
+				writable.delete();
+			}
+		} else {
+			throw new IllegalStateException("Unexpected node type: " + child.getClass().getName());
+		}
+	}
 
+	/**
+	 * @throws DavException Error 404 if no child with the given name exists
+	 */
+	private Node getMemberNode(String name) throws DavException {
+		return node.children().filter(c -> c.name().equals(name)).findAny().orElseThrow(() -> {
+			return new DavException(DavServletResponse.SC_NOT_FOUND, "No such file or directory: " + path + name);
+		});
 	}
 
 	@Override
 	public void move(DavResource destination) throws DavException {
-		// TODO Auto-generated method stub
-
+		if (destination instanceof DavFolder) {
+			DavFolder dst = (DavFolder) destination;
+			node.moveTo(dst.node);
+		} else {
+			throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
+		}
 	}
 
 	@Override
 	public void copy(DavResource destination, boolean shallow) throws DavException {
-		// TODO Auto-generated method stub
-
+		if (shallow) {
+			throw new UnsupportedOperationException("Shallow copy of directories not supported.");
+		} else if (destination instanceof DavFolder) {
+			DavFolder dst = (DavFolder) destination;
+			node.copyTo(dst.node);
+		} else {
+			throw new IllegalArgumentException("Destination not a DavFolder: " + destination.getClass().getName());
+		}
 	}
 
 	@Override