Browse Source

improved recursive directory deletion

Sebastian Stenzel 9 years ago
parent
commit
b5160cddb9

+ 0 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java

@@ -5,7 +5,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.jackrabbit.webdav.DavLocatorFactory;
 import org.apache.jackrabbit.webdav.DavResourceLocator;
 import org.apache.jackrabbit.webdav.util.EncodeUtil;
-import org.apache.logging.log4j.util.Strings;
 
 public class CleartextLocatorFactory implements DavLocatorFactory {
 

+ 0 - 1
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java

@@ -22,7 +22,6 @@ import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.DavSession;
 import org.apache.jackrabbit.webdav.lock.LockManager;
 import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
-import org.apache.logging.log4j.util.Strings;
 import org.cryptomator.crypto.Cryptor;
 import org.cryptomator.webdav.exceptions.IORuntimeException;
 import org.eclipse.jetty.http.HttpHeader;

+ 57 - 22
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java

@@ -16,7 +16,6 @@ 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;
@@ -26,9 +25,10 @@ import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.FileTime;
 import java.time.Instant;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Queue;
 import java.util.UUID;
+import java.util.concurrent.LinkedTransferQueue;
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
@@ -169,7 +169,7 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 			final Path tmpFilePath = Files.createTempFile(dirPath, null, null);
 			// encrypt to tmp file:
 			try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.DSYNC)) {
-				long asd = cryptor.encryptFile(inputContext.getInputStream(), c);
+				cryptor.encryptFile(inputContext.getInputStream(), c);
 			} catch (SecurityException e) {
 				throw new DavException(DavServletResponse.SC_FORBIDDEN, e);
 			} catch (EncryptFailedException e) {
@@ -245,30 +245,21 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 			throw new DavException(DavServletResponse.SC_NOT_FOUND);
 		}
 		// https://tools.ietf.org/html/rfc4918#section-9.6
-		// we must unlock anything we want to delete
+		// we must unlock anything we want to delete:
 		for (ActiveLock lock : member.getLocks()) {
 			member.unlock(lock.getToken());
 		}
+		// now we can delete the file or directory:
 		try {
 			final String cleartextFilename = FilenameUtils.getName(member.getResourcePath());
-			final String ciphertextFilename;
 			if (member instanceof EncryptedDir) {
 				final EncryptedDir subDir = (EncryptedDir) member;
-				// remove sub-members recursively before deleting own directory
-				for (Iterator<DavResource> iterator = member.getMembers(); iterator.hasNext();) {
-					DavResource m = iterator.next();
-					member.removeMember(m);
-				}
-				final Path subDirPath = subDir.getDirectoryPath();
-				if (subDirPath != null) {
-					Files.walkFileTree(subDirPath, new DeletingFileVisitor());
-				}
-				ciphertextFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
+				deleteSubDirectory(subDir);
 			} else {
-				ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename);
+				final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename);
+				final Path memberPath = dirPath.resolve(ciphertextFilename);
+				Files.deleteIfExists(memberPath);
 			}
-			final Path memberPath = dirPath.resolve(ciphertextFilename);
-			Files.deleteIfExists(memberPath);
 		} catch (FileNotFoundException e) {
 			// no-op
 		} catch (IOException e) {
@@ -353,21 +344,65 @@ class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
 	}
 	
 	/**
-	 * Deletes all files and folders, it visits.
+	 * Deletes a given directory recursively by resolving subdirectories using their directory files.
+	 */
+	private void deleteSubDirectory(final EncryptedDir subDir) throws IOException {
+		final Path subDirPath = subDir.getDirectoryPath();
+		filenameTranslator.uncacheDirectoryId(subDir.filePath);
+		Files.delete(subDir.filePath);
+		final LinkedTransferQueue<Path> queue = new LinkedTransferQueue<>();
+		queue.put(subDirPath);
+		Path dir;
+		while ((dir = queue.poll()) != null) {
+			if (Files.exists(dir)) {
+				Files.walkFileTree(dir, new RecursiveDirectoryDeletingVisitor(queue));
+			}
+		}
+	}
+	
+	/**
+	 * Deletes all files it visits and enqueues subdirectories into a given {@link Queue} for deletion, too.
+	 * 
+	 * If its parent directory is empty after deleting, it will get deleted, too.
 	 */
-	private static class DeletingFileVisitor extends SimpleFileVisitor<Path> {
+	private class RecursiveDirectoryDeletingVisitor extends SimpleFileVisitor<Path> {
+		
+		private final Queue<Path> directories;
+		
+		private RecursiveDirectoryDeletingVisitor(Queue<Path> directories) {
+			this.directories = directories;
+		}
 
 		@Override
 		public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
-			if (attributes.isRegularFile()) {
-				Files.delete(file);
+			if (file.toString().endsWith(DIR_EXT)) {
+				final String directoryId = filenameTranslator.getDirectoryId(file, false);
+				final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId);
+				directories.add(directoryPath);
+				filenameTranslator.uncacheDirectoryId(file);
 			}
+			Files.delete(file);
 			return FileVisitResult.CONTINUE;
 		}
 
 		@Override
 		public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
+			// first check, if we're the only remaining child:
+			boolean hasSiblings = false;
+			try (final DirectoryStream<Path> siblings = Files.newDirectoryStream(dir.getParent())) {
+				for (Path sibling : siblings) {
+					if (!dir.getFileName().equals(sibling.getFileName())) {
+						hasSiblings = true;
+						break;
+					}
+				}
+			}
+			// delete our current directory:
 			Files.delete(dir);
+			// if we have siblings, we still need our parent. Otherwise delete it, too:
+			if (!hasSiblings) {
+				Files.delete(dir.getParent());
+			}
 			return FileVisitResult.CONTINUE;
 		}
 

+ 8 - 0
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java

@@ -68,6 +68,14 @@ class FilenameTranslator implements FileConstants {
 			}
 		}
 	}
+	
+	/**
+	 * to be called when a directory gets deleted, so the corresponding directory id is not longer cached.
+	 */
+	public void uncacheDirectoryId(Path directoryFile) throws IOException {
+		final Pair<Path, FileTime> key = ImmutablePair.of(directoryFile, Files.getLastModifiedTime(directoryFile));
+		directoryIdCache.remove(key);
+	}
 
 	public Path getEncryptedDirectoryPath(String directoryId) {
 		final String encrypted = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator());