diff --git a/cloudata-files/src/main/java/com/cloudata/files/fs/FsClient.java b/cloudata-files/src/main/java/com/cloudata/files/fs/FsClient.java index 169afeb..54508ee 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/fs/FsClient.java +++ b/cloudata-files/src/main/java/com/cloudata/files/fs/FsClient.java @@ -251,6 +251,7 @@ private void createNewEntry(FsPath parent, ByteString name, InodeData.Builder in ByteString dirEntryValue = ByteStrings.encode(created.getInode()); Modifier[] modifiers; if (overwrite) { + // TODO: Should we record any existing key in the deleted section? modifiers = new Modifier[] {}; } else { modifiers = new Modifier[] { IfNotExists.INSTANCE }; @@ -343,4 +344,23 @@ public boolean delete(FsPath fsPath) throws IOException { return store.delete(key); } } + + public void move(FsPath fsPath, FsPath newParentFsPath, ByteString newName) throws IOException { + long id = fsPath.getId(); + + ByteString oldKey = buildDirEntryKey(fsPath); + ByteString newKey = buildDirEntryKey(newParentFsPath, newName); + + // Create the new dir entry, delete the old one + { + ByteString dirEntryValue = ByteStrings.encode(id); + Modifier[] modifiers = new Modifier[] { IfNotExists.INSTANCE }; + + if (!store.put(newKey, dirEntryValue, modifiers)) { + throw new FsFileAlreadyExistsException(); + } + } + + store.delete(oldKey); + } } diff --git a/cloudata-files/src/main/java/com/cloudata/files/fs/FsVolume.java b/cloudata-files/src/main/java/com/cloudata/files/fs/FsVolume.java index 534ceac..0b4177d 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/fs/FsVolume.java +++ b/cloudata-files/src/main/java/com/cloudata/files/fs/FsVolume.java @@ -28,4 +28,31 @@ public static FsVolume fromHost(String host) { public ByteString getPrefix() { return prefix; } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (key ^ (key >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + FsVolume other = (FsVolume) obj; + if (key != other.key) { + return false; + } + return true; + } + } diff --git a/cloudata-files/src/main/java/com/cloudata/files/webdav/GetHandler.java b/cloudata-files/src/main/java/com/cloudata/files/webdav/GetHandler.java index 2281d8e..6cbb10d 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/webdav/GetHandler.java +++ b/cloudata-files/src/main/java/com/cloudata/files/webdav/GetHandler.java @@ -48,7 +48,6 @@ public HttpObject doAction(FsPath fsPath) throws Exception { // TODO: Handle ETAG?? // If-Modified-Since Validation - String ifModifiedSince = request.getHeader(HttpHeaders.Names.IF_MODIFIED_SINCE); if (!Strings.isNullOrEmpty(ifModifiedSince)) { Date ifModifiedSinceDate; @@ -63,7 +62,7 @@ public HttpObject doAction(FsPath fsPath) throws Exception { long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; long fileLastModifiedSeconds = fsPath.getModified() / 1000; if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { - // throw new WebdavResponseException(HttpResponseStatus.NOT_MODIFIED); + throw new WebdavResponseException(HttpResponseStatus.NOT_MODIFIED); } } diff --git a/cloudata-files/src/main/java/com/cloudata/files/webdav/MethodHandler.java b/cloudata-files/src/main/java/com/cloudata/files/webdav/MethodHandler.java index fbab140..41dab90 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/webdav/MethodHandler.java +++ b/cloudata-files/src/main/java/com/cloudata/files/webdav/MethodHandler.java @@ -14,7 +14,6 @@ import java.io.FileNotFoundException; import java.net.URI; import java.util.Calendar; -import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import java.util.concurrent.Callable; @@ -37,7 +36,6 @@ import com.google.common.base.Charsets; import com.google.common.base.Splitter; import com.google.common.base.Strings; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -255,28 +253,7 @@ public FsPath call() throws Exception { String host = ""; String path = resolvePath; - List pathTokens; - - if (path.equals("") || path.equals("/")) { - pathTokens = Collections.emptyList(); - } else { - if (path.contains("//")) { - path = path.replace("//", "/"); - } - - if (path.startsWith("/")) { - path = path.substring(1); - } - - if (path.endsWith("/")) { - path = path.substring(0, path.length() - 1); - } - - pathTokens = Lists.newArrayList(); - for (String token : Splitter.on('/').split(path)) { - pathTokens.add(Urls.decodeComponent(token)); - } - } + List pathTokens = Urls.pathToTokens(path); FsVolume volume = FsVolume.fromHost(host); return getFsClient().resolve(volume, credentials, pathTokens); diff --git a/cloudata-files/src/main/java/com/cloudata/files/webdav/MoveHandler.java b/cloudata-files/src/main/java/com/cloudata/files/webdav/MoveHandler.java new file mode 100644 index 0000000..774145f --- /dev/null +++ b/cloudata-files/src/main/java/com/cloudata/files/webdav/MoveHandler.java @@ -0,0 +1,66 @@ +package com.cloudata.files.webdav; + +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpResponseStatus; + +import java.io.FileNotFoundException; +import java.net.URI; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.cloudata.files.fs.FsCredentials; +import com.cloudata.files.fs.FsPath; +import com.cloudata.files.fs.FsVolume; +import com.google.common.base.Strings; +import com.google.protobuf.ByteString; + +public class MoveHandler extends MethodHandler { + + private static final Logger log = LoggerFactory.getLogger(MoveHandler.class); + + public MoveHandler(WebdavRequestHandler requestHandler) { + super(requestHandler); + } + + @Override + protected HttpObject doAction(FsPath fsPath) throws Exception { + WebdavRequest request = getRequest(); + + String destination = request.getHeader("Destination"); + if (Strings.isNullOrEmpty(destination)) { + throw new IllegalArgumentException(); + } + + String newName; + FsPath newParentFsPath; + + { + URI uri = URI.create(destination); + String path = uri.getPath(); + + String parentPath = Urls.getParentPath(path); + List pathTokens = Urls.pathToTokens(parentPath); + newName = Urls.getLastPathComponent(path, true); + + FsVolume volume = fsPath.getVolume(); + if (!volume.equals(FsVolume.fromHost(uri.getHost()))) { + log.error("Move attempted between different volumes: {} {}", getUri(), uri); + throw new IllegalArgumentException(); + } + FsCredentials credentials = request.getCredentials(); + + newParentFsPath = getFsClient().resolve(volume, credentials, pathTokens); + } + + if (newParentFsPath == null) { + throw new FileNotFoundException(); + } + + getFsClient().move(fsPath, newParentFsPath, ByteString.copyFromUtf8(newName)); + + return buildFullResponse(HttpResponseStatus.CREATED); + } + +} diff --git a/cloudata-files/src/main/java/com/cloudata/files/webdav/Urls.java b/cloudata-files/src/main/java/com/cloudata/files/webdav/Urls.java index d9b0f93..9fa850b 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/webdav/Urls.java +++ b/cloudata-files/src/main/java/com/cloudata/files/webdav/Urls.java @@ -2,8 +2,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; +import java.util.Collections; +import java.util.List; +import com.google.common.base.Splitter; import com.google.common.base.Throwables; +import com.google.common.collect.Lists; public class Urls { @@ -52,4 +56,30 @@ public static String decodeComponent(String component) { } } + public static List pathToTokens(String path) { + List pathTokens; + + if (path.equals("") || path.equals("/")) { + pathTokens = Collections.emptyList(); + } else { + if (path.contains("//")) { + path = path.replace("//", "/"); + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + if (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + + pathTokens = Lists.newArrayList(); + for (String token : Splitter.on('/').split(path)) { + pathTokens.add(Urls.decodeComponent(token)); + } + } + return pathTokens; + } + } diff --git a/cloudata-files/src/main/java/com/cloudata/files/webdav/WebdavRequestHandler.java b/cloudata-files/src/main/java/com/cloudata/files/webdav/WebdavRequestHandler.java index bdd1c16..df4b21f 100644 --- a/cloudata-files/src/main/java/com/cloudata/files/webdav/WebdavRequestHandler.java +++ b/cloudata-files/src/main/java/com/cloudata/files/webdav/WebdavRequestHandler.java @@ -94,10 +94,10 @@ public ListenableFuture processRequest() throws Exception { handler = new PutHandler(this); } else if (request.getMethod() == HttpMethod.DELETE) { handler = new DeleteHandler(this); - } else if (request.getMethod().name().equals("PROPPATCH")) { - throw new UnsupportedOperationException(); + } else if (request.getMethod().name().equals("MOVE")) { + handler = new MoveHandler(this); } else { - log.warn("Method not allowed: " + request.getMethod()); + log.warn("Method not allowed: {}", request); throw new WebdavResponseException(HttpResponseStatus.METHOD_NOT_ALLOWED); }