diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java index 0554cdb5..73d18c22 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataCombiner.java @@ -1,8 +1,11 @@ package org.qortal.arbitrary; +import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.qortal.utils.FilesystemUtils; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -30,6 +33,35 @@ public class ArbitraryDataCombiner { } } + public void cleanup() { + this.cleanupPath(this.pathBefore); + this.cleanupPath(this.pathAfter); + } + + private void cleanupPath(Path path) { + // Delete pathBefore, if it exists in our data/temp directory + if (FilesystemUtils.pathInsideDataOrTempPath(path)) { + File directory = new File(path.toString()); + try { + FileUtils.deleteDirectory(directory); + } catch (IOException e) { + // This will eventually be cleaned up by a maintenance process, so log the error and continue + LOGGER.info("Unable to cleanup directory {}", directory.toString()); + } + } + + // Delete the parent directory of pathBefore if it is empty (and exists in our data/temp directory) + Path parentDirectory = path.getParent(); + if (FilesystemUtils.pathInsideDataOrTempPath(parentDirectory)) { + try { + Files.deleteIfExists(parentDirectory); + } catch (IOException e) { + // This will eventually be cleaned up by a maintenance process, so log the error and continue + LOGGER.info("Unable to cleanup parent directory {}", parentDirectory.toString()); + } + } + } + private void preExecute() { if (this.pathBefore == null || this.pathAfter == null) { throw new IllegalStateException(String.format("No paths available to build patch")); diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java index 9bb6fae9..75417546 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataFile.java @@ -6,6 +6,7 @@ import org.qortal.crypto.Crypto; import org.qortal.settings.Settings; import org.qortal.transform.transaction.TransactionTransformer; import org.qortal.utils.Base58; +import org.qortal.utils.FilesystemUtils; import java.io.*; import java.nio.ByteBuffer; @@ -282,7 +283,9 @@ public class ArbitraryDataFile { // Copy temporary file to data directory this.filePath = this.copyToDataDirectory(tempDir); - Files.delete(tempDir); + if (FilesystemUtils.pathInsideDataOrTempPath(tempDir)) { + Files.delete(tempDir); + } return true; } catch (FileNotFoundException e) { @@ -296,22 +299,18 @@ public class ArbitraryDataFile { public boolean delete() { // Delete the complete file - // ... but only if it's inside the Qortal data directory + // ... but only if it's inside the Qortal data or temp directory Path path = Paths.get(this.filePath); - String dataPath = Settings.getInstance().getDataPath(); - Path dataDirectory = Paths.get(dataPath); - if (!path.toAbsolutePath().startsWith(dataDirectory.toAbsolutePath())) { - return false; - } - - if (Files.exists(path)) { - try { - Files.delete(path); - this.cleanupFilesystem(); - LOGGER.debug("Deleted file {}", path.toString()); - return true; - } catch (IOException e) { - LOGGER.warn("Couldn't delete DataFileChunk at path {}", this.filePath); + if (FilesystemUtils.pathInsideDataOrTempPath(path)) { + if (Files.exists(path)) { + try { + Files.delete(path); + this.cleanupFilesystem(); + LOGGER.debug("Deleted file {}", path.toString()); + return true; + } catch (IOException e) { + LOGGER.warn("Couldn't delete DataFileChunk at path {}", this.filePath); + } } } return false; @@ -343,7 +342,9 @@ public class ArbitraryDataFile { try (Stream files = Files.list(directory)) { final long count = files.count(); if (count == 0) { - Files.delete(directory); + if (FilesystemUtils.pathInsideDataOrTempPath(directory)) { + Files.delete(directory); + } } } catch (IOException e) { LOGGER.warn("Unable to count files in directory", e); diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java index 8ac19db5..da399242 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataMerge.java @@ -176,19 +176,23 @@ public class ArbitraryDataMerge { Path dest = Paths.get(base.toString(), relativePath.toString()); LOGGER.trace("Copying {} to {}", source, dest); - FilesystemUtils.copyDirectory(source.toString(), dest.toString()); + FilesystemUtils.copyAndReplaceDirectory(source.toString(), dest.toString()); } private static void deletePathInBaseDir(Path base, Path relativePath) throws IOException { Path dest = Paths.get(base.toString(), relativePath.toString()); File file = new File(dest.toString()); if (file.exists() && file.isFile()) { - LOGGER.trace("Deleting file {}", dest); - Files.delete(dest); + if (FilesystemUtils.pathInsideDataOrTempPath(dest)) { + LOGGER.trace("Deleting file {}", dest); + Files.delete(dest); + } } if (file.exists() && file.isDirectory()) { - LOGGER.trace("Deleting directory {}", dest); - FileUtils.deleteDirectory(file); + if (FilesystemUtils.pathInsideDataOrTempPath(dest)) { + LOGGER.trace("Deleting directory {}", dest); + FileUtils.deleteDirectory(file); + } } } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataPatches.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataPatches.java index c12c6a53..f8882e78 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataPatches.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataPatches.java @@ -54,7 +54,8 @@ public class ArbitraryDataPatches { Path pathAfter = this.paths.get(i); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(pathBefore, pathAfter); combiner.combine(); - pathBefore = combiner.getFinalPath(); // TODO: cleanup + combiner.cleanup(); + pathBefore = combiner.getFinalPath(); } this.finalPath = pathBefore; } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java index 4cf2486e..9a9d034d 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataReader.java @@ -1,5 +1,6 @@ package org.qortal.arbitrary; +import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.crypto.AES; @@ -77,8 +78,8 @@ public class ArbitraryDataReader { this.createUncompressedDirectory(); } - private void postExecute() throws IOException { - this.cleanupFilesystem(); + private void postExecute() { + } private void createWorkingDirectory() { @@ -104,7 +105,7 @@ public class ArbitraryDataReader { private void deleteExistingFiles() { final Path uncompressedPath = this.uncompressedPath; - if (uncompressedPath != null) { + if (FilesystemUtils.pathInsideDataOrTempPath(uncompressedPath)) { if (Files.exists(uncompressedPath)) { LOGGER.trace("Attempting to delete path {}", this.uncompressedPath); try { @@ -268,7 +269,7 @@ public class ArbitraryDataReader { if (file.isDirectory()) { // Already a directory - nothing to uncompress // We still need to copy the directory to its final destination if it's not already there - this.copyFilePathToFinalDestination(); + this.moveFilePathToFinalDestination(); return; } @@ -282,11 +283,13 @@ public class ArbitraryDataReader { } // Replace filePath pointer with the uncompressed file path - Files.delete(this.filePath); + if (FilesystemUtils.pathInsideDataOrTempPath(this.filePath)) { + Files.delete(this.filePath); + } this.filePath = this.uncompressedPath; } - private void copyFilePathToFinalDestination() throws IOException { + private void moveFilePathToFinalDestination() throws IOException { if (this.filePath.compareTo(this.uncompressedPath) != 0) { File source = new File(this.filePath.toString()); File dest = new File(this.uncompressedPath.toString()); @@ -296,17 +299,28 @@ public class ArbitraryDataReader { if (dest == null || !dest.exists()) { throw new IllegalStateException("Destination directory doesn't exist"); } - FilesystemUtils.copyDirectory(source.toString(), dest.toString()); - } - } + FilesystemUtils.copyAndReplaceDirectory(source.toString(), dest.toString()); - private void cleanupFilesystem() throws IOException { - // Clean up - if (this.uncompressedPath != null) { - File unzippedFile = new File(this.uncompressedPath.toString()); - if (unzippedFile.exists()) { - unzippedFile.delete(); + try { + // Delete existing + if (FilesystemUtils.pathInsideDataOrTempPath(this.filePath)) { + File directory = new File(this.filePath.toString()); + FileUtils.deleteDirectory(directory); + } + + // ... and its parent directory if empty + Path parentDirectory = this.filePath.getParent(); + if (FilesystemUtils.pathInsideDataOrTempPath(parentDirectory)) { + Files.deleteIfExists(parentDirectory); + } + + } catch (IOException e) { + // This will eventually be cleaned up by a maintenance process, so log the error and continue + LOGGER.info("Unable to cleanup directories: {}", e.getMessage()); } + + // Finally, update filePath to point to uncompressedPath + this.filePath = this.uncompressedPath; } } diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java index d87256d0..221366f0 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataWriter.java @@ -9,7 +9,7 @@ import org.qortal.crypto.AES; import org.qortal.repository.DataException; import org.qortal.arbitrary.ArbitraryDataFile.*; import org.qortal.settings.Settings; -import org.qortal.utils.Base58; +import org.qortal.utils.FilesystemUtils; import org.qortal.utils.ZipUtils; import javax.crypto.BadPaddingException; @@ -155,7 +155,9 @@ public class ArbitraryDataWriter { AES.encryptFile("AES", this.aesKey, this.filePath.toString(), this.encryptedPath.toString()); // Replace filePath pointer with the encrypted file path - Files.delete(this.filePath); + if (FilesystemUtils.pathInsideDataOrTempPath(this.filePath)) { + Files.delete(this.filePath); + } this.filePath = this.encryptedPath; } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException @@ -205,19 +207,19 @@ public class ArbitraryDataWriter { private void cleanupFilesystem() throws IOException { // Clean up - if (this.compressedPath != null) { + if (FilesystemUtils.pathInsideDataOrTempPath(this.compressedPath)) { File zippedFile = new File(this.compressedPath.toString()); if (zippedFile.exists()) { zippedFile.delete(); } } - if (this.encryptedPath != null) { + if (FilesystemUtils.pathInsideDataOrTempPath(this.encryptedPath)) { File encryptedFile = new File(this.encryptedPath.toString()); if (encryptedFile.exists()) { encryptedFile.delete(); } } - if (this.workingPath != null) { + if (FilesystemUtils.pathInsideDataOrTempPath(this.workingPath)) { FileUtils.deleteDirectory(new File(this.workingPath.toString())); } } diff --git a/src/main/java/org/qortal/utils/FilesystemUtils.java b/src/main/java/org/qortal/utils/FilesystemUtils.java index a87e18a0..54054c2d 100644 --- a/src/main/java/org/qortal/utils/FilesystemUtils.java +++ b/src/main/java/org/qortal/utils/FilesystemUtils.java @@ -1,10 +1,9 @@ package org.qortal.utils; +import org.qortal.settings.Settings; + import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.*; public class FilesystemUtils { @@ -18,17 +17,30 @@ public class FilesystemUtils { return false; } - public static void copyDirectory(String sourceDirectoryLocation, String destinationDirectoryLocation) throws IOException { + public static void copyAndReplaceDirectory(String sourceDirectoryLocation, String destinationDirectoryLocation) throws IOException { Files.walk(Paths.get(sourceDirectoryLocation)) .forEach(source -> { Path destination = Paths.get(destinationDirectoryLocation, source.toString() .substring(sourceDirectoryLocation.length())); try { - Files.copy(source, destination); + Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { e.printStackTrace(); } }); } + public static boolean pathInsideDataOrTempPath(Path path) { + if (path == null) { + return false; + } + Path dataPath = Paths.get(Settings.getInstance().getDataPath()).toAbsolutePath(); + Path tempDataPath = Paths.get(Settings.getInstance().getTempDataPath()).toAbsolutePath(); + Path absolutePath = path.toAbsolutePath(); + if (absolutePath.startsWith(dataPath) || absolutePath.startsWith(tempDataPath)) { + return true; + } + return false; + } + }