mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-08-17 20:56:02 +00:00
improved recursive directory deletion
This commit is contained in:
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user