1
0
mirror of https://github.com/mitb-archive/filebot synced 2025-01-10 21:38:04 -05:00

Support Query->SearchResult persistent memory in Episode mode

This commit is contained in:
Reinhard Pointner 2016-03-04 22:52:15 +00:00
parent 6f7169e255
commit 74569a405f
5 changed files with 77 additions and 31 deletions

View File

@ -3,7 +3,7 @@
Persistent disk store location Persistent disk store location
--> -->
<diskStore path="ehcache.disk.store.dir" /> <diskStore path="ehcache.disk.store.dir" />
<!-- <!--
Mandatory Default Cache configuration. These settings will be applied to caches Mandatory Default Cache configuration. These settings will be applied to caches
created pragmatically using CacheManager.add(String cacheName) created pragmatically using CacheManager.add(String cacheName)
@ -17,7 +17,7 @@
diskPersistent="false" diskPersistent="false"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
<!-- <!--
Short-lived (24 hours) persistent disk cache for web responses Short-lived (24 hours) persistent disk cache for web responses
--> -->
@ -31,7 +31,7 @@
diskPersistent="true" diskPersistent="true"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
<!-- <!--
Long-lived (2 weeks) persistent disk cache for web responses Long-lived (2 weeks) persistent disk cache for web responses
--> -->
@ -45,7 +45,7 @@
diskPersistent="true" diskPersistent="true"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
<!-- <!--
Long-lived (2 months) persistent disk cache for web responses (that can be updated via If-Modified or If-None-Match) Long-lived (2 months) persistent disk cache for web responses (that can be updated via If-Modified or If-None-Match)
--> -->
@ -59,7 +59,7 @@
diskPersistent="true" diskPersistent="true"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
<!-- <!--
Very long-lived cache (4 months) anime/series lists, movie index, etc Very long-lived cache (4 months) anime/series lists, movie index, etc
--> -->
@ -73,11 +73,25 @@
diskPersistent="true" diskPersistent="true"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
<!--
Simple memory cache. Time to live is 4 months.
-->
<cache name="persistent-memory"
maxElementsInMemory="100"
maxElementsOnDisk="50000"
eternal="false"
timeToIdleSeconds="10512000"
timeToLiveSeconds="10512000"
overflowToDisk="true"
diskPersistent="true"
memoryStoreEvictionPolicy="LRU"
/>
<!-- <!--
Simple memory cache. Time to live is 2 hours. Simple memory cache. Time to live is 2 hours.
--> -->
<cache name="ephemeral" <cache name="ephemeral-memory"
maxElementsInMemory="50000" maxElementsInMemory="50000"
eternal="false" eternal="false"
timeToIdleSeconds="7200" timeToIdleSeconds="7200"
@ -86,5 +100,5 @@
diskPersistent="false" diskPersistent="false"
memoryStoreEvictionPolicy="LRU" memoryStoreEvictionPolicy="LRU"
/> />
</ehcache> </ehcache>

View File

@ -10,7 +10,8 @@ import net.sf.ehcache.Element;
public class Cache { public class Cache {
public static final String EPHEMERAL = "ephemeral"; public static final String EPHEMERAL = "ephemeral-memory";
public static final String PERSISTENT = "persistent-memory";
public static Cache getCache(String name) { public static Cache getCache(String name) {
return new Cache(CacheManager.getInstance().getCache(name)); return new Cache(CacheManager.getInstance().getCache(name));

View File

@ -1,5 +1,6 @@
package net.filebot.ui; package net.filebot.ui;
import static java.awt.Cursor.*;
import static net.filebot.util.ui.SwingUI.*; import static net.filebot.util.ui.SwingUI.*;
import java.awt.Component; import java.awt.Component;
@ -13,6 +14,7 @@ import java.util.Collection;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JLabel; import javax.swing.JLabel;
@ -30,12 +32,17 @@ import net.miginfocom.swing.MigLayout;
public class SelectDialog<T> extends JDialog { public class SelectDialog<T> extends JDialog {
private JLabel headerLabel = new JLabel(); private JLabel headerLabel = new JLabel();
private JCheckBox autoRepeatCheckBox = new JCheckBox();
private JList list; private JList list;
private Action selectedAction = null; private Action selectedAction = null;
public SelectDialog(Component parent, Collection<? extends T> options) { public SelectDialog(Component parent, Collection<? extends T> options) {
this(parent, options, false, false);
}
public SelectDialog(Component parent, Collection<? extends T> options, boolean autoRepeatEnabled, boolean autoRepeatSelected) {
super(getWindow(parent), "Select", ModalityType.DOCUMENT_MODAL); super(getWindow(parent), "Select", ModalityType.DOCUMENT_MODAL);
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
@ -70,6 +77,16 @@ public class SelectDialog<T> extends JDialog {
c.add(new JButton(selectAction), "align center, id select"); c.add(new JButton(selectAction), "align center, id select");
c.add(new JButton(cancelAction), "gap unrel, id cancel"); c.add(new JButton(cancelAction), "gap unrel, id cancel");
// add repeat button
if (autoRepeatEnabled) {
autoRepeatCheckBox.setSelected(autoRepeatSelected);
autoRepeatCheckBox.setToolTipText("Remember");
autoRepeatCheckBox.setCursor(getPredefinedCursor(HAND_CURSOR));
autoRepeatCheckBox.setIcon(ResourceManager.getIcon("button.repeat"));
autoRepeatCheckBox.setSelectedIcon(ResourceManager.getIcon("button.repeat.selected"));
c.add(autoRepeatCheckBox, "pos 1al select.y n select.y2");
}
// set default size and location // set default size and location
setMinimumSize(new Dimension(220, 240)); setMinimumSize(new Dimension(220, 240));
setSize(new Dimension(240, 260)); setSize(new Dimension(240, 260));
@ -86,6 +103,10 @@ public class SelectDialog<T> extends JDialog {
return headerLabel; return headerLabel;
} }
public JCheckBox getAutoRepeatCheckBox() {
return autoRepeatCheckBox;
}
public Action getSelectedAction() { public Action getSelectedAction() {
return selectedAction; return selectedAction;
} }

View File

@ -38,6 +38,7 @@ import java.util.concurrent.RunnableFuture;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import net.filebot.Cache;
import net.filebot.Settings; import net.filebot.Settings;
import net.filebot.similarity.CommonSequenceMatcher; import net.filebot.similarity.CommonSequenceMatcher;
import net.filebot.similarity.EpisodeMatcher; import net.filebot.similarity.EpisodeMatcher;
@ -58,13 +59,16 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// 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
private final Object providerLock = new Object(); private final Object providerLock = new Object();
// remember user selections
private final Cache persistentSelectionMemory = Cache.getCache(Cache.PERSISTENT);
public EpisodeListMatcher(EpisodeListProvider provider, boolean useSeriesIndex, boolean useAnimeIndex) { public EpisodeListMatcher(EpisodeListProvider provider, boolean useSeriesIndex, boolean useAnimeIndex) {
this.provider = provider; this.provider = provider;
this.useSeriesIndex = useSeriesIndex; this.useSeriesIndex = useSeriesIndex;
this.useAnimeIndex = useAnimeIndex; this.useAnimeIndex = useAnimeIndex;
} }
protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, Map<String, SearchResult> selectionMemory, final Component parent) throws Exception { protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, Map<String, SearchResult> selectionMemory, boolean autodetection, final Component parent) throws Exception {
if (searchResults.size() == 1) { if (searchResults.size() == 1) {
return searchResults.get(0); return searchResults.get(0);
} }
@ -83,7 +87,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override @Override
public SearchResult call() throws Exception { public SearchResult call() throws Exception {
// multiple results have been found, user must select one // multiple results have been found, user must select one
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, searchResults); SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, searchResults, true, false);
selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query)); selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query));
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");
@ -107,6 +111,11 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
throw new CancellationException("Cancelled by user"); throw new CancellationException("Cancelled by user");
} }
// remember if we should auto-repeat the chosen action in the future
if (selectDialog.getAutoRepeatCheckBox().isSelected() && selectDialog.getSelectedValue() != null) {
persistentSelectionMemory.put(query, selectDialog.getSelectedValue());
}
// selected value or null if the dialog was canceled by the user // selected value or null if the dialog was canceled by the user
return selectDialog.getSelectedValue(); return selectDialog.getSelectedValue();
} }
@ -119,16 +128,26 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
return selectionMemory.get(query); return selectionMemory.get(query);
} }
SwingUtilities.invokeAndWait(showSelectDialog); // check persistent memory
if (autodetection) {
SearchResult persistentSelection = persistentSelectionMemory.get(query, SearchResult.class);
if (persistentSelection != null) {
return persistentSelection;
}
}
// cache selected value // ask user
selectionMemory.put(query, showSelectDialog.get()); SwingUtilities.invokeAndWait(showSelectDialog);
return showSelectDialog.get(); SearchResult userSelection = showSelectDialog.get();
// remember selected value
selectionMemory.put(query, userSelection);
return userSelection;
} }
} }
} }
protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Map<String, SearchResult> selectionMemory, final Component parent) throws Exception { protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Map<String, SearchResult> selectionMemory, final boolean autodetection, 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
@ -141,7 +160,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// select search result // select search result
if (results.size() > 0) { if (results.size() > 0) {
SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, parent); SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, autodetection, parent);
if (selectedSearchResult != null) { if (selectedSearchResult != null) {
return provider.getEpisodeList(selectedSearchResult, sortOrder, locale); return provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
@ -278,7 +297,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, selectionMemory, parent); episodes = fetchEpisodeSet(queries, sortOrder, locale, selectionMemory, autodetection, parent);
} }
} }
} }
@ -301,7 +320,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
if (input != null && input.size() > 0) { if (input != null && 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, new HashMap<String, SearchResult>(), parent); episodes = fetchEpisodeSet(input, sortOrder, locale, new HashMap<String, SearchResult>(), false, parent);
} }
} }
} }
@ -374,7 +393,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>(); List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
if (input.size() > 0) { if (input.size() > 0) {
synchronized (providerLock) { synchronized (providerLock) {
Set<Episode> episodes = fetchEpisodeSet(input, sortOrder, locale, new HashMap<String, SearchResult>(), parent); Set<Episode> episodes = fetchEpisodeSet(input, sortOrder, locale, new HashMap<String, SearchResult>(), false, parent);
for (Episode it : episodes) { for (Episode it : episodes) {
matches.add(new Match<File, Episode>(null, it)); matches.add(new Match<File, Episode>(null, it));
} }

View File

@ -404,21 +404,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
@Override @Override
public Movie call() throws Exception { public Movie call() throws Exception {
// 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, true, false);
selectDialog.setTitle(folderQuery.isEmpty() ? fileQuery : String.format("%s / %s", folderQuery, fileQuery)); selectDialog.setTitle(folderQuery.isEmpty() ? fileQuery : String.format("%s / %s", folderQuery, fileQuery));
selectDialog.getHeaderLabel().setText(String.format("Movies matching '%s':", fileQuery.length() >= 2 || folderQuery.length() <= 2 ? fileQuery : folderQuery)); 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");
// add repeat button
JCheckBox checkBox = new JCheckBox();
checkBox.setToolTipText("Select / Ignore for all");
checkBox.setCursor(getPredefinedCursor(HAND_CURSOR));
checkBox.setIcon(ResourceManager.getIcon("button.repeat"));
checkBox.setSelectedIcon(ResourceManager.getIcon("button.repeat.selected"));
JComponent c = (JComponent) selectDialog.getContentPane();
c.add(checkBox, "pos 1al select.y n select.y2");
// restore original dialog size // restore original dialog size
Settings prefs = Settings.forPackage(MovieHashMatcher.class); Settings prefs = Settings.forPackage(MovieHashMatcher.class);
int w = Integer.parseInt(prefs.get("dialog.select.w", "280")); int w = Integer.parseInt(prefs.get("dialog.select.w", "280"));
@ -435,7 +426,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
prefs.put("dialog.select.h", Integer.toString(selectDialog.getHeight())); prefs.put("dialog.select.h", Integer.toString(selectDialog.getHeight()));
// remember if we should auto-repeat the chosen action in the future // remember if we should auto-repeat the chosen action in the future
if (checkBox.isSelected() || selectDialog.getSelectedAction() == null) { if (selectDialog.getAutoRepeatCheckBox().isSelected() || selectDialog.getSelectedAction() == null) {
memory.put("repeat", selectDialog.getSelectedValue() != null ? "select" : "ignore"); memory.put("repeat", selectDialog.getSelectedValue() != null ? "select" : "ignore");
} }