From f85c561b78bcb6d92f0291a5c33d0f71cee2539e Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Mon, 23 Jun 2014 15:09:43 +0000 Subject: [PATCH] * due to permission issues File.listFiles() can return null --- source/net/filebot/Main.java | 6 +- source/net/filebot/cli/ArgumentBean.java | 2 +- .../net/filebot/cli/ScriptShellMethods.java | 14 +-- .../analyze/FileTreeTransferablePolicy.java | 57 +++++------ .../rename/FilesListTransferablePolicy.java | 7 +- .../sfv/ChecksumTableTransferablePolicy.java | 94 ++++++++----------- source/net/filebot/util/FileUtilities.java | 17 +++- source/net/filebot/util/TemporaryFolder.java | 81 +++++++--------- 8 files changed, 114 insertions(+), 164 deletions(-) diff --git a/source/net/filebot/Main.java b/source/net/filebot/Main.java index 7bdd06ea..17658f83 100644 --- a/source/net/filebot/Main.java +++ b/source/net/filebot/Main.java @@ -48,8 +48,6 @@ import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.xml.parsers.DocumentBuilderFactory; -import net.miginfocom.swing.MigLayout; -import net.sf.ehcache.CacheManager; import net.filebot.cli.ArgumentBean; import net.filebot.cli.ArgumentProcessor; import net.filebot.cli.CmdlineOperations; @@ -63,6 +61,8 @@ import net.filebot.util.ByteBufferInputStream; import net.filebot.util.PreferencesMap.PreferencesEntry; import net.filebot.util.TeePrintStream; import net.filebot.web.CachedResource; +import net.miginfocom.swing.MigLayout; +import net.sf.ehcache.CacheManager; import org.w3c.dom.NodeList; @@ -464,7 +464,7 @@ public class Main { isNewCache = true; // delete all files related to previous cache instances - for (File it : cache.listFiles()) { + for (File it : getChildren(cache)) { if (!it.equals(lockFile)) { delete(cache); } diff --git a/source/net/filebot/cli/ArgumentBean.java b/source/net/filebot/cli/ArgumentBean.java index 9aa5fdbf..d5c2055d 100644 --- a/source/net/filebot/cli/ArgumentBean.java +++ b/source/net/filebot/cli/ArgumentBean.java @@ -165,7 +165,7 @@ public class ArgumentBean { if (recursive) { files.addAll(listFiles(file)); } else { - files.addAll(asList(file.listFiles())); + files.addAll(getChildren(file)); } } else { files.add(file); diff --git a/source/net/filebot/cli/ScriptShellMethods.java b/source/net/filebot/cli/ScriptShellMethods.java index 071e0d06..0379b071 100644 --- a/source/net/filebot/cli/ScriptShellMethods.java +++ b/source/net/filebot/cli/ScriptShellMethods.java @@ -57,11 +57,7 @@ public class ScriptShellMethods { } public static List listFiles(File self, Closure closure) { - File[] files = self.listFiles(); - if (files == null) - return emptyList(); - - return (List) DefaultGroovyMethods.findAll(asList(files), closure); + return (List) DefaultGroovyMethods.findAll(FileUtilities.getChildren(self), closure); } public static boolean isVideo(File self) { @@ -101,11 +97,7 @@ public class ScriptShellMethods { } public static boolean hasFile(File self, Closure closure) { - File[] files = self.listFiles(); - if (files == null) - return false; - - return DefaultGroovyMethods.find(asList(files), closure) != null; + return DefaultGroovyMethods.find(FileUtilities.getChildren(self), closure) != null; } public static List getFiles(File self) { @@ -163,7 +155,7 @@ public class ScriptShellMethods { public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { File folder = dir.toFile(); - if (FileUtilities.filter(asList(folder.listFiles()), VIDEO_FILES).size() > 0 || MediaDetection.isDiskFolder(folder)) { + if (FileUtilities.filter(FileUtilities.getChildren(folder), VIDEO_FILES).size() > 0 || MediaDetection.isDiskFolder(folder)) { mediaFolders.add(folder); return FileVisitResult.SKIP_SUBTREE; } diff --git a/source/net/filebot/ui/analyze/FileTreeTransferablePolicy.java b/source/net/filebot/ui/analyze/FileTreeTransferablePolicy.java index d0492e8a..85022323 100644 --- a/source/net/filebot/ui/analyze/FileTreeTransferablePolicy.java +++ b/source/net/filebot/ui/analyze/FileTreeTransferablePolicy.java @@ -1,8 +1,7 @@ - package net.filebot.ui.analyze; - import static net.filebot.ui.NotificationLogging.*; +import static net.filebot.util.FileUtilities.*; import java.io.File; import java.util.List; @@ -14,58 +13,50 @@ import net.filebot.ui.analyze.FileTree.FolderNode; import net.filebot.ui.transfer.BackgroundFileTransferablePolicy; import net.filebot.util.ExceptionUtilities; import net.filebot.util.FastFile; -import net.filebot.util.FileUtilities; - class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy { - + private final FileTree tree; - - + public FileTreeTransferablePolicy(FileTree tree) { this.tree = tree; } - - + @Override protected boolean accept(List files) { return true; } - - + @Override protected void clear() { super.clear(); - + tree.clear(); } - - + @Override protected void process(List chunks) { FolderNode root = tree.getRoot(); - + for (AbstractTreeNode node : chunks) { root.add(node); } - + tree.getModel().reload(); } - - + @Override protected void process(Exception e) { UILogger.log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e); } - - + @Override protected void load(List files) { try { for (File file : files) { // use fast file to minimize system calls like length(), isDirectory(), isFile(), ... AbstractTreeNode node = getTreeNode(new FastFile(file.getPath())); - + // publish on EDT publish(node); } @@ -73,39 +64,37 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy files = getChildren(file); + FolderNode node = new FolderNode(getFolderName(file), files.size()); + // add folders first for (File f : files) { if (f.isDirectory()) { node.add(getTreeNode(f)); } } - + for (File f : files) { if (f.isFile()) { node.add(getTreeNode(f)); } } - + return node; } - + return new FileNode(file); } - - + @Override public String getFileFilterDescription() { return "files and folders"; } - + } diff --git a/source/net/filebot/ui/rename/FilesListTransferablePolicy.java b/source/net/filebot/ui/rename/FilesListTransferablePolicy.java index f2d619c8..7e6d1c63 100644 --- a/source/net/filebot/ui/rename/FilesListTransferablePolicy.java +++ b/source/net/filebot/ui/rename/FilesListTransferablePolicy.java @@ -1,6 +1,5 @@ package net.filebot.ui.rename; -import static java.util.Arrays.*; import static net.filebot.MediaTypes.*; import static net.filebot.ui.transfer.FileTransferable.*; import static net.filebot.util.FileUtilities.*; @@ -11,7 +10,6 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Scanner; -import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; @@ -91,10 +89,7 @@ class FilesListTransferablePolicy extends FileTransferablePolicy { } else if (!recursive || f.isFile() || MediaDetection.isDiskFolder(f)) { entries.add(f); } else if (f.isDirectory()) { - File[] children = f.listFiles(); - if (children != null) { - queue.addAll(0, new TreeSet(asList(children))); // FORCE NATURAL FILE ORDER - } + queue.addAll(0, sortByUniquePath(getChildren(f))); // FORCE NATURAL FILE ORDER } } diff --git a/source/net/filebot/ui/sfv/ChecksumTableTransferablePolicy.java b/source/net/filebot/ui/sfv/ChecksumTableTransferablePolicy.java index ac042f44..c4c20dcb 100644 --- a/source/net/filebot/ui/sfv/ChecksumTableTransferablePolicy.java +++ b/source/net/filebot/ui/sfv/ChecksumTableTransferablePolicy.java @@ -1,7 +1,5 @@ - package net.filebot.ui.sfv; - import static java.util.Collections.*; import static net.filebot.hash.VerificationUtilities.*; import static net.filebot.ui.NotificationLogging.*; @@ -22,33 +20,28 @@ import net.filebot.hash.VerificationFileReader; import net.filebot.ui.transfer.BackgroundFileTransferablePolicy; import net.filebot.util.ExceptionUtilities; - class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy { - + private final ChecksumTableModel model; private final ChecksumComputationService computationService; - public ChecksumTableTransferablePolicy(ChecksumTableModel model, ChecksumComputationService checksumComputationService) { this.model = model; this.computationService = checksumComputationService; } - @Override protected boolean accept(List files) { return true; } - @Override protected void clear() { super.clear(); - + computationService.reset(); model.clear(); } - @Override protected void prepare(List files) { @@ -56,30 +49,26 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy chunks) { model.addAll(chunks); } - @Override protected void process(Exception e) { UILogger.log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e); } - private final ThreadLocal executor = new ThreadLocal(); private final ThreadLocal verificationTracker = new ThreadLocal(); - @Override protected void load(List files) throws IOException { // initialize drop parameters executor.set(computationService.newExecutor()); verificationTracker.set(new VerificationTracker(3)); - + try { // handle single verification file drop if (files.size() == 1 && getHashType(files.get(0)) != null) { @@ -87,7 +76,7 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy entry = parser.next(); - + String name = normalizePathSeparators(entry.getKey().getPath()); String hash = new String(entry.getValue()); - + ChecksumCell correct = new ChecksumCell(name, file, singletonMap(type, hash)); ChecksumCell current = createComputationCell(name, baseFolder, type); - + publish(correct, current); } } finally { parser.close(); } } - protected void load(File absoluteFile, File relativeFile, File root) throws IOException, InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); - + // ignore hidden files/folders if (absoluteFile.isHidden()) return; - + // add next name to relative path relativeFile = new File(relativeFile, absoluteFile.getName()); - + if (absoluteFile.isDirectory()) { // load all files in the file tree - for (File child : absoluteFile.listFiles()) { + for (File child : getChildren(absoluteFile)) { load(child, relativeFile, root); } } else { String name = normalizePathSeparators(relativeFile.getPath()); - + // publish computation cell first publish(createComputationCell(name, root, model.getHashType())); - + // publish verification cell, if we can Map hashByVerificationFile = verificationTracker.get().getHashByVerificationFile(absoluteFile); - + for (Entry entry : hashByVerificationFile.entrySet()) { HashType hashType = verificationTracker.get().getVerificationFileType(entry.getKey()); publish(new ChecksumCell(name, entry.getKey(), singletonMap(hashType, entry.getValue()))); } } } - protected ChecksumCell createComputationCell(String name, File root, HashType hash) { ChecksumCell cell = new ChecksumCell(name, root, new ChecksumComputationTask(new File(root, name), hash)); - + // start computation task executor.get().execute(cell.getTask()); - + return cell; } - @Override public String getFileFilterDescription() { return "files, folders and sfv files"; } - private static class VerificationTracker { - + private final Map seen = new HashMap(); private final Map> cache = new HashMap>(); private final Map types = new HashMap(); - + private final int maxDepth; - public VerificationTracker(int maxDepth) { this.maxDepth = maxDepth; } - public Map getHashByVerificationFile(File file) throws IOException { // cache all verification files File folder = file.getParentFile(); int depth = 0; - + while (folder != null && depth <= maxDepth) { Integer seenLevel = seen.get(folder); - + if (seenLevel != null && seenLevel <= depth) { - // we have completely seen this parent tree before + // we have completely seen this parent tree before break; } - + if (seenLevel == null) { // folder we have never encountered before for (File verificationFile : folder.listFiles(MediaTypes.getDefaultFilter("verification"))) { @@ -222,39 +204,37 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy result = new HashMap(2); - + for (Entry> entry : cache.entrySet()) { String hash = entry.getValue().get(file); - + if (hash != null) { result.put(entry.getKey(), hash); } } - + return result; } - public HashType getVerificationFileType(File verificationFile) { return types.get(verificationFile); } - /** * Completely read a verification file and resolve all relative file paths against a given base folder @@ -262,20 +242,20 @@ class ChecksumTableTransferablePolicy extends BackgroundFileTransferablePolicy importVerificationFile(File verificationFile, HashType hashType, File baseFolder) throws IOException { VerificationFileReader parser = new VerificationFileReader(createTextReader(verificationFile), hashType.getFormat()); Map result = new HashMap(); - + try { while (parser.hasNext()) { Entry entry = parser.next(); - + // resolve relative path, the hash is probably a substring, so we compact it, for memory reasons result.put(new File(baseFolder, entry.getKey().getPath()), new String(entry.getValue())); } } finally { parser.close(); } - + return result; } } - + } diff --git a/source/net/filebot/util/FileUtilities.java b/source/net/filebot/util/FileUtilities.java index b2ae332f..b101914a 100644 --- a/source/net/filebot/util/FileUtilities.java +++ b/source/net/filebot/util/FileUtilities.java @@ -380,6 +380,17 @@ public final class FileUtilities { return f; } + public static List getChildren(File file) { + File[] files = file.listFiles(); + + // children array may be null if folder permissions do not allow listing of files + if (files == null) { + return asList(new File[0]); + } + + return asList(files); + } + public static List listFiles(File... folders) { return listFiles(asList(folders)); } @@ -417,11 +428,7 @@ public final class FileUtilities { if (depth > maxDepth) return; - File[] children = folder.listFiles(); - if (children == null) - return; - - for (File it : children) { + for (File it : getChildren(folder)) { if (!addHidden && it.isHidden()) // ignore hidden files continue; diff --git a/source/net/filebot/util/TemporaryFolder.java b/source/net/filebot/util/TemporaryFolder.java index 0bdd636a..be48d96a 100644 --- a/source/net/filebot/util/TemporaryFolder.java +++ b/source/net/filebot/util/TemporaryFolder.java @@ -1,6 +1,6 @@ - package net.filebot.util; +import static net.filebot.util.FileUtilities.*; import java.io.File; import java.io.IOException; @@ -10,47 +10,42 @@ import java.util.List; import java.util.Map; import java.util.UUID; - public final class TemporaryFolder { - + private static final Map folders = new HashMap(); - /** - * Get a {@link TemporaryFolder} instance for a given name. The actual directory will be - * created lazily (e.g. when a file is created). The name of the directory will start with - * the given name (lower-case) and contain a unique id, so multiple application instances - * may run at the same time without the risk of interference. + * Get a {@link TemporaryFolder} instance for a given name. The actual directory will be created lazily (e.g. when a file is created). The name of the directory will start with the given name (lower-case) and contain a unique id, so multiple application instances may run at the same time without the risk of interference. * - * @param name case-insensitive name of a temporary folder (e.g. application name) + * @param name + * case-insensitive name of a temporary folder (e.g. application name) * @return temporary folder for this name */ public static TemporaryFolder getFolder(String name) { // make name case-insensitive name = name.toLowerCase(); - + synchronized (folders) { TemporaryFolder folder = folders.get(name); - + if (folder == null) { File tmpdir = new File(System.getProperty("java.io.tmpdir")); String subdir = String.format("%s [%s]", name, UUID.randomUUID()); - + folder = new TemporaryFolder(new File(tmpdir, subdir)); folders.put(name, folder); } - + return folder; } } - /** * Delete all temporary folders on shutdown */ static { Runtime.getRuntime().addShutdownHook(new Thread("TemporaryFolder Cleanup") { - + @Override public void run() { synchronized (folders) { @@ -61,57 +56,53 @@ public final class TemporaryFolder { } }); } - + private final File root; - private TemporaryFolder(File root) { this.root = root; } - /** * Create an empty file in this temporary folder. * - * @param name name of the file + * @param name + * name of the file * @return newly created file - * @throws IOException if an I/O error occurred + * @throws IOException + * if an I/O error occurred */ public File createFile(String name) throws IOException { - + // if the directory does not exist it will be created File file = new File(getFolder(), name); file.createNewFile(); - + return file; } - /** - * Creates an empty file in this temporary folder, using the given prefix and suffix to - * generate its name. + * Creates an empty file in this temporary folder, using the given prefix and suffix to generate its name. * - * @param prefix The prefix string to be used in generating the file's name; must be at - * least three characters long - * @param suffix The suffix string to be used in generating the file's name; may be null, - * in which case the suffix ".tmp" will be used + * @param prefix + * The prefix string to be used in generating the file's name; must be at least three characters long + * @param suffix + * The suffix string to be used in generating the file's name; may be null, in which case the suffix ".tmp" will be used * @return An abstract pathname denoting a newly-created empty file - * @throws IOException If a file could not be created + * @throws IOException + * If a file could not be created * @see File#createTempFile(String, String) */ public File createFile(String prefix, String suffix) throws IOException { return File.createTempFile(prefix, suffix, getFolder()); } - public boolean deleteFile(String name) { return new File(getFolder(), name).delete(); } - /** - * Retrieve the {@link File} object for this {@link TemporaryFolder}. The actual directory - * for the {@link TemporaryFolder} instance will be created by this method. + * Retrieve the {@link File} object for this {@link TemporaryFolder}. The actual directory for the {@link TemporaryFolder} instance will be created by this method. * * @return the {@link File} object for this {@link TemporaryFolder} */ @@ -119,28 +110,25 @@ public final class TemporaryFolder { if (!root.exists()) { root.mkdirs(); } - + return root; } - public TemporaryFolder subFolder(String name) { return new TemporaryFolder(new File(getFolder(), name)); } - public List list(boolean recursive) { List list = new ArrayList(); - + list(root, list, recursive); - + return list; } - private void list(File file, List list, boolean recursive) { if (file.isDirectory()) { - for (File entry : file.listFiles()) { + for (File entry : getChildren(file)) { if (entry.isDirectory()) { if (recursive) { list(entry, list, recursive); @@ -151,26 +139,25 @@ public final class TemporaryFolder { } } } - public void delete() { delete(root); } - /** * Delete files/folders recursively * - * @param file file/folder that will be deleted + * @param file + * file/folder that will be deleted */ private void delete(File file) { if (file.isDirectory()) { - for (File entry : file.listFiles()) { + for (File entry : getChildren(file)) { delete(entry); } } - + file.delete(); } - + }