* ask for user interaction once per unique query at most

This commit is contained in:
Reinhard Pointner 2012-03-19 02:17:29 +00:00
parent b10b6743f6
commit 25742ba566
1 changed files with 31 additions and 19 deletions

View File

@ -20,9 +20,11 @@ import java.util.LinkedHashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
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;
@ -35,6 +37,7 @@ import javax.swing.Action;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import net.sourceforge.filebot.Analytics; import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.similarity.CommonSequenceMatcher;
import net.sourceforge.filebot.similarity.EpisodeMatcher; import net.sourceforge.filebot.similarity.EpisodeMatcher;
import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.NameSimilarityMetric; import net.sourceforge.filebot.similarity.NameSimilarityMetric;
@ -60,7 +63,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, final Component parent) throws Exception { protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, Map<String, SearchResult> selectionMemory, final Component parent) throws Exception {
if (searchResults.size() == 1) { if (searchResults.size() == 1) {
return searchResults.get(0); return searchResults.get(0);
} }
@ -71,9 +74,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// use name similarity metric // use name similarity metric
SimilarityMetric metric = new NameSimilarityMetric(); SimilarityMetric metric = new NameSimilarityMetric();
// find probable matches using name similarity >= 0.9 // find probable matches using name similarity >= 0.85
for (SearchResult result : searchResults) { for (SearchResult result : searchResults) {
if (metric.getSimilarity(normalizeName(query), normalizeName(result.getName())) >= 0.9) { // remove trailing braces, e.g. Doctor Who (2005) -> Doctor Who
if (metric.getSimilarity(removeTrailingBrackets(query), removeTrailingBrackets(result.getName())) >= 0.85) {
probableMatches.add(result); probableMatches.add(result);
} }
} }
@ -107,21 +111,26 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// allow only one select dialog at a time // allow only one select dialog at a time
synchronized (this) { synchronized (this) {
synchronized (selectionMemory) {
SearchResult selection = selectionMemory.get(query);
if (selection != null) {
return selection;
}
SwingUtilities.invokeAndWait(showSelectDialog); SwingUtilities.invokeAndWait(showSelectDialog);
}
// selected value or null // cache selected value
return showSelectDialog.get(); selection = showSelectDialog.get();
if (selection != null) {
selectionMemory.put(query, selection);
}
return selection;
}
}
} }
private String normalizeName(String value) { protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Map<String, SearchResult> selectionMemory, final Component parent) throws Exception {
// remove trailing braces, e.g. Doctor Who (2005) -> doctor who
return removeTrailingBrackets(value).toLowerCase();
}
protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Component parent) throws Exception {
List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>(); List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>();
// detect series names and create episode list fetch tasks // detect series names and create episode list fetch tasks
@ -134,7 +143,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// select search result // select search result
if (results.size() > 0) { if (results.size() > 0) {
SearchResult selectedSearchResult = selectSearchResult(query, results, parent); SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, parent);
if (selectedSearchResult != null) { if (selectedSearchResult != null) {
List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, sortOrder, locale); List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
@ -177,6 +186,9 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// assume that many shows will be matched, do it folder by folder // assume that many shows will be matched, do it folder by folder
List<Callable<List<Match<File, ?>>>> taskPerFolder = new ArrayList<Callable<List<Match<File, ?>>>>(); List<Callable<List<Match<File, ?>>>> taskPerFolder = new ArrayList<Callable<List<Match<File, ?>>>>();
// remember user decisions and only bother user once
final Map<String, SearchResult> selectionMemory = new TreeMap<String, SearchResult>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT));
// detect series names and create episode list fetch tasks // detect series names and create episode list fetch tasks
for (Entry<Set<File>, Set<String>> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale).entrySet()) { for (Entry<Set<File>, Set<String>> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale).entrySet()) {
final List<List<File>> batchSets = new ArrayList<List<File>>(); final List<List<File>> batchSets = new ArrayList<List<File>>();
@ -195,7 +207,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override @Override
public List<Match<File, ?>> call() throws Exception { public List<Match<File, ?>> call() throws Exception {
return matchEpisodeSet(batchSet, queries, sortOrder, locale, autodetection, parent); return matchEpisodeSet(batchSet, queries, sortOrder, locale, autodetection, selectionMemory, parent);
} }
}); });
} }
@ -246,7 +258,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Collection<String> queries, SortOrder sortOrder, Locale locale, boolean autodetection, Component parent) throws Exception { public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Collection<String> queries, SortOrder sortOrder, Locale locale, boolean autodetection, Map<String, SearchResult> selectionMemory, Component parent) throws Exception {
Set<Episode> episodes = emptySet(); Set<Episode> episodes = emptySet();
// detect series name and fetch episode list // detect series name and fetch episode list
@ -254,7 +266,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
if (queries != null && queries.size() > 0) { if (queries != null && queries.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
synchronized (providerLock) { synchronized (providerLock) {
episodes = fetchEpisodeSet(queries, sortOrder, locale, parent); episodes = fetchEpisodeSet(queries, sortOrder, locale, selectionMemory, parent);
} }
} }
} }
@ -278,7 +290,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
if (input.size() > 0) { if (input.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
synchronized (providerLock) { synchronized (providerLock) {
episodes = fetchEpisodeSet(input, sortOrder, locale, parent); episodes = fetchEpisodeSet(input, sortOrder, locale, selectionMemory, parent);
} }
} }
} }