* remember recent formats in EpisodeFormatDialog

* display script exceptions if formatted name is empty
* better handling of empty search results in some page scrapes
* some test cases
* refactoring
This commit is contained in:
Reinhard Pointner 2009-07-18 22:06:32 +00:00
parent c4ce1aebe7
commit 78b77034b1
19 changed files with 261 additions and 140 deletions

View File

@ -2,8 +2,6 @@
package net.sourceforge.filebot;
import java.util.List;
import java.util.Map;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
@ -26,16 +24,18 @@ public final class Settings {
return "1.9";
};
private static final Settings userRoot = new Settings(Preferences.userNodeForPackage(Settings.class));
public static Settings userRoot() {
return userRoot;
}
private final Preferences prefs;
private Settings(Preferences prefs) {
this.prefs = prefs;
}
@ -83,22 +83,22 @@ public final class Settings {
}
public Map<String, String> asMap() {
public PreferencesMap<String> asMap() {
return PreferencesMap.map(prefs);
}
public <T> Map<String, T> asMap(Adapter<T> adapter) {
public <T> PreferencesMap<T> asMap(Adapter<T> adapter) {
return PreferencesMap.map(prefs, adapter);
}
public List<String> asList() {
public PreferencesList<String> asList() {
return PreferencesList.map(prefs);
}
public <T> List<T> asList(Adapter<T> adapter) {
public <T> PreferencesList<T> asList(Adapter<T> adapter) {
return PreferencesList.map(prefs, adapter);
}

View File

@ -5,6 +5,13 @@ importPackage(java.lang);
importPackage(java.util);
/**
* Convenience methods for String.toLowerCase() and String.toUpperCase().
*/
String.prototype.lower = String.prototype.toLowerCase;
String.prototype.upper = String.prototype.toUpperCase;
/**
* Pad strings or numbers with given characters ('0' by default).
*

View File

@ -117,6 +117,9 @@ public class ExpressionFormat extends Format {
ScriptContext context = new SimpleScriptContext();
context.setBindings(priviledgedBindings, ScriptContext.GLOBAL_SCOPE);
// reset exception state
lastException = null;
for (Object snipped : compilation) {
if (snipped instanceof CompiledScript) {
try {
@ -146,7 +149,7 @@ public class ExpressionFormat extends Format {
}
public ScriptException scriptException() {
public ScriptException caughtScriptException() {
return lastException;
}

View File

@ -13,10 +13,18 @@ import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeFormat;
class EpisodeExpressionFormatter extends ExpressionFormat implements MatchFormatter {
class EpisodeExpressionFormatter implements MatchFormatter {
public EpisodeExpressionFormatter(String expression) throws ScriptException {
super(expression);
private final ExpressionFormat format;
public EpisodeExpressionFormatter(ExpressionFormat format) {
this.format = format;
}
public ExpressionFormat getFormat() {
return format;
}
@ -34,11 +42,17 @@ class EpisodeExpressionFormatter extends ExpressionFormat implements MatchFormat
@Override
public String format(Match<?, ?> match) {
public synchronized String format(Match<?, ?> match) throws ScriptException {
Episode episode = (Episode) match.getValue();
File mediaFile = (File) match.getCandidate();
return format(new EpisodeFormatBindingBean(episode, mediaFile)).trim();
String result = format.format(new EpisodeFormatBindingBean(episode, mediaFile)).trim();
// if result is empty, check for script exceptions
if (result.isEmpty() && format.caughtScriptException() != null)
throw format.caughtScriptException();
return result;
}
}

View File

@ -1,8 +1,9 @@
package net.sourceforge.filebot.ui;
package net.sourceforge.filebot.ui.panel.rename;
import static java.awt.Font.*;
import static javax.swing.BorderFactory.*;
import java.awt.Color;
import java.awt.Font;
@ -16,8 +17,10 @@ import java.io.File;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
@ -34,16 +37,16 @@ import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.undo.UndoManager;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
@ -54,6 +57,7 @@ import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeFormat;
import net.sourceforge.tuned.DefaultThreadFactory;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.PreferencesList;
import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LazyDocumentListener;
import net.sourceforge.tuned.ui.LinkButton;
@ -67,6 +71,8 @@ public class EpisodeFormatDialog extends JDialog {
private Option selectedOption = Option.CANCEL;
private ExpressionFormat selectedFormat = null;
private JLabel preview = new JLabel();
private JLabel status = new JLabel();
@ -79,6 +85,8 @@ public class EpisodeFormatDialog extends JDialog {
private JTextField editor = new JTextField();
private PreferencesList<String> persistentFormatHistory = Settings.userRoot().node("rename/format.recent").asList();
private Color defaultColor = preview.getForeground();
private Color errorColor = Color.red;
@ -93,7 +101,6 @@ public class EpisodeFormatDialog extends JDialog {
public EpisodeFormatDialog(Window owner) {
super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL);
editor.setText(Settings.userRoot().get("dialog.format"));
editor.setFont(new Font(MONOSPACED, PLAIN, 14));
// bold title label in header
@ -132,10 +139,6 @@ public class EpisodeFormatDialog extends JDialog {
header.setComponentPopupMenu(createPreviewSamplePopup());
// setup undo support
final UndoManager undo = new UndoManager();
editor.getDocument().addUndoableEditListener(undo);
// enable undo/redo
TunedUtilities.installUndoSupport(editor);
@ -174,6 +177,12 @@ public class EpisodeFormatDialog extends JDialog {
}
});
// install editor suggestions popup
TunedUtilities.installAction(editor, KeyStroke.getKeyStroke("DOWN"), displayRecentFormatHistory);
// restore editor state
editor.setText(persistentFormatHistory.isEmpty() ? "" : persistentFormatHistory.get(0));
// update preview to current format
firePreviewSampleChanged();
@ -244,7 +253,7 @@ public class EpisodeFormatDialog extends JDialog {
private JPanel createSyntaxPanel() {
JPanel panel = new JPanel(new MigLayout("fill, nogrid"));
panel.setBorder(new LineBorder(new Color(0xACA899)));
panel.setBorder(createLineBorder(new Color(0xACA899)));
panel.setBackground(new Color(0xFFFFE1));
panel.setOpaque(true);
@ -257,7 +266,7 @@ public class EpisodeFormatDialog extends JDialog {
private JComponent createExamplesPanel() {
JPanel panel = new JPanel(new MigLayout("fill, wrap 3"));
panel.setBorder(new LineBorder(new Color(0xACA899)));
panel.setBorder(createLineBorder(new Color(0xACA899)));
panel.setBackground(new Color(0xFFFFE1));
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
@ -357,10 +366,10 @@ public class EpisodeFormatDialog extends JDialog {
threadGroup.stop();
// log access of potentially unsafe method
Logger.getLogger("global").warning("Thread was forcibly terminated");
Logger.getLogger(getClass().getName()).warning("Thread was forcibly terminated");
}
} catch (InterruptedException e) {
Logger.getLogger("global").log(Level.WARNING, "Thread was not terminated", e);
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Thread was not terminated", e);
}
return remaining;
@ -377,7 +386,7 @@ public class EpisodeFormatDialog extends JDialog {
private void checkFormatInBackground() {
try {
// check syntax in foreground
final ExpressionFormat format = new ExpressionFormat(getExpression());
final ExpressionFormat format = new ExpressionFormat(editor.getText().trim());
// format in background
final Timer progressIndicatorTimer = TunedUtilities.invokeLater(400, new Runnable() {
@ -402,8 +411,8 @@ public class EpisodeFormatDialog extends JDialog {
preview.setText(get());
// check internal script exception
if (format.scriptException() != null) {
throw format.scriptException();
if (format.caughtScriptException() != null) {
throw format.caughtScriptException();
}
// check empty output
@ -438,13 +447,13 @@ public class EpisodeFormatDialog extends JDialog {
}
public String getExpression() {
return editor.getText().trim();
public Option getSelectedOption() {
return selectedOption;
}
public Option getSelectedOption() {
return selectedOption;
public ExpressionFormat getSelectedFormat() {
return selectedFormat;
}
@ -459,6 +468,29 @@ public class EpisodeFormatDialog extends JDialog {
}
protected final Action displayRecentFormatHistory = new AbstractAction("Recent") {
@Override
public void actionPerformed(ActionEvent evt) {
JPopupMenu popup = new JPopupMenu();
for (final String expression : persistentFormatHistory) {
JMenuItem item = popup.add(new AbstractAction(expression) {
@Override
public void actionPerformed(ActionEvent evt) {
editor.setText(expression);
}
});
item.setFont(new Font(MONOSPACED, PLAIN, 11));
}
// display popup below format editor
popup.show(editor, 0, editor.getHeight() + 3);
}
};
protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
@Override
@ -480,17 +512,25 @@ public class EpisodeFormatDialog extends JDialog {
@Override
public void actionPerformed(ActionEvent evt) {
try {
if (progressIndicator.isVisible())
throw new IllegalStateException("Format has not been verified yet.");
// check syntax
ExpressionFormat format = new ExpressionFormat(getExpression());
selectedFormat = new ExpressionFormat(editor.getText().trim());
// remember format
Settings.userRoot().put("dialog.format", format.getExpression());
// create new recent history and ignore duplicates
Set<String> recent = new LinkedHashSet<String>();
// add new format first
recent.add(selectedFormat.getExpression());
// add next 4 most recent formats
for (int i = 0, limit = Math.min(4, persistentFormatHistory.size()); i < limit; i++) {
recent.add(persistentFormatHistory.get(i));
}
// update persistent history
persistentFormatHistory.set(recent);
finish(Option.APPROVE);
} catch (Exception e) {
} catch (ScriptException e) {
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e));
}
}

View File

@ -10,4 +10,4 @@ example[1]: {n} - {'S'+s.pad(2)}E{e.pad(2)} - {t}
example[2]: {n} - {s+'x'}{e.pad(2)}
# uglyfy name
example[3]: {n.space('.').toLowerCase()}.{s}{e.pad(2)}
example[3]: {n.space('.').lower()}.{s}{e.pad(2)}

View File

@ -15,16 +15,17 @@ final class HistorySpooler {
private static final HistorySpooler instance = new HistorySpooler();
public static HistorySpooler getInstance() {
return instance;
}
private final File file = new File("history.xml");
private final History sessionHistory = new History();
public synchronized History getCompleteHistory() {
History history = new History();
@ -33,7 +34,7 @@ final class HistorySpooler {
try {
history.addAll(importHistory(file).sequences());
} catch (IOException e) {
Logger.getLogger("global").log(Level.SEVERE, "Failed to load history", e);
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to load history", e);
}
}
@ -57,7 +58,7 @@ final class HistorySpooler {
// clear session history
sessionHistory.clear();
} catch (IOException e) {
Logger.getLogger("global").log(Level.SEVERE, "Failed to store history", e);
Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Failed to store history", e);
}
}

View File

@ -13,6 +13,6 @@ public interface MatchFormatter {
public String preview(Match<?, ?> match);
public String format(Match<?, ?> match);
public String format(Match<?, ?> match) throws Exception;
}

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot.ui;
package net.sourceforge.filebot.ui.panel.rename;
import java.awt.Component;

View File

@ -34,7 +34,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
private final Color noMatchGradientBeginColor = new Color(0xB7B7B7);
private final Color noMatchGradientEndColor = new Color(0x9A9A9A);
public RenameListCellRenderer(RenameModel renameModel) {
super(new Insets(4, 7, 4, 7));
@ -56,6 +56,16 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
typeRenderer.setVisible(false);
typeRenderer.setAlpha(1.0f);
// render unmatched values differently
if (!renameModel.hasComplement(index)) {
if (isSelected) {
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
} else {
setForeground(noMatchGradientBeginColor);
typeRenderer.setAlpha(0.5f);
}
}
if (value instanceof File) {
// display file extension
File file = (File) value;
@ -69,9 +79,9 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
}
} else if (value instanceof FormattedFuture) {
// display progress icon
FormattedFuture future = (FormattedFuture) value;
FormattedFuture formattedFuture = (FormattedFuture) value;
switch (future.getState()) {
switch (formattedFuture.getState()) {
case PENDING:
setIcon(ResourceManager.getIcon("worker.pending"));
break;
@ -80,15 +90,6 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
break;
}
}
if (!renameModel.hasComplement(index)) {
if (isSelected) {
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
} else {
setForeground(noMatchGradientBeginColor);
typeRenderer.setAlpha(0.5f);
}
}
}
@ -105,7 +106,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
return "File";
}
private static class TypeRenderer extends DefaultListCellRenderer {
private final Insets margin = new Insets(0, 10, 0, 0);
@ -117,7 +118,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
private float alpha = 1.0f;
public TypeRenderer() {
setOpaque(false);
setForeground(new Color(0x141414));

View File

@ -10,23 +10,24 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.concurrent.TimeoutException;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.ui.TunedUtilities;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.tuned.FileUtilities;
import net.sourceforge.tuned.ui.TunedUtilities;
public class RenameModel extends MatchModel<Object, File> {
@ -56,7 +57,7 @@ public class RenameModel extends MatchModel<Object, File> {
private boolean preserveExtension = true;
public EventList<FormattedFuture> names() {
return names;
}
@ -82,26 +83,33 @@ public class RenameModel extends MatchModel<Object, File> {
for (int i = 0; i < names.size(); i++) {
if (hasComplement(i)) {
FormattedFuture future = names.get(i);
File originalFile = files().get(i);
FormattedFuture formattedFuture = names.get(i);
// check if background formatter is done
if (!future.isDone()) {
throw new IllegalStateException(String.format("\"%s\" has not been formatted yet.", future.toString()));
StringBuilder nameBuilder = new StringBuilder();
// append formatted name, throw exception if not ready
try {
nameBuilder.append(formattedFuture.get(0, TimeUnit.SECONDS));
} catch (ExecutionException e) {
throw new IllegalStateException(String.format("\"%s\" could not be formatted: %s.", formattedFuture.preview(), e.getCause().getMessage()));
} catch (TimeoutException e) {
throw new IllegalStateException(String.format("\"%s\" has not been formatted yet.", formattedFuture.preview()));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
File originalFile = files().get(i);
StringBuilder newName = new StringBuilder(future.toString());
// append extension, if desired
if (preserveExtension) {
String extension = FileUtilities.getExtension(originalFile);
if (extension != null) {
newName.append(".").append(extension.toLowerCase());
nameBuilder.append('.').append(extension.toLowerCase());
}
}
// same parent, different name
File newFile = new File(originalFile.getParentFile(), newName.toString());
File newFile = new File(originalFile.getParentFile(), nameBuilder.toString());
// insert mapping
if (map.put(originalFile, newFile) != null) {
@ -136,14 +144,14 @@ public class RenameModel extends MatchModel<Object, File> {
return defaultFormatter;
}
private class FormattedFutureEventList extends TransformedList<Object, FormattedFuture> {
private final List<FormattedFuture> futures = new ArrayList<FormattedFuture>();
private final Executor backgroundFormatter = new ThreadPoolExecutor(0, 1, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
public FormattedFutureEventList() {
super(values());
@ -278,6 +286,7 @@ public class RenameModel extends MatchModel<Object, File> {
future.cancel(true);
}
private final PropertyChangeListener futureListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
@ -302,15 +311,10 @@ public class RenameModel extends MatchModel<Object, File> {
private final MatchFormatter formatter;
private String display;
private FormattedFuture(Match<Object, File> match, MatchFormatter formatter) {
this.match = match;
this.formatter = formatter;
// initial display value
this.display = formatter.preview(match);
}
@ -319,6 +323,11 @@ public class RenameModel extends MatchModel<Object, File> {
}
public String preview() {
return formatter.preview(match);
}
@Override
protected String doInBackground() throws Exception {
return formatter.format(match);
@ -326,22 +335,17 @@ public class RenameModel extends MatchModel<Object, File> {
@Override
protected void done() {
if (isCancelled()) {
return;
public String toString() {
if (isDone()) {
try {
return get(0, TimeUnit.SECONDS);
} catch (Exception e) {
return String.format("[%s] %s", e instanceof ExecutionException ? e.getCause().getMessage() : e, preview());
}
}
try {
this.display = get();
} catch (Exception e) {
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
}
}
@Override
public String toString() {
return display;
// use preview if we are not ready yet
return preview();
}
}

View File

@ -16,7 +16,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.script.ScriptException;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
@ -32,8 +31,8 @@ import ca.odell.glazedlists.swing.EventSelectionModel;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.ui.EpisodeFormatDialog;
import net.sourceforge.filebot.ui.panel.rename.RenameModel.FormattedFuture;
import net.sourceforge.filebot.web.AnidbClient;
import net.sourceforge.filebot.web.Episode;
@ -159,14 +158,9 @@ public class RenamePanel extends JComponent {
switch (dialog.getSelectedOption()) {
case APPROVE:
try {
EpisodeExpressionFormatter formatter = new EpisodeExpressionFormatter(dialog.getExpression());
renameModel.useFormatter(Episode.class, formatter);
persistentExpressionFormatter.setValue(formatter);
} catch (ScriptException e) {
// will not happen because illegal expressions cannot be approved in dialog
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
}
EpisodeExpressionFormatter formatter = new EpisodeExpressionFormatter(dialog.getSelectedFormat());
renameModel.useFormatter(Episode.class, formatter);
persistentExpressionFormatter.setValue(formatter);
break;
case USE_DEFAULT:
renameModel.useFormatter(Episode.class, null);
@ -325,7 +319,7 @@ public class RenamePanel extends JComponent {
if (expression != null) {
try {
return new EpisodeExpressionFormatter(expression);
return new EpisodeExpressionFormatter(new ExpressionFormat(expression));
} catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
}
@ -337,7 +331,7 @@ public class RenamePanel extends JComponent {
@Override
public void put(Preferences prefs, String key, EpisodeExpressionFormatter value) {
prefs.put(key, value.getExpression());
prefs.put(key, value.getFormat().getExpression());
}
});

View File

@ -83,14 +83,15 @@ public class AnidbClient implements EpisodeListProvider {
// we might have been redirected to the episode list page
if (results.isEmpty()) {
// get anime information from document
String title = selectTitle(dom);
String link = selectString("//*[@class='data']//A[@class='short_link']/@href", dom);
try {
// insert single entry
results.add(new HyperLink(title, new URL(link)));
} catch (MalformedURLException e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid location: " + link);
// check if page is an anime page, are an empty search result page
if (!link.isEmpty()) {
try {
results.add(new HyperLink(selectTitle(dom), new URL(link)));
} catch (MalformedURLException e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid location: " + link);
}
}
}
@ -100,7 +101,7 @@ public class AnidbClient implements EpisodeListProvider {
protected String selectTitle(Document animePage) {
// extract name from header (e.g. "Anime: Naruto")
return selectString("//H1", animePage).replaceFirst("Anime:\\s*", "");
return selectString("//H1", animePage).replaceFirst("^Anime:\\s*", "");
}

View File

@ -71,11 +71,15 @@ public class IMDbClient implements EpisodeListProvider {
// we might have been redirected to the movie page
if (results.isEmpty()) {
String name = normalizeName(selectString("//H1/text()", dom));
String year = selectString("//H1//A", dom);
String url = selectString("//LINK[@rel='canonical']/@href", dom);
results.add(new MovieDescriptor(name, Integer.parseInt(year), getImdbId(url)));
try {
String name = normalizeName(selectString("//H1/text()", dom));
String year = selectString("//H1//A", dom);
String url = selectString("//LINK[@rel='canonical']/@href", dom);
results.add(new MovieDescriptor(name, Integer.parseInt(year), getImdbId(url)));
} catch (Exception e) {
// ignore, we probably got redirected to an error page
}
}
return results;
@ -136,26 +140,14 @@ public class IMDbClient implements EpisodeListProvider {
protected int getImdbId(String link) {
try {
// try to extract path
link = new URI(link).getPath();
} catch (URISyntaxException e) {
// cannot extract path component, just move on
}
Matcher matcher = Pattern.compile("tt(\\d{7})").matcher(link);
String imdbId = null;
// find last match
while (matcher.find()) {
imdbId = matcher.group(1);
if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
}
if (imdbId == null)
throw new IllegalArgumentException(String.format("Cannot find imdb id: %s", link));
return Integer.parseInt(imdbId);
// pattern not found
throw new IllegalArgumentException(String.format("Cannot find imdb id: %s", link));
}

View File

@ -4,17 +4,19 @@ package net.sourceforge.tuned;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
import java.util.prefs.Preferences;
import net.sourceforge.tuned.PreferencesMap.Adapter;
public class PreferencesList<T> extends AbstractList<T> {
public class PreferencesList<T> extends AbstractList<T> implements RandomAccess {
private final PreferencesMap<T> prefs;
public PreferencesList(PreferencesMap<T> preferencesMap) {
this.prefs = preferencesMap;
}
@ -97,6 +99,25 @@ public class PreferencesList<T> extends AbstractList<T> {
}
public void trimToSize(int limit) {
for (int i = size() - 1; i >= limit; i--) {
remove(i);
}
}
public void set(Collection<T> data) {
// remove all elements beyond data.size
trimToSize(data.size());
// override elements
int i = 0;
for (T element : data) {
setImpl(i++, element);
}
}
@Override
public void clear() {
prefs.clear();

View File

@ -46,6 +46,14 @@ public class AnidbClientTest {
}
@Test
public void searchNoMatch() throws Exception {
List<SearchResult> results = anidb.search("i will not find anything for this query string");
assertTrue(results.isEmpty());
}
@Test
public void searchHideSynonyms() throws Exception {
final List<SearchResult> results = anidb.search("one piece");

View File

@ -28,6 +28,14 @@ public class IMDbClientTest {
}
@Test
public void searchNoMatch() throws Exception {
List<SearchResult> results = imdb.search("i will not find anything for this query string");
assertTrue(results.isEmpty());
}
@Test
public void searchResultPageRedirect() throws Exception {
List<SearchResult> results = imdb.search("my name is earl");

View File

@ -16,6 +16,8 @@ import org.junit.Test;
import net.sourceforge.filebot.web.OpenSubtitlesSubtitleDescriptor.Property;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.SubFile;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.TryUploadResponse;
public class OpenSubtitlesXmlRpcTest {
@ -83,6 +85,23 @@ public class OpenSubtitlesXmlRpcTest {
}
@Test
public void tryUploadSubtitles() throws Exception {
SubFile subtitle = new SubFile();
subtitle.setSubFileName("firefly.s01e01.serenity.pilot.dvdrip.xvid.srt");
subtitle.setSubHash("6d9c600fb8b07f87ffcf156e4ed308ca");
subtitle.setMovieFileName("firefly.s01e01.serenity.pilot.dvdrip.xvid.avi");
subtitle.setMovieHash("2bba5c34b007153b");
subtitle.setMovieByteSize(717565952);
TryUploadResponse response = xmlrpc.tryUploadSubtitles(subtitle);
assertFalse(response.isUploadRequired());
assertEquals("100705", response.getSubtitleData().get(Property.IDSubtitle));
assertEquals("eng", response.getSubtitleData().get(Property.SubLanguageID));
}
@Test
public void checkSubHash() throws Exception {
Map<String, Integer> subHashMap = xmlrpc.checkSubHash(singleton("e12715f466ee73c86694b7ab9f311285"));

View File

@ -38,6 +38,14 @@ public class TVDotComClientTest {
}
@Test
public void searchNoMatch() throws Exception {
List<SearchResult> results = tvdotcom.search("i will not find anything for this query string");
assertTrue(results.isEmpty());
}
@Test
public void getEpisodeList() throws Exception {
List<Episode> list = tvdotcom.getEpisodeList(buffySearchResult, 7);