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
-->
<diskStore path="ehcache.disk.store.dir" />
<!--
Mandatory Default Cache configuration. These settings will be applied to caches
created pragmatically using CacheManager.add(String cacheName)
@ -17,7 +17,7 @@
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"
/>
<!--
Short-lived (24 hours) persistent disk cache for web responses
-->
@ -31,7 +31,7 @@
diskPersistent="true"
memoryStoreEvictionPolicy="LRU"
/>
<!--
Long-lived (2 weeks) persistent disk cache for web responses
-->
@ -45,7 +45,7 @@
diskPersistent="true"
memoryStoreEvictionPolicy="LRU"
/>
<!--
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"
memoryStoreEvictionPolicy="LRU"
/>
<!--
Very long-lived cache (4 months) anime/series lists, movie index, etc
-->
@ -73,11 +73,25 @@
diskPersistent="true"
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.
-->
<cache name="ephemeral"
<cache name="ephemeral-memory"
maxElementsInMemory="50000"
eternal="false"
timeToIdleSeconds="7200"
@ -86,5 +100,5 @@
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>

View File

@ -10,7 +10,8 @@ import net.sf.ehcache.Element;
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) {
return new Cache(CacheManager.getInstance().getCache(name));

View File

@ -1,5 +1,6 @@
package net.filebot.ui;
import static java.awt.Cursor.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.Component;
@ -13,6 +14,7 @@ import java.util.Collection;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
@ -30,12 +32,17 @@ import net.miginfocom.swing.MigLayout;
public class SelectDialog<T> extends JDialog {
private JLabel headerLabel = new JLabel();
private JCheckBox autoRepeatCheckBox = new JCheckBox();
private JList list;
private Action selectedAction = null;
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);
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(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
setMinimumSize(new Dimension(220, 240));
setSize(new Dimension(240, 260));
@ -86,6 +103,10 @@ public class SelectDialog<T> extends JDialog {
return headerLabel;
}
public JCheckBox getAutoRepeatCheckBox() {
return autoRepeatCheckBox;
}
public Action getSelectedAction() {
return selectedAction;
}

View File

@ -38,6 +38,7 @@ import java.util.concurrent.RunnableFuture;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import net.filebot.Cache;
import net.filebot.Settings;
import net.filebot.similarity.CommonSequenceMatcher;
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
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) {
this.provider = provider;
this.useSeriesIndex = useSeriesIndex;
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) {
return searchResults.get(0);
}
@ -83,7 +87,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
@Override
public SearchResult call() throws Exception {
// 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.getCancelAction().putValue(Action.NAME, "Ignore");
@ -107,6 +111,11 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
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
return selectDialog.getSelectedValue();
}
@ -119,16 +128,26 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
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
selectionMemory.put(query, showSelectDialog.get());
return showSelectDialog.get();
// ask user
SwingUtilities.invokeAndWait(showSelectDialog);
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>>>();
// detect series names and create episode list fetch tasks
@ -141,7 +160,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// select search result
if (results.size() > 0) {
SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, parent);
SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, autodetection, parent);
if (selectedSearchResult != null) {
return provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
@ -278,7 +297,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
if (queries != null && queries.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results
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) {
// only allow one fetch session at a time so later requests can make use of cached results
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, ?>>();
if (input.size() > 0) {
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) {
matches.add(new Match<File, Episode>(null, it));
}

View File

@ -404,21 +404,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
@Override
public Movie call() throws Exception {
// 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.getHeaderLabel().setText(String.format("Movies matching '%s':", fileQuery.length() >= 2 || folderQuery.length() <= 2 ? fileQuery : folderQuery));
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
Settings prefs = Settings.forPackage(MovieHashMatcher.class);
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()));
// 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");
}