mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-23 16:28:51 -05:00
* improved handling of clutter files like samples/trailers/etc
* improved movie detection
This commit is contained in:
parent
0c9bc8a742
commit
c67b0d0d47
@ -52,7 +52,6 @@ import net.sourceforge.filebot.hash.HashType;
|
|||||||
import net.sourceforge.filebot.hash.VerificationFileReader;
|
import net.sourceforge.filebot.hash.VerificationFileReader;
|
||||||
import net.sourceforge.filebot.hash.VerificationFileWriter;
|
import net.sourceforge.filebot.hash.VerificationFileWriter;
|
||||||
import net.sourceforge.filebot.media.MediaDetection;
|
import net.sourceforge.filebot.media.MediaDetection;
|
||||||
import net.sourceforge.filebot.media.ReleaseInfo;
|
|
||||||
import net.sourceforge.filebot.similarity.EpisodeMatcher;
|
import net.sourceforge.filebot.similarity.EpisodeMatcher;
|
||||||
import net.sourceforge.filebot.similarity.EpisodeMetrics;
|
import net.sourceforge.filebot.similarity.EpisodeMetrics;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
@ -298,11 +297,14 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||||||
throws Exception {
|
throws Exception {
|
||||||
CLILogger.config(format("Rename movies using [%s]", service.getName()));
|
CLILogger.config(format("Rename movies using [%s]", service.getName()));
|
||||||
|
|
||||||
// handle movie files
|
// ignore sample files
|
||||||
List<File> movieFiles = filter(files, VIDEO_FILES);
|
List<File> fileset = filter(files, NON_CLUTTER_FILES);
|
||||||
List<File> nfoFiles = filter(files, MediaTypes.getDefaultFilter("application/nfo"));
|
|
||||||
|
|
||||||
List<File> orphanedFiles = new ArrayList<File>(filter(files, FILES));
|
// handle movie files
|
||||||
|
List<File> movieFiles = filter(fileset, VIDEO_FILES);
|
||||||
|
List<File> nfoFiles = filter(fileset, MediaTypes.getDefaultFilter("application/nfo"));
|
||||||
|
|
||||||
|
List<File> orphanedFiles = new ArrayList<File>(filter(fileset, FILES));
|
||||||
orphanedFiles.removeAll(movieFiles);
|
orphanedFiles.removeAll(movieFiles);
|
||||||
orphanedFiles.removeAll(nfoFiles);
|
orphanedFiles.removeAll(nfoFiles);
|
||||||
|
|
||||||
@ -366,7 +368,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||||||
List<File> movieMatchFiles = new ArrayList<File>();
|
List<File> movieMatchFiles = new ArrayList<File>();
|
||||||
movieMatchFiles.addAll(movieFiles);
|
movieMatchFiles.addAll(movieFiles);
|
||||||
movieMatchFiles.addAll(nfoFiles);
|
movieMatchFiles.addAll(nfoFiles);
|
||||||
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
|
movieMatchFiles.addAll(filter(files, DISK_FOLDERS));
|
||||||
movieMatchFiles.addAll(filter(orphanedFiles, SUBTITLE_FILES)); // run movie detection only on orphaned subtitle files
|
movieMatchFiles.addAll(filter(orphanedFiles, SUBTITLE_FILES)); // run movie detection only on orphaned subtitle files
|
||||||
|
|
||||||
// map movies to (possibly multiple) files (in natural order)
|
// map movies to (possibly multiple) files (in natural order)
|
||||||
|
@ -8,6 +8,7 @@ import static net.sourceforge.filebot.similarity.Normalization.*;
|
|||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -53,9 +54,17 @@ public class MediaDetection {
|
|||||||
|
|
||||||
private static final ReleaseInfo releaseInfo = new ReleaseInfo();
|
private static final ReleaseInfo releaseInfo = new ReleaseInfo();
|
||||||
|
|
||||||
|
public static final FileFilter DISK_FOLDERS = releaseInfo.getDiskFolderFilter();
|
||||||
|
public static final FileFilter NON_CLUTTER_FILES = not(releaseInfo.getClutterFileFilter());
|
||||||
|
|
||||||
|
|
||||||
public static boolean isDiskFolder(File folder) {
|
public static boolean isDiskFolder(File folder) {
|
||||||
return releaseInfo.getDiskFolderFilter().accept(folder);
|
return DISK_FOLDERS.accept(folder);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isNonClutter(File file) {
|
||||||
|
return NON_CLUTTER_FILES.accept(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -290,11 +299,18 @@ public class MediaDetection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// search by file name or folder name
|
// search by file name or folder name
|
||||||
List<String> files = new ArrayList<String>();
|
List<String> terms = new ArrayList<String>();
|
||||||
files.add(getName(movieFile));
|
|
||||||
files.add(getName(movieFile.getParentFile()));
|
|
||||||
|
|
||||||
List<Movie> movieNameMatches = matchMovieName(files, locale, strict);
|
// 1. term: file
|
||||||
|
terms.add(getName(movieFile));
|
||||||
|
|
||||||
|
// 2. term: first meaningful parent folder
|
||||||
|
File movieFolder = guessMovieFolder(movieFile);
|
||||||
|
if (movieFolder != null) {
|
||||||
|
terms.add(getName(movieFolder));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Movie> movieNameMatches = matchMovieName(terms, locale, strict);
|
||||||
|
|
||||||
// skip further queries if collected matches are already sufficient
|
// skip further queries if collected matches are already sufficient
|
||||||
if (options.size() > 0 && movieNameMatches.size() > 0) {
|
if (options.size() > 0 && movieNameMatches.size() > 0) {
|
||||||
@ -304,12 +320,12 @@ public class MediaDetection {
|
|||||||
|
|
||||||
// if matching name+year failed, try matching only by name
|
// if matching name+year failed, try matching only by name
|
||||||
if (movieNameMatches.isEmpty() && strict) {
|
if (movieNameMatches.isEmpty() && strict) {
|
||||||
movieNameMatches = matchMovieName(files, locale, false);
|
movieNameMatches = matchMovieName(terms, locale, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// query by file / folder name
|
// query by file / folder name
|
||||||
if (queryLookupService != null) {
|
if (queryLookupService != null) {
|
||||||
options.addAll(queryMovieByFileName(files, queryLookupService, locale));
|
options.addAll(queryMovieByFileName(terms, queryLookupService, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
// add local matching after online search
|
// add local matching after online search
|
||||||
@ -317,11 +333,23 @@ public class MediaDetection {
|
|||||||
|
|
||||||
// sort by relevance
|
// sort by relevance
|
||||||
List<Movie> optionsByRelevance = new ArrayList<Movie>(options);
|
List<Movie> optionsByRelevance = new ArrayList<Movie>(options);
|
||||||
sort(optionsByRelevance, new SimilarityComparator(stripReleaseInfo(getName(movieFile)), stripReleaseInfo(getName(movieFile.getParentFile()))));
|
sort(optionsByRelevance, new SimilarityComparator(new NameSimilarityMetric(), stripReleaseInfo(terms, true).toArray()));
|
||||||
return optionsByRelevance;
|
return optionsByRelevance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static File guessMovieFolder(File movieFile) throws IOException {
|
||||||
|
// first meaningful parent folder
|
||||||
|
for (File f = movieFile.getParentFile(); f != null; f = f.getParentFile()) {
|
||||||
|
String term = stripReleaseInfo(f.getName());
|
||||||
|
if (term.length() > 0) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception {
|
private static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception {
|
||||||
// cross-reference file / folder name with movie list
|
// cross-reference file / folder name with movie list
|
||||||
final HighPerformanceMatcher nameMatcher = new HighPerformanceMatcher(3);
|
final HighPerformanceMatcher nameMatcher = new HighPerformanceMatcher(3);
|
||||||
|
@ -200,6 +200,11 @@ public class ReleaseInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FileFilter getClutterFileFilter() {
|
||||||
|
return new FileFolderNameFilter(compile(getBundle(getClass().getName()).getString("pattern.file.ignore")));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// fetch release group names online and try to update the data every other day
|
// fetch release group names online and try to update the data every other day
|
||||||
protected final CachedResource<String[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
|
protected final CachedResource<String[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
|
||||||
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist"));
|
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist"));
|
||||||
@ -283,6 +288,23 @@ public class ReleaseInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class FileFolderNameFilter implements FileFilter {
|
||||||
|
|
||||||
|
private final Pattern namePattern;
|
||||||
|
|
||||||
|
|
||||||
|
public FileFolderNameFilter(Pattern namePattern) {
|
||||||
|
this.namePattern = namePattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return (namePattern.matcher(file.getName()).find() || (file.isFile() && namePattern.matcher(file.getParentFile().getName()).find()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Collection<String> quoteAll(Collection<String> strings) {
|
private Collection<String> quoteAll(Collection<String> strings) {
|
||||||
List<String> patterns = new ArrayList<String>(strings.size());
|
List<String> patterns = new ArrayList<String>(strings.size());
|
||||||
for (String it : strings) {
|
for (String it : strings) {
|
||||||
|
@ -16,3 +16,4 @@ url.series-list: http://filebot.sourceforge.net/data/series.list.gz
|
|||||||
|
|
||||||
# disk folder matcher
|
# disk folder matcher
|
||||||
pattern.diskfolder.entry: ^BDMV$|^HVDVD_TS$|^VIDEO_TS$|^AUDIO_TS$|^VCD$
|
pattern.diskfolder.entry: ^BDMV$|^HVDVD_TS$|^VIDEO_TS$|^AUDIO_TS$|^VCD$
|
||||||
|
pattern.file.ignore: (?<!\\p{Alnum})(?i:sample|trailer|extras|deleted.scenes)(?!\\p{Alnum})
|
||||||
|
@ -25,6 +25,7 @@ import java.util.Map.Entry;
|
|||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -40,7 +41,7 @@ import javax.swing.SwingUtilities;
|
|||||||
|
|
||||||
import net.sourceforge.filebot.Analytics;
|
import net.sourceforge.filebot.Analytics;
|
||||||
import net.sourceforge.filebot.MediaTypes;
|
import net.sourceforge.filebot.MediaTypes;
|
||||||
import net.sourceforge.filebot.media.ReleaseInfo;
|
import net.sourceforge.filebot.similarity.CommonSequenceMatcher;
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
|
||||||
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
import net.sourceforge.filebot.similarity.SimilarityMetric;
|
||||||
@ -64,11 +65,14 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Match<File, ?>> match(final List<File> files, final SortOrder sortOrder, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
|
public List<Match<File, ?>> match(final List<File> files, final SortOrder sortOrder, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
|
||||||
// handle movie files
|
// ignore sample files
|
||||||
List<File> movieFiles = filter(files, VIDEO_FILES);
|
List<File> fileset = filter(files, NON_CLUTTER_FILES);
|
||||||
List<File> nfoFiles = filter(files, MediaTypes.getDefaultFilter("application/nfo"));
|
|
||||||
|
|
||||||
List<File> orphanedFiles = new ArrayList<File>(filter(files, FILES));
|
// handle movie files
|
||||||
|
List<File> movieFiles = filter(fileset, VIDEO_FILES);
|
||||||
|
List<File> nfoFiles = filter(fileset, MediaTypes.getDefaultFilter("application/nfo"));
|
||||||
|
|
||||||
|
List<File> orphanedFiles = new ArrayList<File>(filter(fileset, FILES));
|
||||||
orphanedFiles.removeAll(movieFiles);
|
orphanedFiles.removeAll(movieFiles);
|
||||||
orphanedFiles.removeAll(nfoFiles);
|
orphanedFiles.removeAll(nfoFiles);
|
||||||
|
|
||||||
@ -122,12 +126,16 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
List<File> movieMatchFiles = new ArrayList<File>();
|
List<File> movieMatchFiles = new ArrayList<File>();
|
||||||
movieMatchFiles.addAll(movieFiles);
|
movieMatchFiles.addAll(movieFiles);
|
||||||
movieMatchFiles.addAll(nfoFiles);
|
movieMatchFiles.addAll(nfoFiles);
|
||||||
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
|
movieMatchFiles.addAll(filter(files, DISK_FOLDERS));
|
||||||
movieMatchFiles.addAll(filter(orphanedFiles, SUBTITLE_FILES)); // run movie detection only on orphaned subtitle files
|
movieMatchFiles.addAll(filter(orphanedFiles, SUBTITLE_FILES)); // run movie detection only on orphaned subtitle files
|
||||||
|
|
||||||
// match remaining movies file by file in parallel
|
// match remaining movies file by file in parallel
|
||||||
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
|
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
|
||||||
|
|
||||||
|
// remember user decisions and only bother user once
|
||||||
|
final Map<String, Movie> selectionMemory = new TreeMap<String, Movie>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT));
|
||||||
|
final Map<String, String> inputMemory = new TreeMap<String, String>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT));
|
||||||
|
|
||||||
// map all files by movie
|
// map all files by movie
|
||||||
for (final File file : movieMatchFiles) {
|
for (final File file : movieMatchFiles) {
|
||||||
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
|
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
|
||||||
@ -136,7 +144,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
public Entry<File, Movie> call() throws Exception {
|
public Entry<File, Movie> call() throws Exception {
|
||||||
// unknown hash, try via imdb id from nfo file
|
// unknown hash, try via imdb id from nfo file
|
||||||
if (!movieByFile.containsKey(file) || !autodetect) {
|
if (!movieByFile.containsKey(file) || !autodetect) {
|
||||||
Movie result = grabMovieName(file, locale, autodetect, parent, movieByFile.get(file));
|
Movie result = grabMovieName(file, locale, autodetect, selectionMemory, inputMemory, parent, movieByFile.get(file));
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
Analytics.trackEvent(service.getName(), "SearchMovie", result.toString(), 1);
|
Analytics.trackEvent(service.getName(), "SearchMovie", result.toString(), 1);
|
||||||
}
|
}
|
||||||
@ -209,7 +217,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Component parent, Movie... suggestions) throws Exception {
|
protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Map<String, Movie> selectionMemory, Map<String, String> inputMemory, Component parent, Movie... suggestions) throws Exception {
|
||||||
Set<Movie> options = new LinkedHashSet<Movie>();
|
Set<Movie> options = new LinkedHashSet<Movie>();
|
||||||
|
|
||||||
// add default value if any
|
// add default value if any
|
||||||
@ -227,8 +235,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
String suggestion = options.isEmpty() ? stripReleaseInfo(getName(movieFile)) : options.iterator().next().getName();
|
String suggestion = options.isEmpty() ? stripReleaseInfo(getName(movieFile)) : options.iterator().next().getName();
|
||||||
|
|
||||||
String input = null;
|
String input = null;
|
||||||
synchronized (this) {
|
synchronized (inputMemory) {
|
||||||
input = showInputDialog("Enter movie name:", suggestion, String.format("%s / %s", movieFile.getParentFile().getName(), movieFile.getName()), parent);
|
input = inputMemory.get(suggestion);
|
||||||
|
if (input == null || suggestion == null || suggestion.isEmpty()) {
|
||||||
|
input = showInputDialog("Enter movie name:", suggestion, String.format("%s/%s", movieFile.getParentFile().getName(), movieFile.getName()), parent);
|
||||||
|
inputMemory.put(suggestion, input);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only care about results from manual input from here on out
|
// we only care about results from manual input from here on out
|
||||||
@ -239,17 +251,20 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return options.isEmpty() ? null : selectMovie(movieFile, options, parent);
|
return options.isEmpty() ? null : selectMovie(movieFile, options, selectionMemory, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Movie selectMovie(final File movieFile, final Collection<Movie> options, final Component parent) throws Exception {
|
protected Movie selectMovie(final File movieFile, final Collection<Movie> options, final Map<String, Movie> selectionMemory, final Component parent) throws Exception {
|
||||||
// clean file / folder names
|
// 1. movie by filename
|
||||||
final String fileQuery = stripReleaseInfo(getName(movieFile)).toLowerCase();
|
final String fileQuery = stripReleaseInfo(getName(movieFile));
|
||||||
final String folderQuery = stripReleaseInfo(getName(movieFile.getParentFile())).toLowerCase();
|
|
||||||
|
// 2. movie by directory
|
||||||
|
final File movieFolder = guessMovieFolder(movieFile);
|
||||||
|
final String folderQuery = (movieFolder == null) ? "" : stripReleaseInfo(movieFolder.getName());
|
||||||
|
|
||||||
// auto-ignore invalid files
|
// auto-ignore invalid files
|
||||||
if (fileQuery.length() < 2) {
|
if (fileQuery.length() < 2 && folderQuery.length() < 2) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +275,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
// auto-select perfect match
|
// auto-select perfect match
|
||||||
for (Movie movie : options) {
|
for (Movie movie : options) {
|
||||||
String movieIdentifier = normalizePunctuation(movie.toString()).toLowerCase();
|
String movieIdentifier = normalizePunctuation(movie.toString()).toLowerCase();
|
||||||
if (fileQuery.startsWith(movieIdentifier) || folderQuery.startsWith(movieIdentifier)) {
|
if (fileQuery.toLowerCase().startsWith(movieIdentifier) || folderQuery.toLowerCase().startsWith(movieIdentifier)) {
|
||||||
return movie;
|
return movie;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,8 +305,8 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
// multiple results have been found, user must select one
|
// multiple results have been found, user must select one
|
||||||
SelectDialog<Movie> selectDialog = new SelectDialog<Movie>(parent, options);
|
SelectDialog<Movie> selectDialog = new SelectDialog<Movie>(parent, options);
|
||||||
|
|
||||||
selectDialog.setTitle(String.format("%s / %s", movieFile.getParentFile().getName(), movieFile.getName()));
|
selectDialog.setTitle(String.format("%s / %s", folderQuery, fileQuery));
|
||||||
selectDialog.getHeaderLabel().setText(String.format("Movies matching '%s':", fileQuery));
|
selectDialog.getHeaderLabel().setText(String.format("Movies matching '%s':", fileQuery.length() >= 2 || folderQuery.length() <= 2 ? fileQuery : folderQuery));
|
||||||
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");
|
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");
|
||||||
selectDialog.pack();
|
selectDialog.pack();
|
||||||
|
|
||||||
@ -306,10 +321,17 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
|||||||
|
|
||||||
// allow only one select dialog at a time
|
// allow only one select dialog at a time
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
SwingUtilities.invokeAndWait(showSelectDialog);
|
synchronized (selectionMemory) {
|
||||||
|
if (selectionMemory.containsKey(fileQuery)) {
|
||||||
|
return selectionMemory.get(fileQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
SwingUtilities.invokeAndWait(showSelectDialog);
|
||||||
|
|
||||||
|
// cache selected value
|
||||||
|
selectionMemory.put(fileQuery, showSelectDialog.get());
|
||||||
|
return showSelectDialog.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// selected value or null
|
|
||||||
return showSelectDialog.get();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,6 +326,11 @@ public final class FileUtilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static FileFilter not(FileFilter filter) {
|
||||||
|
return new NotFileFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static List<File> flatten(Iterable<File> roots, int maxDepth, boolean listHiddenFiles) {
|
public static List<File> flatten(Iterable<File> roots, int maxDepth, boolean listHiddenFiles) {
|
||||||
List<File> files = new ArrayList<File>();
|
List<File> files = new ArrayList<File>();
|
||||||
|
|
||||||
@ -620,6 +625,23 @@ public final class FileUtilities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class NotFileFilter implements FileFilter {
|
||||||
|
|
||||||
|
public FileFilter filter;
|
||||||
|
|
||||||
|
|
||||||
|
public NotFileFilter(FileFilter filter) {
|
||||||
|
this.filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean accept(File file) {
|
||||||
|
return !filter.accept(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dummy constructor to prevent instantiation.
|
* Dummy constructor to prevent instantiation.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user