Bugfixes, optimizations, improved user-interaction behaviour
This commit is contained in:
parent
397fb14be7
commit
612a243518
|
@ -89,7 +89,7 @@ public class AutoDetection {
|
|||
}
|
||||
|
||||
public boolean isAnime(File f) {
|
||||
if (MediaDetection.parseEpisodeNumber(f.getName(), false).isEmpty()) {
|
||||
if (MediaDetection.parseEpisodeNumber(f.getName(), false) == null) {
|
||||
return false;
|
||||
}
|
||||
if (anyMatch(f.getParentFile(), ANIME_PATTERN) || find(f.getName(), ANIME_EPISODE_PATTERN) || find(f.getName(), EMBEDDED_CHECKSUM)) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import java.awt.Component;
|
|||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -55,6 +54,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
private EpisodeListProvider provider;
|
||||
private boolean anime;
|
||||
|
||||
// remember user decisions
|
||||
private Map<String, SearchResult> selectionMemory = new TreeMap<String, SearchResult>(getLenientCollator(Locale.ENGLISH));
|
||||
private Map<String, List<String>> inputMemory = new TreeMap<String, List<String>>(getLenientCollator(Locale.ENGLISH));
|
||||
|
||||
public EpisodeListMatcher(EpisodeListProvider provider, boolean anime) {
|
||||
this.provider = provider;
|
||||
this.anime = anime;
|
||||
|
@ -64,7 +67,159 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
return Cache.getCache("selection_" + provider.getName(), CacheType.Persistent).cast(SearchResult.class);
|
||||
}
|
||||
|
||||
protected SearchResult selectSearchResult(List<File> files, String query, List<SearchResult> options, Map<String, SearchResult> selectionMemory, boolean autodetection, Component parent) throws Exception {
|
||||
@Override
|
||||
public List<Match<File, ?>> match(Collection<File> files, boolean strict, SortOrder sortOrder, Locale locale, boolean autodetection, Component parent) throws Exception {
|
||||
if (files.isEmpty()) {
|
||||
return justFetchEpisodeList(sortOrder, locale, parent);
|
||||
}
|
||||
|
||||
// ignore sample files
|
||||
List<File> fileset = autodetection ? filter(files, not(getClutterFileFilter())) : new ArrayList<File>(files);
|
||||
|
||||
// focus on movie and subtitle files
|
||||
List<File> mediaFiles = filter(fileset, VIDEO_FILES, SUBTITLE_FILES);
|
||||
|
||||
// merge episode matches
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
ExecutorService workerThreadPool = Executors.newFixedThreadPool(getPreferredThreadPoolSize());
|
||||
try {
|
||||
// detect series names and create episode list fetch tasks
|
||||
List<Future<List<Match<File, ?>>>> tasks = new ArrayList<Future<List<Match<File, ?>>>>();
|
||||
|
||||
if (strict) {
|
||||
// in strict mode simply process file-by-file (ignoring all files that don't contain clear SxE patterns)
|
||||
mediaFiles.stream().filter(f -> isEpisode(f, false)).map(f -> {
|
||||
return workerThreadPool.submit(() -> {
|
||||
return matchEpisodeSet(singletonList(f), detectSeriesNames(singleton(f), anime, locale), sortOrder, strict, locale, autodetection, parent);
|
||||
});
|
||||
}).forEach(tasks::add);
|
||||
} else {
|
||||
// in non-strict mode use the complicated (more powerful but also more error prone) match-batch-by-batch logic
|
||||
mapSeriesNamesByFiles(mediaFiles, locale, anime).forEach((f, n) -> {
|
||||
// 1. handle series name batch set all at once -> only 1 batch set
|
||||
// 2. files don't seem to belong to any series -> handle folder per folder -> multiple batch sets
|
||||
Collection<List<File>> batches = n != null && n.size() > 0 ? singleton(new ArrayList<File>(f)) : mapByFolder(f).values();
|
||||
|
||||
batches.stream().map(b -> {
|
||||
return workerThreadPool.submit(() -> {
|
||||
return matchEpisodeSet(b, n, sortOrder, strict, locale, autodetection, parent);
|
||||
});
|
||||
}).forEach(tasks::add);
|
||||
});
|
||||
}
|
||||
|
||||
for (Future<List<Match<File, ?>>> future : tasks) {
|
||||
// make sure each episode has unique object data
|
||||
for (Match<File, ?> it : future.get()) {
|
||||
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
workerThreadPool.shutdownNow();
|
||||
}
|
||||
|
||||
// handle derived files
|
||||
List<Match<File, ?>> derivateMatches = new ArrayList<Match<File, ?>>();
|
||||
Set<File> derivateFiles = new TreeSet<File>(fileset);
|
||||
derivateFiles.removeAll(mediaFiles);
|
||||
|
||||
for (File file : derivateFiles) {
|
||||
for (Match<File, ?> match : matches) {
|
||||
if (file.getPath().startsWith(match.getValue().getParentFile().getPath()) && isDerived(file, match.getValue()) && match.getCandidate() instanceof Episode) {
|
||||
derivateMatches.add(new Match<File, Object>(file, ((Episode) match.getCandidate()).clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add matches from other files that are linked via filenames
|
||||
matches.addAll(derivateMatches);
|
||||
|
||||
// restore original order
|
||||
matches.sort(comparing(Match::getValue, new OriginalOrder<File>(files)));
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public List<Match<File, ?>> matchEpisodeSet(List<File> files, Collection<String> queries, SortOrder sortOrder, boolean strict, Locale locale, boolean autodetection, Component parent) throws Exception {
|
||||
Collection<Episode> episodes = emptySet();
|
||||
|
||||
// detect series name and fetch episode list
|
||||
if (autodetection) {
|
||||
if (queries != null && queries.size() > 0) {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
episodes = fetchEpisodeSet(files, queries, sortOrder, locale, autodetection, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// require user input if auto-detection has failed or has been disabled
|
||||
if (episodes.isEmpty() && !strict) {
|
||||
List<String> detectedSeriesNames = detectSeriesNames(files, anime, locale);
|
||||
String suggestion = detectedSeriesNames.size() > 0 ? join(detectedSeriesNames, "; ") : normalizePunctuation(getName(files.get(0)));
|
||||
|
||||
synchronized (inputMemory) {
|
||||
List<String> input = inputMemory.get(suggestion);
|
||||
if (input == null || suggestion == null || suggestion.isEmpty()) {
|
||||
synchronized (parent) {
|
||||
input = showMultiValueInputDialog(getQueryInputMessage("Please identify the following files:", "Enter series name:", files), suggestion, provider.getName(), parent);
|
||||
}
|
||||
inputMemory.put(suggestion, input);
|
||||
}
|
||||
|
||||
if (input != null && input.size() > 0) {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
episodes = fetchEpisodeSet(files, input, sortOrder, locale, false, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find file/episode matches
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
// group by subtitles first and then by files in general
|
||||
if (episodes.size() > 0) {
|
||||
for (List<File> filesPerType : mapByMediaExtension(files).values()) {
|
||||
EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, strict);
|
||||
for (Match<File, Object> it : matcher.match()) {
|
||||
// in strict mode sanity check the result and only pass back good matches
|
||||
if (!strict || isEpisodeNumberMatch(it.getValue(), (Episode) it.getCandidate())) {
|
||||
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
protected Set<Episode> fetchEpisodeSet(List<File> files, Collection<String> querySet, SortOrder sortOrder, Locale locale, boolean autodetection, Component parent) throws Exception {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
// detect series names and fetch episode lists in parallel
|
||||
List<Future<List<Episode>>> tasks = querySet.stream().map(q -> {
|
||||
return requestThreadPool.submit(() -> {
|
||||
// select search result
|
||||
List<SearchResult> options = provider.search(q, locale);
|
||||
|
||||
if (options.size() > 0) {
|
||||
SearchResult selectedSearchResult = selectSearchResult(files, q, options, autodetection, parent);
|
||||
if (selectedSearchResult != null) {
|
||||
return provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
|
||||
}
|
||||
}
|
||||
return (List<Episode>) EMPTY_LIST;
|
||||
});
|
||||
}).collect(toList());
|
||||
|
||||
// merge all episodes
|
||||
Set<Episode> episodes = new LinkedHashSet<Episode>();
|
||||
for (Future<List<Episode>> it : tasks) {
|
||||
episodes.addAll(it.get());
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
|
||||
protected SearchResult selectSearchResult(List<File> files, String query, List<SearchResult> options, boolean autodetection, Component parent) throws Exception {
|
||||
if (options.size() == 1) {
|
||||
return options.get(0);
|
||||
}
|
||||
|
@ -85,7 +240,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
// multiple results have been found, user must select one
|
||||
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, options, true, false, header);
|
||||
selectDialog.setTitle(provider.getName());
|
||||
selectDialog.getMessageLabel().setText("<html>Select best match for \"<b>" + query + "</b>\":</html>");
|
||||
selectDialog.getMessageLabel().setText("<html>Select best match for \"<b>" + escapeHTML(query) + "</b>\":</html>");
|
||||
selectDialog.getCancelAction().putValue(Action.NAME, "Skip");
|
||||
selectDialog.pack();
|
||||
|
||||
|
@ -135,162 +290,6 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
}
|
||||
}
|
||||
|
||||
protected Set<Episode> fetchEpisodeSet(List<File> files, Collection<String> querySet, SortOrder sortOrder, Locale locale, Map<String, SearchResult> selectionMemory, boolean autodetection, Component parent) throws Exception {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
synchronized (this) {
|
||||
// detect series names and fetch episode lists in parallel
|
||||
List<Future<List<Episode>>> tasks = querySet.stream().map(q -> {
|
||||
return requestThreadPool.submit(() -> {
|
||||
// select search result
|
||||
List<SearchResult> options = provider.search(q, locale);
|
||||
|
||||
if (options.size() > 0) {
|
||||
SearchResult selectedSearchResult = selectSearchResult(files, q, options, selectionMemory, autodetection, parent);
|
||||
if (selectedSearchResult != null) {
|
||||
return provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
|
||||
}
|
||||
}
|
||||
return (List<Episode>) EMPTY_LIST;
|
||||
});
|
||||
}).collect(toList());
|
||||
|
||||
// merge all episodes
|
||||
Set<Episode> episodes = new LinkedHashSet<Episode>();
|
||||
for (Future<List<Episode>> it : tasks) {
|
||||
episodes.addAll(it.get());
|
||||
}
|
||||
return episodes;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Match<File, ?>> match(Collection<File> files, boolean strict, SortOrder sortOrder, Locale locale, boolean autodetection, Component parent) throws Exception {
|
||||
if (files.isEmpty()) {
|
||||
return justFetchEpisodeList(sortOrder, locale, parent);
|
||||
}
|
||||
|
||||
// ignore sample files
|
||||
List<File> fileset = autodetection ? filter(files, not(getClutterFileFilter())) : new ArrayList<File>(files);
|
||||
|
||||
// focus on movie and subtitle files
|
||||
List<File> mediaFiles = filter(fileset, VIDEO_FILES, SUBTITLE_FILES);
|
||||
|
||||
// remember user decisions and only bother user once
|
||||
Map<String, SearchResult> selectionMemory = new TreeMap<String, SearchResult>(getLenientCollator(Locale.ENGLISH));
|
||||
Map<String, List<String>> inputMemory = new TreeMap<String, List<String>>(getLenientCollator(Locale.ENGLISH));
|
||||
|
||||
// merge episode matches
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
ExecutorService workerThreadPool = Executors.newFixedThreadPool(getPreferredThreadPoolSize());
|
||||
try {
|
||||
// detect series names and create episode list fetch tasks
|
||||
List<Future<List<Match<File, ?>>>> tasks = new ArrayList<Future<List<Match<File, ?>>>>();
|
||||
|
||||
if (strict) {
|
||||
// in strict mode simply process file-by-file (ignoring all files that don't contain clear SxE patterns)
|
||||
mediaFiles.stream().filter(f -> isEpisode(f, false)).map(f -> {
|
||||
return workerThreadPool.submit(() -> {
|
||||
return matchEpisodeSet(singletonList(f), detectSeriesNames(singleton(f), anime, locale), sortOrder, strict, locale, autodetection, selectionMemory, inputMemory, parent);
|
||||
});
|
||||
}).forEach(tasks::add);
|
||||
} else {
|
||||
// in non-strict mode use the complicated (more powerful but also more error prone) match-batch-by-batch logic
|
||||
mapSeriesNamesByFiles(mediaFiles, locale, anime).forEach((f, n) -> {
|
||||
// 1. handle series name batch set all at once -> only 1 batch set
|
||||
// 2. files don't seem to belong to any series -> handle folder per folder -> multiple batch sets
|
||||
Collection<List<File>> batches = n != null && n.size() > 0 ? singleton(new ArrayList<File>(f)) : mapByFolder(f).values();
|
||||
|
||||
batches.stream().map(b -> {
|
||||
return workerThreadPool.submit(() -> {
|
||||
return matchEpisodeSet(b, n, sortOrder, strict, locale, autodetection, selectionMemory, inputMemory, parent);
|
||||
});
|
||||
}).forEach(tasks::add);
|
||||
});
|
||||
}
|
||||
|
||||
for (Future<List<Match<File, ?>>> future : tasks) {
|
||||
// make sure each episode has unique object data
|
||||
for (Match<File, ?> it : future.get()) {
|
||||
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
workerThreadPool.shutdownNow();
|
||||
}
|
||||
|
||||
// handle derived files
|
||||
List<Match<File, ?>> derivateMatches = new ArrayList<Match<File, ?>>();
|
||||
Set<File> derivateFiles = new TreeSet<File>(fileset);
|
||||
derivateFiles.removeAll(mediaFiles);
|
||||
|
||||
for (File file : derivateFiles) {
|
||||
for (Match<File, ?> match : matches) {
|
||||
if (file.getPath().startsWith(match.getValue().getParentFile().getPath()) && isDerived(file, match.getValue()) && match.getCandidate() instanceof Episode) {
|
||||
derivateMatches.add(new Match<File, Object>(file, ((Episode) match.getCandidate()).clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add matches from other files that are linked via filenames
|
||||
matches.addAll(derivateMatches);
|
||||
|
||||
// restore original order
|
||||
matches.sort(comparing(Match::getValue, new OriginalOrder<File>(files)));
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public List<Match<File, ?>> matchEpisodeSet(List<File> files, Collection<String> queries, SortOrder sortOrder, boolean strict, Locale locale, boolean autodetection, Map<String, SearchResult> selectionMemory, Map<String, List<String>> inputMemory, Component parent) throws Exception {
|
||||
Collection<Episode> episodes = emptySet();
|
||||
|
||||
// detect series name and fetch episode list
|
||||
if (autodetection) {
|
||||
if (queries != null && queries.size() > 0) {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
episodes = fetchEpisodeSet(files, queries, sortOrder, locale, selectionMemory, autodetection, parent);
|
||||
}
|
||||
}
|
||||
|
||||
// require user input if auto-detection has failed or has been disabled
|
||||
if (episodes.isEmpty() && !strict) {
|
||||
List<String> detectedSeriesNames = detectSeriesNames(files, anime, locale);
|
||||
String suggestion = detectedSeriesNames.size() > 0 ? join(detectedSeriesNames, "; ") : normalizePunctuation(getName(files.get(0)));
|
||||
|
||||
synchronized (inputMemory) {
|
||||
List<String> input = inputMemory.get(suggestion);
|
||||
if (input == null || suggestion == null || suggestion.isEmpty()) {
|
||||
input = showMultiValueInputDialog(getQueryInputMessage("Please identify the following files:", "Enter series name:", files), suggestion, provider.getName(), parent);
|
||||
inputMemory.put(suggestion, input);
|
||||
}
|
||||
|
||||
if (input != null && input.size() > 0) {
|
||||
// only allow one fetch session at a time so later requests can make use of cached results
|
||||
episodes = fetchEpisodeSet(files, input, sortOrder, locale, new HashMap<String, SearchResult>(), false, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find file/episode matches
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
// group by subtitles first and then by files in general
|
||||
if (episodes.size() > 0) {
|
||||
for (List<File> filesPerType : mapByMediaExtension(files).values()) {
|
||||
EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, strict);
|
||||
for (Match<File, Object> it : matcher.match()) {
|
||||
// in strict mode sanity check the result and only pass back good matches
|
||||
if (!strict || isEpisodeNumberMatch(it.getValue(), (Episode) it.getCandidate())) {
|
||||
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
protected Collection<File> getFilesForQuery(Collection<File> files, String query) {
|
||||
Pattern pattern = Pattern.compile(query.isEmpty() ? ".+" : normalizePunctuation(query).replaceAll("\\W+", ".+"), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
|
||||
List<File> selection = files.stream().filter(f -> find(f.getPath(), pattern)).collect(toList());
|
||||
|
@ -304,7 +303,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
html.append("<html>");
|
||||
if (selection.size() > 0) {
|
||||
if (header != null) {
|
||||
html.append(header).append("<br>");
|
||||
html.append(escapeHTML(header)).append("<br>");
|
||||
}
|
||||
for (File file : sortByUniquePath(selection)) {
|
||||
html.append("<nobr>");
|
||||
|
@ -325,7 +324,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
html.append("<br>");
|
||||
}
|
||||
if (message != null) {
|
||||
html.append(message);
|
||||
html.append(escapeHTML(message));
|
||||
}
|
||||
html.append("</html>");
|
||||
return html.toString();
|
||||
|
@ -337,7 +336,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
|||
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
if (input.size() > 0) {
|
||||
Collection<Episode> episodes = fetchEpisodeSet(emptyList(), input, sortOrder, locale, new HashMap<String, SearchResult>(), false, parent);
|
||||
Collection<Episode> episodes = fetchEpisodeSet(emptyList(), input, sortOrder, locale, false, parent);
|
||||
for (Episode it : episodes) {
|
||||
matches.add(new Match<File, Episode>(null, it));
|
||||
}
|
||||
|
|
|
@ -54,7 +54,12 @@ import net.filebot.web.SortOrder;
|
|||
|
||||
class MovieMatcher implements AutoCompleteMatcher {
|
||||
|
||||
private final MovieIdentificationService service;
|
||||
private MovieIdentificationService service;
|
||||
|
||||
// remember user decisions and only bother user once
|
||||
private Set<AutoSelection> autoSelectionMode = EnumSet.noneOf(AutoSelection.class);
|
||||
private Map<String, Movie> selectionMemory = new TreeMap<String, Movie>(getLenientCollator(Locale.ENGLISH));
|
||||
private Map<String, String> inputMemory = new TreeMap<String, String>(getLenientCollator(Locale.ENGLISH));
|
||||
|
||||
public MovieMatcher(MovieIdentificationService service) {
|
||||
this.service = service;
|
||||
|
@ -170,15 +175,10 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
});
|
||||
}).collect(toList());
|
||||
|
||||
// remember user decisions and only bother user once
|
||||
Set<AutoSelection> selectionMode = EnumSet.noneOf(AutoSelection.class);
|
||||
Map<String, Movie> selectionMemory = new TreeMap<String, Movie>(getLenientCollator(locale));
|
||||
Map<String, String> inputMemory = new TreeMap<String, String>(getLenientCollator(locale));
|
||||
|
||||
for (Future<Map<File, List<Movie>>> future : tasks) {
|
||||
for (Entry<File, List<Movie>> it : future.get().entrySet()) {
|
||||
// auto-select movie or ask user
|
||||
Movie movie = grabMovieName(it.getKey(), it.getValue(), strict, locale, autodetect, selectionMode, selectionMemory, inputMemory, parent);
|
||||
Movie movie = grabMovieName(it.getKey(), it.getValue(), strict, locale, autodetect, parent);
|
||||
|
||||
// make sure to use language-specific movie object
|
||||
if (movie != null) {
|
||||
|
@ -222,7 +222,7 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
return matches;
|
||||
}
|
||||
|
||||
protected Movie grabMovieName(File movieFile, Collection<Movie> options, boolean strict, Locale locale, boolean autodetect, Set<AutoSelection> autoSelectionMode, Map<String, Movie> selectionMemory, Map<String, String> inputMemory, Component parent) throws Exception {
|
||||
protected Movie grabMovieName(File movieFile, Collection<Movie> options, boolean strict, Locale locale, boolean autodetect, Component parent) throws Exception {
|
||||
// allow manual user input
|
||||
synchronized (selectionMemory) {
|
||||
if (!strict && (!autodetect || options.isEmpty()) && !(autodetect && autoSelectionMode.size() > 0)) {
|
||||
|
@ -231,27 +231,29 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
|
||||
if (input == null || suggestion == null || suggestion.isEmpty()) {
|
||||
File movieFolder = guessMovieFolder(movieFile);
|
||||
input = showInputDialog(getQueryInputMessage("Please identify the following files:", "Enter movie name:", movieFile), suggestion != null && suggestion.length() > 0 ? suggestion : getName(movieFile), movieFolder == null ? movieFile.getName() : String.join(" / ", movieFolder.getName(), movieFile.getName()), parent);
|
||||
synchronized (parent) {
|
||||
input = showInputDialog(getQueryInputMessage("Please identify the following files:", "Enter movie name:", movieFile), suggestion != null && suggestion.length() > 0 ? suggestion : getName(movieFile), movieFolder == null ? movieFile.getName() : String.join(" / ", movieFolder.getName(), movieFile.getName()), parent);
|
||||
}
|
||||
inputMemory.put(suggestion, input);
|
||||
}
|
||||
|
||||
if (input != null && input.length() > 0) {
|
||||
options = service.searchMovie(input, locale);
|
||||
if (options.size() > 0) {
|
||||
return selectMovie(movieFile, strict, input, options, autoSelectionMode, selectionMemory, parent);
|
||||
return selectMovie(movieFile, strict, input, options, parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options.isEmpty() ? null : selectMovie(movieFile, strict, null, options, autoSelectionMode, selectionMemory, parent);
|
||||
return options.isEmpty() ? null : selectMovie(movieFile, strict, null, options, parent);
|
||||
}
|
||||
|
||||
protected String getQueryInputMessage(String header, String message, File file) throws Exception {
|
||||
StringBuilder html = new StringBuilder(512);
|
||||
html.append("<html>");
|
||||
if (header != null) {
|
||||
html.append(header).append("<br>");
|
||||
html.append(escapeHTML(header)).append("<br>");
|
||||
}
|
||||
|
||||
html.append("<nobr>");
|
||||
|
@ -268,7 +270,7 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
|
||||
html.append("<br>");
|
||||
if (message != null) {
|
||||
html.append(message);
|
||||
html.append(escapeHTML(message));
|
||||
}
|
||||
html.append("</html>");
|
||||
return html.toString();
|
||||
|
@ -288,7 +290,7 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
return name;
|
||||
}
|
||||
|
||||
protected Movie selectMovie(File movieFile, boolean strict, String userQuery, Collection<Movie> options, Set<AutoSelection> autoSelectionMode, Map<String, Movie> selectionMemory, Component parent) throws Exception {
|
||||
protected Movie selectMovie(File movieFile, boolean strict, String userQuery, Collection<Movie> options, Component parent) throws Exception {
|
||||
// just auto-pick singleton results
|
||||
if (options.size() == 1) {
|
||||
return options.iterator().next();
|
||||
|
@ -356,7 +358,7 @@ class MovieMatcher implements AutoCompleteMatcher {
|
|||
SelectDialog<Movie> selectDialog = new SelectDialog<Movie>(parent, options, true, false, header);
|
||||
|
||||
selectDialog.setTitle(service.getName());
|
||||
selectDialog.getMessageLabel().setText("<html>Select best match for \"<b>" + query + "</b>\":</html>");
|
||||
selectDialog.getMessageLabel().setText("<html>Select best match for \"<b>" + escapeHTML(query) + "</b>\":</html>");
|
||||
selectDialog.getCancelAction().putValue(Action.NAME, "Skip");
|
||||
selectDialog.pack();
|
||||
|
||||
|
|
Loading…
Reference in New Issue