improved recursive directory deletion

This commit is contained in:
Sebastian Stenzel
2015-10-29 10:24:11 +01:00
parent 1a81b3a781
commit b5160cddb9
4 changed files with 65 additions and 24 deletions
@@ -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 {
@@ -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;
@@ -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 static class DeletingFileVisitor extends SimpleFileVisitor<Path> {
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 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;
}
@@ -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());