diff --git a/source/net/sourceforge/filebot/Settings.java b/source/net/sourceforge/filebot/Settings.java index a3a43021..10c91eca 100644 --- a/source/net/sourceforge/filebot/Settings.java +++ b/source/net/sourceforge/filebot/Settings.java @@ -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 asMap() { + public PreferencesMap asMap() { return PreferencesMap.map(prefs); } - public Map asMap(Adapter adapter) { + public PreferencesMap asMap(Adapter adapter) { return PreferencesMap.map(prefs, adapter); } - public List asList() { + public PreferencesList asList() { return PreferencesList.map(prefs); } - public List asList(Adapter adapter) { + public PreferencesList asList(Adapter adapter) { return PreferencesList.map(prefs, adapter); } diff --git a/source/net/sourceforge/filebot/format/ExpressionFormat.global.js b/source/net/sourceforge/filebot/format/ExpressionFormat.global.js index d5653704..c22b95ab 100644 --- a/source/net/sourceforge/filebot/format/ExpressionFormat.global.js +++ b/source/net/sourceforge/filebot/format/ExpressionFormat.global.js @@ -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). * diff --git a/source/net/sourceforge/filebot/format/ExpressionFormat.java b/source/net/sourceforge/filebot/format/ExpressionFormat.java index fe3afcb5..15c419c2 100644 --- a/source/net/sourceforge/filebot/format/ExpressionFormat.java +++ b/source/net/sourceforge/filebot/format/ExpressionFormat.java @@ -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; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java index c94cf79e..eaf95585 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java @@ -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; } } diff --git a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java similarity index 84% rename from source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java rename to source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java index 5045fff7..7e3656d5 100644 --- a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.java @@ -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 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 recent = new LinkedHashSet(); + + // 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)); } } diff --git a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.properties b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties similarity index 84% rename from source/net/sourceforge/filebot/ui/EpisodeFormatDialog.properties rename to source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties index e3c8dd49..d3f9a582 100644 --- a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.properties +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeFormatDialog.properties @@ -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)} \ No newline at end of file +example[3]: {n.space('.').lower()}.{s}{e.pad(2)} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/HistorySpooler.java b/source/net/sourceforge/filebot/ui/panel/rename/HistorySpooler.java index 1fd7d2f1..4ec4af01 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/HistorySpooler.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/HistorySpooler.java @@ -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); } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchFormatter.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchFormatter.java index 52d4d18b..9fd58e0c 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchFormatter.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchFormatter.java @@ -13,6 +13,6 @@ public interface MatchFormatter { public String preview(Match match); - public String format(Match match); + public String format(Match match) throws Exception; } diff --git a/source/net/sourceforge/filebot/ui/MediaInfoPane.java b/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java similarity index 98% rename from source/net/sourceforge/filebot/ui/MediaInfoPane.java rename to source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java index 2461987d..458ebaac 100644 --- a/source/net/sourceforge/filebot/ui/MediaInfoPane.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MediaInfoPane.java @@ -1,5 +1,5 @@ -package net.sourceforge.filebot.ui; +package net.sourceforge.filebot.ui.panel.rename; import java.awt.Component; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java index 534d0ed9..f06df952 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameListCellRenderer.java @@ -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)); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameModel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameModel.java index 9c5344c3..f3d67712 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameModel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameModel.java @@ -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 { @@ -56,7 +57,7 @@ public class RenameModel extends MatchModel { private boolean preserveExtension = true; - + public EventList names() { return names; } @@ -82,26 +83,33 @@ public class RenameModel extends MatchModel { 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 { return defaultFormatter; } - + private class FormattedFutureEventList extends TransformedList { private final List futures = new ArrayList(); private final Executor backgroundFormatter = new ThreadPoolExecutor(0, 1, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue()); - + public FormattedFutureEventList() { super(values()); @@ -278,6 +286,7 @@ public class RenameModel extends MatchModel { future.cancel(true); } + private final PropertyChangeListener futureListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { @@ -302,15 +311,10 @@ public class RenameModel extends MatchModel { private final MatchFormatter formatter; - private String display; - - + private FormattedFuture(Match 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 { } + 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 { @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(); } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index 1a6d7abe..fd1fa548 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -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()); } }); diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index 420d8791..e3f8708e 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -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*", ""); } diff --git a/source/net/sourceforge/filebot/web/IMDbClient.java b/source/net/sourceforge/filebot/web/IMDbClient.java index 670963a0..b72e20b2 100644 --- a/source/net/sourceforge/filebot/web/IMDbClient.java +++ b/source/net/sourceforge/filebot/web/IMDbClient.java @@ -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)); } diff --git a/source/net/sourceforge/tuned/PreferencesList.java b/source/net/sourceforge/tuned/PreferencesList.java index 190be36c..bf832a1a 100644 --- a/source/net/sourceforge/tuned/PreferencesList.java +++ b/source/net/sourceforge/tuned/PreferencesList.java @@ -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 extends AbstractList { +public class PreferencesList extends AbstractList implements RandomAccess { private final PreferencesMap prefs; - + public PreferencesList(PreferencesMap preferencesMap) { this.prefs = preferencesMap; } @@ -97,6 +99,25 @@ public class PreferencesList extends AbstractList { } + public void trimToSize(int limit) { + for (int i = size() - 1; i >= limit; i--) { + remove(i); + } + } + + + public void set(Collection 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(); diff --git a/test/net/sourceforge/filebot/web/AnidbClientTest.java b/test/net/sourceforge/filebot/web/AnidbClientTest.java index 6bbcfd12..871a1a7d 100644 --- a/test/net/sourceforge/filebot/web/AnidbClientTest.java +++ b/test/net/sourceforge/filebot/web/AnidbClientTest.java @@ -46,6 +46,14 @@ public class AnidbClientTest { } + @Test + public void searchNoMatch() throws Exception { + List results = anidb.search("i will not find anything for this query string"); + + assertTrue(results.isEmpty()); + } + + @Test public void searchHideSynonyms() throws Exception { final List results = anidb.search("one piece"); diff --git a/test/net/sourceforge/filebot/web/IMDbClientTest.java b/test/net/sourceforge/filebot/web/IMDbClientTest.java index e73d7f54..55530eda 100644 --- a/test/net/sourceforge/filebot/web/IMDbClientTest.java +++ b/test/net/sourceforge/filebot/web/IMDbClientTest.java @@ -28,6 +28,14 @@ public class IMDbClientTest { } + @Test + public void searchNoMatch() throws Exception { + List results = imdb.search("i will not find anything for this query string"); + + assertTrue(results.isEmpty()); + } + + @Test public void searchResultPageRedirect() throws Exception { List results = imdb.search("my name is earl"); diff --git a/test/net/sourceforge/filebot/web/OpenSubtitlesXmlRpcTest.java b/test/net/sourceforge/filebot/web/OpenSubtitlesXmlRpcTest.java index fa80235b..c8e6d63c 100644 --- a/test/net/sourceforge/filebot/web/OpenSubtitlesXmlRpcTest.java +++ b/test/net/sourceforge/filebot/web/OpenSubtitlesXmlRpcTest.java @@ -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 subHashMap = xmlrpc.checkSubHash(singleton("e12715f466ee73c86694b7ab9f311285")); diff --git a/test/net/sourceforge/filebot/web/TVDotComClientTest.java b/test/net/sourceforge/filebot/web/TVDotComClientTest.java index c6a67eea..f444d042 100644 --- a/test/net/sourceforge/filebot/web/TVDotComClientTest.java +++ b/test/net/sourceforge/filebot/web/TVDotComClientTest.java @@ -38,6 +38,14 @@ public class TVDotComClientTest { } + @Test + public void searchNoMatch() throws Exception { + List results = tvdotcom.search("i will not find anything for this query string"); + + assertTrue(results.isEmpty()); + } + + @Test public void getEpisodeList() throws Exception { List list = tvdotcom.getEpisodeList(buffySearchResult, 7);