mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-08-17 20:56:02 +00:00
webdav move/copy/delete
This commit is contained in:
@@ -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);
|
||||
|
||||
+6
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user