* 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; package net.sourceforge.filebot;
import java.util.List;
import java.util.Map;
import java.util.prefs.BackingStoreException; import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
@ -26,6 +24,7 @@ public final class Settings {
return "1.9"; return "1.9";
}; };
private static final Settings userRoot = new Settings(Preferences.userNodeForPackage(Settings.class)); private static final Settings userRoot = new Settings(Preferences.userNodeForPackage(Settings.class));
@ -33,6 +32,7 @@ public final class Settings {
return userRoot; return userRoot;
} }
private final Preferences prefs; private final Preferences prefs;
@ -83,22 +83,22 @@ public final class Settings {
} }
public Map<String, String> asMap() { public PreferencesMap<String> asMap() {
return PreferencesMap.map(prefs); 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); return PreferencesMap.map(prefs, adapter);
} }
public List<String> asList() { public PreferencesList<String> asList() {
return PreferencesList.map(prefs); 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); return PreferencesList.map(prefs, adapter);
} }

View File

@ -5,6 +5,13 @@ importPackage(java.lang);
importPackage(java.util); 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). * 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(); ScriptContext context = new SimpleScriptContext();
context.setBindings(priviledgedBindings, ScriptContext.GLOBAL_SCOPE); context.setBindings(priviledgedBindings, ScriptContext.GLOBAL_SCOPE);
// reset exception state
lastException = null;
for (Object snipped : compilation) { for (Object snipped : compilation) {
if (snipped instanceof CompiledScript) { if (snipped instanceof CompiledScript) {
try { try {
@ -146,7 +149,7 @@ public class ExpressionFormat extends Format {
} }
public ScriptException scriptException() { public ScriptException caughtScriptException() {
return lastException; return lastException;
} }

View File

@ -13,10 +13,18 @@ import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeFormat; import net.sourceforge.filebot.web.EpisodeFormat;
class EpisodeExpressionFormatter extends ExpressionFormat implements MatchFormatter { class EpisodeExpressionFormatter implements MatchFormatter {
public EpisodeExpressionFormatter(String expression) throws ScriptException { private final ExpressionFormat format;
super(expression);
public EpisodeExpressionFormatter(ExpressionFormat format) {
this.format = format;
}
public ExpressionFormat getFormat() {
return format;
} }
@ -34,11 +42,17 @@ class EpisodeExpressionFormatter extends ExpressionFormat implements MatchFormat
@Override @Override
public String format(Match<?, ?> match) { public synchronized String format(Match<?, ?> match) throws ScriptException {
Episode episode = (Episode) match.getValue(); Episode episode = (Episode) match.getValue();
File mediaFile = (File) match.getCandidate(); 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 java.awt.Font.*;
import static javax.swing.BorderFactory.*;
import java.awt.Color; import java.awt.Color;
import java.awt.Font; import java.awt.Font;
@ -16,8 +17,10 @@ import java.io.File;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@ -34,16 +37,16 @@ import javax.swing.JComponent;
import javax.swing.JDialog; import javax.swing.JDialog;
import javax.swing.JFileChooser; import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.Timer; import javax.swing.Timer;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.undo.UndoManager;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ResourceManager;
@ -54,6 +57,7 @@ import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeFormat; import net.sourceforge.filebot.web.EpisodeFormat;
import net.sourceforge.tuned.DefaultThreadFactory; import net.sourceforge.tuned.DefaultThreadFactory;
import net.sourceforge.tuned.ExceptionUtilities; import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.PreferencesList;
import net.sourceforge.tuned.ui.GradientStyle; import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LazyDocumentListener; import net.sourceforge.tuned.ui.LazyDocumentListener;
import net.sourceforge.tuned.ui.LinkButton; import net.sourceforge.tuned.ui.LinkButton;
@ -67,6 +71,8 @@ public class EpisodeFormatDialog extends JDialog {
private Option selectedOption = Option.CANCEL; private Option selectedOption = Option.CANCEL;
private ExpressionFormat selectedFormat = null;
private JLabel preview = new JLabel(); private JLabel preview = new JLabel();
private JLabel status = new JLabel(); private JLabel status = new JLabel();
@ -79,6 +85,8 @@ public class EpisodeFormatDialog extends JDialog {
private JTextField editor = new JTextField(); private JTextField editor = new JTextField();
private PreferencesList<String> persistentFormatHistory = Settings.userRoot().node("rename/format.recent").asList();
private Color defaultColor = preview.getForeground(); private Color defaultColor = preview.getForeground();
private Color errorColor = Color.red; private Color errorColor = Color.red;
@ -93,7 +101,6 @@ public class EpisodeFormatDialog extends JDialog {
public EpisodeFormatDialog(Window owner) { public EpisodeFormatDialog(Window owner) {
super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL); super(owner, "Episode Format", ModalityType.DOCUMENT_MODAL);
editor.setText(Settings.userRoot().get("dialog.format"));
editor.setFont(new Font(MONOSPACED, PLAIN, 14)); editor.setFont(new Font(MONOSPACED, PLAIN, 14));
// bold title label in header // bold title label in header
@ -132,10 +139,6 @@ public class EpisodeFormatDialog extends JDialog {
header.setComponentPopupMenu(createPreviewSamplePopup()); header.setComponentPopupMenu(createPreviewSamplePopup());
// setup undo support
final UndoManager undo = new UndoManager();
editor.getDocument().addUndoableEditListener(undo);
// enable undo/redo // enable undo/redo
TunedUtilities.installUndoSupport(editor); 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 // update preview to current format
firePreviewSampleChanged(); firePreviewSampleChanged();
@ -244,7 +253,7 @@ public class EpisodeFormatDialog extends JDialog {
private JPanel createSyntaxPanel() { private JPanel createSyntaxPanel() {
JPanel panel = new JPanel(new MigLayout("fill, nogrid")); 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.setBackground(new Color(0xFFFFE1));
panel.setOpaque(true); panel.setOpaque(true);
@ -257,7 +266,7 @@ public class EpisodeFormatDialog extends JDialog {
private JComponent createExamplesPanel() { private JComponent createExamplesPanel() {
JPanel panel = new JPanel(new MigLayout("fill, wrap 3")); 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)); panel.setBackground(new Color(0xFFFFE1));
ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName()); ResourceBundle bundle = ResourceBundle.getBundle(getClass().getName());
@ -357,10 +366,10 @@ public class EpisodeFormatDialog extends JDialog {
threadGroup.stop(); threadGroup.stop();
// log access of potentially unsafe method // 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) { } 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; return remaining;
@ -377,7 +386,7 @@ public class EpisodeFormatDialog extends JDialog {
private void checkFormatInBackground() { private void checkFormatInBackground() {
try { try {
// check syntax in foreground // check syntax in foreground
final ExpressionFormat format = new ExpressionFormat(getExpression()); final ExpressionFormat format = new ExpressionFormat(editor.getText().trim());
// format in background // format in background
final Timer progressIndicatorTimer = TunedUtilities.invokeLater(400, new Runnable() { final Timer progressIndicatorTimer = TunedUtilities.invokeLater(400, new Runnable() {
@ -402,8 +411,8 @@ public class EpisodeFormatDialog extends JDialog {
preview.setText(get()); preview.setText(get());
// check internal script exception // check internal script exception
if (format.scriptException() != null) { if (format.caughtScriptException() != null) {
throw format.scriptException(); throw format.caughtScriptException();
} }
// check empty output // check empty output
@ -438,13 +447,13 @@ public class EpisodeFormatDialog extends JDialog {
} }
public String getExpression() { public Option getSelectedOption() {
return editor.getText().trim(); return selectedOption;
} }
public Option getSelectedOption() { public ExpressionFormat getSelectedFormat() {
return selectedOption; 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")) { protected final Action cancelAction = new AbstractAction("Cancel", ResourceManager.getIcon("dialog.cancel")) {
@Override @Override
@ -480,17 +512,25 @@ public class EpisodeFormatDialog extends JDialog {
@Override @Override
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
try { try {
if (progressIndicator.isVisible())
throw new IllegalStateException("Format has not been verified yet.");
// check syntax // check syntax
ExpressionFormat format = new ExpressionFormat(getExpression()); selectedFormat = new ExpressionFormat(editor.getText().trim());
// remember format // create new recent history and ignore duplicates
Settings.userRoot().put("dialog.format", format.getExpression()); 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); finish(Option.APPROVE);
} catch (Exception e) { } catch (ScriptException e) {
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(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)} example[2]: {n} - {s+'x'}{e.pad(2)}
# uglyfy name # uglyfy name
example[3]: {n.space('.').toLowerCase()}.{s}{e.pad(2)} example[3]: {n.space('.').lower()}.{s}{e.pad(2)}

View File

@ -20,6 +20,7 @@ final class HistorySpooler {
return instance; return instance;
} }
private final File file = new File("history.xml"); private final File file = new File("history.xml");
private final History sessionHistory = new History(); private final History sessionHistory = new History();
@ -33,7 +34,7 @@ final class HistorySpooler {
try { try {
history.addAll(importHistory(file).sequences()); history.addAll(importHistory(file).sequences());
} catch (IOException e) { } 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 // clear session history
sessionHistory.clear(); sessionHistory.clear();
} catch (IOException e) { } 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 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; import java.awt.Component;

View File

@ -56,6 +56,16 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
typeRenderer.setVisible(false); typeRenderer.setVisible(false);
typeRenderer.setAlpha(1.0f); 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) { if (value instanceof File) {
// display file extension // display file extension
File file = (File) value; File file = (File) value;
@ -69,9 +79,9 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
} }
} else if (value instanceof FormattedFuture) { } else if (value instanceof FormattedFuture) {
// display progress icon // display progress icon
FormattedFuture future = (FormattedFuture) value; FormattedFuture formattedFuture = (FormattedFuture) value;
switch (future.getState()) { switch (formattedFuture.getState()) {
case PENDING: case PENDING:
setIcon(ResourceManager.getIcon("worker.pending")); setIcon(ResourceManager.getIcon("worker.pending"));
break; break;
@ -80,15 +90,6 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
break; break;
} }
} }
if (!renameModel.hasComplement(index)) {
if (isSelected) {
setGradientColors(noMatchGradientBeginColor, noMatchGradientEndColor);
} else {
setForeground(noMatchGradientBeginColor);
typeRenderer.setAlpha(0.5f);
}
}
} }

View File

@ -10,23 +10,24 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue; 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.EventList;
import ca.odell.glazedlists.TransformedList; import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent; 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> { public class RenameModel extends MatchModel<Object, File> {
@ -82,26 +83,33 @@ public class RenameModel extends MatchModel<Object, File> {
for (int i = 0; i < names.size(); i++) { for (int i = 0; i < names.size(); i++) {
if (hasComplement(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 StringBuilder nameBuilder = new StringBuilder();
if (!future.isDone()) {
throw new IllegalStateException(String.format("\"%s\" has not been formatted yet.", future.toString())); // 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); // append extension, if desired
StringBuilder newName = new StringBuilder(future.toString());
if (preserveExtension) { if (preserveExtension) {
String extension = FileUtilities.getExtension(originalFile); String extension = FileUtilities.getExtension(originalFile);
if (extension != null) { if (extension != null) {
newName.append(".").append(extension.toLowerCase()); nameBuilder.append('.').append(extension.toLowerCase());
} }
} }
// same parent, different name // same parent, different name
File newFile = new File(originalFile.getParentFile(), newName.toString()); File newFile = new File(originalFile.getParentFile(), nameBuilder.toString());
// insert mapping // insert mapping
if (map.put(originalFile, newFile) != null) { if (map.put(originalFile, newFile) != null) {
@ -278,6 +286,7 @@ public class RenameModel extends MatchModel<Object, File> {
future.cancel(true); future.cancel(true);
} }
private final PropertyChangeListener futureListener = new PropertyChangeListener() { private final PropertyChangeListener futureListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) { public void propertyChange(PropertyChangeEvent evt) {
@ -302,15 +311,10 @@ public class RenameModel extends MatchModel<Object, File> {
private final MatchFormatter formatter; private final MatchFormatter formatter;
private String display;
private FormattedFuture(Match<Object, File> match, MatchFormatter formatter) { private FormattedFuture(Match<Object, File> match, MatchFormatter formatter) {
this.match = match; this.match = match;
this.formatter = formatter; this.formatter = formatter;
// initial display value
this.display = formatter.preview(match);
} }
@ -319,29 +323,29 @@ public class RenameModel extends MatchModel<Object, File> {
} }
public String preview() {
return formatter.preview(match);
}
@Override @Override
protected String doInBackground() throws Exception { protected String doInBackground() throws Exception {
return formatter.format(match); return formatter.format(match);
} }
@Override
protected void done() {
if (isCancelled()) {
return;
}
try {
this.display = get();
} catch (Exception e) {
Logger.getLogger("global").log(Level.WARNING, e.getMessage(), e);
}
}
@Override @Override
public String toString() { public String toString() {
return display; if (isDone()) {
try {
return get(0, TimeUnit.SECONDS);
} catch (Exception e) {
return String.format("[%s] %s", e instanceof ExecutionException ? e.getCause().getMessage() : e, preview());
}
}
// 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.logging.Logger;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import javax.script.ScriptException;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.Action; import javax.swing.Action;
import javax.swing.Icon; import javax.swing.Icon;
@ -32,8 +31,8 @@ import ca.odell.glazedlists.swing.EventSelectionModel;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.similarity.Match; 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.ui.panel.rename.RenameModel.FormattedFuture;
import net.sourceforge.filebot.web.AnidbClient; import net.sourceforge.filebot.web.AnidbClient;
import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.Episode;
@ -159,14 +158,9 @@ public class RenamePanel extends JComponent {
switch (dialog.getSelectedOption()) { switch (dialog.getSelectedOption()) {
case APPROVE: case APPROVE:
try { EpisodeExpressionFormatter formatter = new EpisodeExpressionFormatter(dialog.getSelectedFormat());
EpisodeExpressionFormatter formatter = new EpisodeExpressionFormatter(dialog.getExpression());
renameModel.useFormatter(Episode.class, formatter); renameModel.useFormatter(Episode.class, formatter);
persistentExpressionFormatter.setValue(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);
}
break; break;
case USE_DEFAULT: case USE_DEFAULT:
renameModel.useFormatter(Episode.class, null); renameModel.useFormatter(Episode.class, null);
@ -325,7 +319,7 @@ public class RenamePanel extends JComponent {
if (expression != null) { if (expression != null) {
try { try {
return new EpisodeExpressionFormatter(expression); return new EpisodeExpressionFormatter(new ExpressionFormat(expression));
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e); Logger.getLogger("ui").log(Level.WARNING, e.getMessage(), e);
} }
@ -337,7 +331,7 @@ public class RenamePanel extends JComponent {
@Override @Override
public void put(Preferences prefs, String key, EpisodeExpressionFormatter value) { public void put(Preferences prefs, String key, EpisodeExpressionFormatter value) {
prefs.put(key, value.getExpression()); prefs.put(key, value.getFormat().getExpression());
} }
}); });

View File

@ -83,16 +83,17 @@ public class AnidbClient implements EpisodeListProvider {
// we might have been redirected to the episode list page // we might have been redirected to the episode list page
if (results.isEmpty()) { if (results.isEmpty()) {
// get anime information from document // get anime information from document
String title = selectTitle(dom);
String link = selectString("//*[@class='data']//A[@class='short_link']/@href", dom); String link = selectString("//*[@class='data']//A[@class='short_link']/@href", dom);
// check if page is an anime page, are an empty search result page
if (!link.isEmpty()) {
try { try {
// insert single entry results.add(new HyperLink(selectTitle(dom), new URL(link)));
results.add(new HyperLink(title, new URL(link)));
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid location: " + link); Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid location: " + link);
} }
} }
}
return results; return results;
} }
@ -100,7 +101,7 @@ public class AnidbClient implements EpisodeListProvider {
protected String selectTitle(Document animePage) { protected String selectTitle(Document animePage) {
// extract name from header (e.g. "Anime: Naruto") // 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 // we might have been redirected to the movie page
if (results.isEmpty()) { if (results.isEmpty()) {
try {
String name = normalizeName(selectString("//H1/text()", dom)); String name = normalizeName(selectString("//H1/text()", dom));
String year = selectString("//H1//A", dom); String year = selectString("//H1//A", dom);
String url = selectString("//LINK[@rel='canonical']/@href", dom); String url = selectString("//LINK[@rel='canonical']/@href", dom);
results.add(new MovieDescriptor(name, Integer.parseInt(year), getImdbId(url))); results.add(new MovieDescriptor(name, Integer.parseInt(year), getImdbId(url)));
} catch (Exception e) {
// ignore, we probably got redirected to an error page
}
} }
return results; return results;
@ -136,26 +140,14 @@ public class IMDbClient implements EpisodeListProvider {
protected int getImdbId(String link) { 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); Matcher matcher = Pattern.compile("tt(\\d{7})").matcher(link);
String imdbId = null; if (matcher.find()) {
return Integer.parseInt(matcher.group(1));
// find last match
while (matcher.find()) {
imdbId = matcher.group(1);
} }
if (imdbId == null) // pattern not found
throw new IllegalArgumentException(String.format("Cannot find imdb id: %s", link)); throw new IllegalArgumentException(String.format("Cannot find imdb id: %s", link));
return Integer.parseInt(imdbId);
} }

View File

@ -4,13 +4,15 @@ package net.sourceforge.tuned;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.RandomAccess;
import java.util.prefs.Preferences; import java.util.prefs.Preferences;
import net.sourceforge.tuned.PreferencesMap.Adapter; 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; private final PreferencesMap<T> prefs;
@ -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 @Override
public void clear() { public void clear() {
prefs.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 @Test
public void searchHideSynonyms() throws Exception { public void searchHideSynonyms() throws Exception {
final List<SearchResult> results = anidb.search("one piece"); 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 @Test
public void searchResultPageRedirect() throws Exception { public void searchResultPageRedirect() throws Exception {
List<SearchResult> results = imdb.search("my name is earl"); 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.OpenSubtitlesSubtitleDescriptor.Property;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query; import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.SubFile;
import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.TryUploadResponse;
public class OpenSubtitlesXmlRpcTest { 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 @Test
public void checkSubHash() throws Exception { public void checkSubHash() throws Exception {
Map<String, Integer> subHashMap = xmlrpc.checkSubHash(singleton("e12715f466ee73c86694b7ab9f311285")); 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 @Test
public void getEpisodeList() throws Exception { public void getEpisodeList() throws Exception {
List<Episode> list = tvdotcom.getEpisodeList(buffySearchResult, 7); List<Episode> list = tvdotcom.getEpisodeList(buffySearchResult, 7);