webdav move/copy/delete

This commit is contained in:
Sebastian Stenzel
2015-12-28 14:06:40 +01:00
parent 389c49d846
commit 0254569826
9 changed files with 129 additions and 24 deletions
@@ -71,4 +71,8 @@ public interface File extends Node, Comparable<File> {
Copier.copy(this, destination);
}
default void moveTo(File destination) {
Mover.move(this, destination);
}
}
@@ -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));
}
}
}
@@ -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);
@@ -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
@@ -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
@@ -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();
}
@@ -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 "/".
*/
@@ -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
@@ -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