001// SPDX-License-Identifier: GPL-3.0-or-later
002
003package es.uvigo.esei.sing.textproc.step.util;
004
005import java.io.IOException;
006import java.nio.file.FileVisitOption;
007import java.nio.file.FileVisitResult;
008import java.nio.file.Files;
009import java.nio.file.Path;
010import java.nio.file.SimpleFileVisitor;
011import java.nio.file.attribute.BasicFileAttributes;
012import java.util.EnumSet;
013
014import lombok.AccessLevel;
015import lombok.NoArgsConstructor;
016import lombok.NonNull;
017
018/**
019 * This class contains static utility methods for manipulating filesystem
020 * objects that don't belong to the responsibilities of a single class.
021 *
022 * @author Alejandro González García
023 */
024@NoArgsConstructor(access = AccessLevel.PRIVATE)
025public final class PathUtil {
026        /**
027         * Attempts a best effort to clean up the file tree whose root is at the
028         * specified path (that is, all the files and directories inside a directory,
029         * like {@code rm -rf} does on Unix systems). Any I/O error that occurs is
030         * silently discarded.
031         *
032         * @param root The root location of file system (sub)tree that will be deleted.
033         * @throws IllegalArgumentException If {@code root} is {@code null}.
034         */
035        public static void deletePathRecursively(@NonNull final Path root) {
036                try {
037                        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
038                                @Override
039                                public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
040                                        // Ignore that the file doesn't exist, because another process deleted it
041                                        // or whatever
042                                        Files.deleteIfExists(file);
043
044                                        return FileVisitResult.CONTINUE;
045                                }
046
047                                @Override
048                                public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
049                                        // Delete the directory itself after its entries are deleted
050                                        Files.deleteIfExists(dir);
051
052                                        return FileVisitResult.CONTINUE;
053                                }
054                        });
055                } catch (final IOException ignored) {}
056        }
057
058        /**
059         * Returns an approximation to the total disk space consumed by a file tree
060         * whose root is at the specified path (in other words, the total disk space
061         * consumed by all the files inside a directory, including files in
062         * subdirectories).
063         * <p>
064         * If an I/O error occurs while processing a file or directory, which can be
065         * caused, for instance, by insufficient permissions to read it, it won't be
066         * counted for the total. On the other hand, even if no I/O error occurs, it is
067         * not guaranteed that the resulting number is the exact total size of the files
068         * in the file system because of transparent compression, sparse files and so
069         * on. Also, symbolic links are resolved.
070         * </p>
071         *
072         * @param root The root location of file system (sub)tree whose size will be
073         *             computed.
074         * @return The total approximate size of the disk space consumed by the
075         *         (sub)tree, in bytes.
076         * @throws IllegalArgumentException If {@code root} is {@code null}.
077         */
078        public static long getTotalPathSize(@NonNull final Path root) {
079                final VariableHolder<Long> totalSize = new VariableHolder<>(0L);
080
081                try {
082                        Files.walkFileTree(
083                                root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
084                                new SimpleFileVisitor<Path>() {
085                                        @Override
086                                        public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
087                                                // Ignore named pipes and such
088                                                if (attrs.isRegularFile()) {
089                                                        totalSize.setVariable(totalSize.getVariable() + attrs.size());
090                                                }
091
092                                                return FileVisitResult.CONTINUE;
093                                        }
094
095                                        @Override
096                                        public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
097                                                // Ignore failures
098                                                return FileVisitResult.CONTINUE;
099                                        }
100
101                                        @Override
102                                        public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
103                                                // Ignore failures
104                                                return FileVisitResult.CONTINUE;
105                                        }
106                                }
107                        );
108                } catch (final IOException ignored) {}
109
110                return totalSize.getVariable();
111        }
112}