* separated pure OpenSubtitlesClient and SubtitleClient implementation for OpenSubtitles

* improved OpenSubtitlesHasher
This commit is contained in:
Reinhard Pointner 2008-04-20 16:03:19 +00:00
parent b9906b6a0d
commit 19b99132ad
24 changed files with 361 additions and 377 deletions

View File

@ -2,24 +2,17 @@
package net.sourceforge.filebot;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.util.List;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.KeyStroke;
public class FileBotUtil {
private FileBotUtil() {
// hide constructor
}
/**
@ -36,7 +29,7 @@ public class FileBotUtil {
* @return filename stripped of invalid characters
*/
public static String validateFileName(String filename) {
// strip \, /, :, *, ?, ", <, > and |
// strip invalid characters from filename
return INVALID_CHARACTERS_PATTERN.matcher(filename).replaceAll("");
}
@ -54,7 +47,12 @@ public class FileBotUtil {
return t;
}
private static final String[] TORRENT_FILE_EXTENSIONS = { "torrent" };
private static final String[] SFV_FILE_EXTENSIONS = { "sfv" };
private static final String[] LIST_FILE_EXTENSIONS = { "txt", "list", "" };
private static final String[] SUBTITLE_FILE_EXTENSIONS = { "srt", "sub", "ssa" };
public static boolean containsOnlyFolders(List<File> files) {
for (File file : files) {
if (!file.isDirectory())
@ -66,54 +64,29 @@ public class FileBotUtil {
public static boolean containsOnlyTorrentFiles(List<File> files) {
for (File file : files) {
if (!FileFormat.hasExtension(file, "torrent"))
return false;
}
return true;
return containsOnly(files, TORRENT_FILE_EXTENSIONS);
}
public static boolean containsOnlySfvFiles(List<File> files) {
for (File file : files) {
if (!FileFormat.hasExtension(file, "sfv"))
return false;
}
return true;
return containsOnly(files, SFV_FILE_EXTENSIONS);
}
public static boolean containsOnlyListFiles(List<File> files) {
return containsOnly(files, LIST_FILE_EXTENSIONS);
}
private static boolean containsOnly(List<File> files, String[] extensions) {
for (File file : files) {
if (!FileFormat.hasExtension(file, "txt", "list", ""))
if (!FileFormat.hasExtension(file, extensions))
return false;
}
return true;
}
public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) {
Integer key = action.hashCode();
component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key);
component.getActionMap().put(key, action);
}
public static Point getPreferredLocation(JDialog dialog) {
Window owner = dialog.getOwner();
if (owner == null)
return new Point(120, 80);
Point p = owner.getLocation();
Dimension d = owner.getSize();
return new Point(p.x + d.width / 4, p.y + d.height / 7);
}
public static final FileFilter FOLDERS_ONLY = new FileFilter() {
@Override
@ -131,4 +104,14 @@ public class FileBotUtil {
}
};
public static final FilenameFilter SUBTITLES_ONLY = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return FileFormat.hasExtension(name, SUBTITLE_FILE_EXTENSIONS);
}
};
}

View File

@ -35,7 +35,12 @@ public class FileFormat {
if (file.isDirectory())
return false;
String extension = getExtension(file);
return hasExtension(file.getName(), extensions);
}
public static boolean hasExtension(String filename, String... extensions) {
String extension = getExtension(filename, false);
for (String ext : extensions) {
if (ext.equalsIgnoreCase(extension))

View File

@ -19,7 +19,6 @@ import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.TitledBorder;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.filebot.ui.transfer.ExportHandler;
import net.sourceforge.filebot.ui.transfer.FileTransferable;
@ -32,6 +31,7 @@ import net.sourceforge.filebot.ui.transferablepolicies.NullTransferablePolicy;
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
import net.sourceforge.tuned.ui.SimpleListModel;
import net.sourceforge.tuned.ui.TunedUtil;
public class FileBotList extends JPanel implements Saveable, TransferablePolicySupport {
@ -81,7 +81,7 @@ public class FileBotList extends JPanel implements Saveable, TransferablePolicyS
if (enableRemoveAction) {
// Shortcut DELETE
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
}
}

View File

@ -21,13 +21,13 @@ import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.MessageBus;
import net.sourceforge.tuned.MessageHandler;
import net.sourceforge.tuned.ui.ShadowBorder;
import net.sourceforge.tuned.ui.SimpleListModel;
import net.sourceforge.tuned.ui.TunedUtil;
public class FileBotWindow extends JFrame implements ListSelectionListener {
@ -56,7 +56,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
setContentPane(contentPane);
// Shortcut ESC
FileBotUtil.registerActionForKeystroke(contentPane, KeyStroke.getKeyStroke("released ESCAPE"), closeAction);
TunedUtil.registerActionForKeystroke(contentPane, KeyStroke.getKeyStroke("released ESCAPE"), closeAction);
setSize(760, 615);

View File

@ -7,7 +7,6 @@ import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.net.URL;
import java.text.NumberFormat;
import javax.swing.BorderFactory;
import javax.swing.Icon;
@ -16,18 +15,22 @@ import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import net.sourceforge.tuned.ui.HyperlinkLabel;
public class HistoryPanel extends JPanel {
private JPanel grid = new JPanel(new GridLayout(0, 3, 15, 10));
private final JPanel grid = new JPanel(new GridLayout(0, 3, 15, 10));
private final JLabel columnHeader1 = new JLabel();
private final JLabel columnHeader2 = new JLabel();
private final JLabel columnHeader3 = new JLabel();
public HistoryPanel(String titleHeader, String infoHeader) {
setLayout(new FlowLayout(FlowLayout.CENTER));
public HistoryPanel() {
super(new FlowLayout(FlowLayout.CENTER));
JScrollPane scrollPane = new JScrollPane(grid, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
@ -38,48 +41,53 @@ public class HistoryPanel extends JPanel {
setOpaque(true);
grid.setOpaque(false);
JLabel titleLabel = new JLabel(titleHeader);
JLabel infoLabel = new JLabel(infoHeader);
JLabel durationLabel = new JLabel("Duration");
Font font = columnHeader1.getFont().deriveFont(Font.BOLD);
Font font = titleLabel.getFont().deriveFont(Font.BOLD);
columnHeader1.setHorizontalAlignment(SwingConstants.CENTER);
columnHeader2.setHorizontalAlignment(SwingConstants.CENTER);
columnHeader3.setHorizontalAlignment(SwingConstants.RIGHT);
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
infoLabel.setHorizontalAlignment(SwingConstants.CENTER);
durationLabel.setHorizontalAlignment(SwingConstants.RIGHT);
columnHeader1.setFont(font);
columnHeader2.setFont(font);
columnHeader3.setFont(font);
titleLabel.setFont(font);
infoLabel.setFont(font);
durationLabel.setFont(font);
grid.add(titleLabel);
grid.add(infoLabel);
grid.add(durationLabel);
grid.add(columnHeader1);
grid.add(columnHeader2);
grid.add(columnHeader3);
}
private final Border infoBorder = BorderFactory.createEmptyBorder(0, 0, 0, 10);
public void add(String title, URL url, String info, long duration, Icon icon) {
String durationString = NumberFormat.getInstance().format(duration) + " ms";
JLabel titleLabel = (url != null) ? new HyperlinkLabel(title, url) : new JLabel(title);
JLabel infoLabel = new JLabel(info);
JLabel durationLabel = new JLabel(durationString);
infoLabel.setBorder(infoBorder);
titleLabel.setHorizontalAlignment(SwingConstants.LEFT);
infoLabel.setHorizontalAlignment(SwingConstants.RIGHT);
durationLabel.setHorizontalAlignment(SwingConstants.RIGHT);
titleLabel.setIcon(icon);
titleLabel.setIconTextGap(7);
grid.add(titleLabel);
grid.add(infoLabel);
grid.add(durationLabel);
public void setColumnHeader1(String text) {
columnHeader1.setText(text);
}
public void setColumnHeader2(String text) {
columnHeader2.setText(text);
}
public void setColumnHeader3(String text) {
columnHeader3.setText(text);
}
public void add(String column1, URL url, Icon icon, String column2, String column3) {
JLabel label1 = (url != null) ? new HyperlinkLabel(column1, url) : new JLabel(column1);
JLabel label2 = new JLabel(column2);
JLabel label3 = new JLabel(column3);
label1.setHorizontalAlignment(SwingConstants.LEFT);
label2.setHorizontalAlignment(SwingConstants.RIGHT);
label3.setHorizontalAlignment(SwingConstants.RIGHT);
label1.setIcon(icon);
label1.setIconTextGap(7);
label2.setBorder(new EmptyBorder(0, 0, 0, 10));
grid.add(label1);
grid.add(label2);
grid.add(label3);
}
}

View File

@ -24,10 +24,10 @@ import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer;
import net.sourceforge.tuned.ui.SimpleListModel;
import net.sourceforge.tuned.ui.TunedUtil;
public class SelectDialog<T> extends JDialog {
@ -75,7 +75,7 @@ public class SelectDialog<T> extends JDialog {
// bounds and location
setMinimumSize(new Dimension(175, 175));
setSize(new Dimension(200, 190));
setLocation(FileBotUtil.getPreferredLocation(this));
setLocation(TunedUtil.getPreferredLocation(this));
// default selection
list.setModel(new SimpleListModel(options));

View File

@ -14,10 +14,10 @@ import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import net.sourceforge.tuned.ui.TunedUtil;
class FileTreePanel extends JPanel {
@ -39,7 +39,7 @@ class FileTreePanel extends JPanel {
buttons.add(Box.createGlue());
// Shortcut DELETE
FileBotUtil.registerActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtil.registerActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
add(new LoadingOverlayPane(new JScrollPane(fileTree), ResourceManager.getIcon("loading")), BorderLayout.CENTER);
add(buttons, BorderLayout.SOUTH);

View File

@ -21,7 +21,6 @@ import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.FileBotPanel;
@ -30,6 +29,7 @@ import net.sourceforge.filebot.ui.MessageManager;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.filebot.ui.transfer.SaveAction;
import net.sourceforge.tuned.MessageBus;
import net.sourceforge.tuned.ui.TunedUtil;
public class ListPanel extends FileBotPanel {
@ -88,7 +88,7 @@ public class ListPanel extends FileBotPanel {
add(spinners, BorderLayout.NORTH);
add(list, BorderLayout.CENTER);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction);
MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(getPanelName(), list));
}

View File

@ -13,11 +13,13 @@ import java.awt.event.MouseEvent;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
@ -53,7 +55,11 @@ public class RenamePanel extends FileBotPanel {
JList list1 = namesList.getListComponent();
JList list2 = filesList.getListComponent();
new SelectionSynchronizer(list1, list2);
ListSelectionModel selectionModel = new DefaultListSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
namesList.getListComponent().setSelectionModel(selectionModel);
filesList.getListComponent().setSelectionModel(selectionModel);
viewPortSynchroniser = new ViewPortSynchronizer((JViewport) list1.getParent(), (JViewport) list2.getParent());

View File

@ -1,70 +0,0 @@
package net.sourceforge.filebot.ui.panel.rename;
import javax.swing.JList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
class SelectionSynchronizer {
private final JList list1;
private final JList list2;
private final SelectionSynchronizeListener selectionSynchronizeListener1;
private final SelectionSynchronizeListener selectionSynchronizeListener2;
public SelectionSynchronizer(JList list1, JList list2) {
this.list1 = list1;
this.list2 = list2;
selectionSynchronizeListener1 = new SelectionSynchronizeListener(list2);
selectionSynchronizeListener2 = new SelectionSynchronizeListener(list1);
setEnabled(true);
}
public void setEnabled(boolean enabled) {
// remove listeners to avoid adding them multiple times
list1.removeListSelectionListener(selectionSynchronizeListener1);
list2.removeListSelectionListener(selectionSynchronizeListener2);
// if enabled add them again
if (enabled) {
list1.addListSelectionListener(selectionSynchronizeListener1);
list2.addListSelectionListener(selectionSynchronizeListener2);
}
}
private static class SelectionSynchronizeListener implements ListSelectionListener {
private JList target;
public SelectionSynchronizeListener(JList target) {
this.target = target;
}
public void valueChanged(ListSelectionEvent e) {
JList source = (JList) e.getSource();
int index = source.getSelectedIndex();
if (target.getModel().getSize() > index) {
if (index != target.getSelectedIndex()) {
target.setSelectedIndex(index);
}
target.ensureIndexIsVisible(index);
} else {
target.clearSelection();
}
}
}
}

View File

@ -30,6 +30,7 @@ import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
import net.sourceforge.tuned.ui.SimpleListModel;
import net.sourceforge.tuned.ui.TunedUtil;
public class ValidateNamesDialog extends JDialog {
@ -83,13 +84,12 @@ public class ValidateNamesDialog extends JDialog {
c.add(listPanel, BorderLayout.CENTER);
c.add(buttonBox, BorderLayout.SOUTH);
setLocation(FileBotUtil.getPreferredLocation(this));
setLocation(TunedUtil.getPreferredLocation(this));
setPreferredSize(new Dimension(365, 280));
pack();
// Shortcut Escape
FileBotUtil.registerActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
TunedUtil.registerActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction);
}

View File

@ -9,19 +9,25 @@ import net.sourceforge.filebot.FileFormat;
public class FileEntry extends AbstractFileEntry<File> {
private final long length;
private final String type;
public FileEntry(File file) {
super(FileFormat.getFileName(file), file);
this.length = file.length();
this.type = FileFormat.getFileType(file);
}
@Override
public long getLength() {
return getValue().length();
return length;
}
public String getType() {
return FileFormat.getFileType(getValue());
return type;
}
}

View File

@ -12,10 +12,11 @@ import net.sourceforge.filebot.web.EpisodeListClient;
class FetchEpisodeListTask extends SwingWorker<List<Episode>, Object> {
private String showName;
private EpisodeListClient searchEngine;
private int numberOfSeason;
private long duration;
private final String showName;
private final EpisodeListClient searchEngine;
private final int numberOfSeason;
private long duration = -1;
public FetchEpisodeListTask(EpisodeListClient searchEngine, String showname, int numberOfSeason) {

View File

@ -10,6 +10,7 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -45,13 +46,14 @@ import net.sourceforge.tuned.ui.SelectButton;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TextCompletion;
import net.sourceforge.tuned.ui.TextFieldWithSelect;
import net.sourceforge.tuned.ui.TunedUtil;
public class SearchPanel extends FileBotPanel {
private JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
private HistoryPanel historyPanel = new HistoryPanel("Show", "Number of Episodes");
private HistoryPanel historyPanel = new HistoryPanel();
private SpinnerNumberModel seasonSpinnerModel = new SpinnerNumberModel(SeasonSpinnerEditor.ALL_SEASONS, SeasonSpinnerEditor.ALL_SEASONS, Integer.MAX_VALUE, 1);
@ -77,6 +79,10 @@ public class SearchPanel extends FileBotPanel {
searchFieldCompletion.addTerms(Settings.getSettings().getStringList(Settings.SEARCH_HISTORY));
searchFieldCompletion.hook();
historyPanel.setColumnHeader1("Show");
historyPanel.setColumnHeader2("Number of Episodes");
historyPanel.setColumnHeader3("Duration");
JPanel mainPanel = new JPanel(new BorderLayout(5, 5));
Box searchBox = Box.createHorizontalBox();
@ -114,11 +120,9 @@ public class SearchPanel extends FileBotPanel {
this.add(mainPanel, BorderLayout.CENTER);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("UP"), upAction);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("DOWN"), downAction);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1));
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1));
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("UP"), upAction);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("DOWN"), downAction);
}
@ -206,22 +210,6 @@ public class SearchPanel extends FileBotPanel {
};
private class SpinClientAction extends AbstractAction {
private int spin;
public SpinClientAction(int spin) {
this.spin = spin;
}
public void actionPerformed(ActionEvent e) {
searchField.getSelectButton().spinValue(spin);
}
}
private class SearchTask extends SwingWorker<List<String>, Object> {
private String query;
@ -366,7 +354,7 @@ public class SearchPanel extends FileBotPanel {
String info = (episodes.size() > 0) ? String.format("%d episodes", episodes.size()) : "No episodes found";
historyPanel.add(episodeList.getTitle(), url, info, task.getDuration(), episodeList.getIcon());
historyPanel.add(episodeList.getTitle(), url, episodeList.getIcon(), info, NumberFormat.getInstance().format(task.getDuration()) + " ms");
if (episodes.size() <= 0)
tabbedPane.remove(episodeList);

View File

@ -17,7 +17,6 @@ import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotPanel;
@ -26,6 +25,7 @@ import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.filebot.ui.transfer.SaveAction;
import net.sourceforge.tuned.MessageBus;
import net.sourceforge.tuned.ui.TunedUtil;
public class SfvPanel extends FileBotPanel {
@ -61,7 +61,7 @@ public class SfvPanel extends FileBotPanel {
add(southPanel, BorderLayout.SOUTH);
// Shortcut DELETE
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction);
MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(getPanelName(), sfvTable));
}

View File

@ -6,6 +6,7 @@ import java.awt.BorderLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
@ -38,13 +39,14 @@ import net.sourceforge.tuned.ui.SelectButton;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TextCompletion;
import net.sourceforge.tuned.ui.TextFieldWithSelect;
import net.sourceforge.tuned.ui.TunedUtil;
public class SubtitlePanel extends FileBotPanel {
private JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
private HistoryPanel historyPanel = new HistoryPanel("Show / Movie", "Number of Subtitles");
private HistoryPanel historyPanel = new HistoryPanel();
private TextFieldWithSelect<SubtitleClient> searchField;
@ -67,6 +69,10 @@ public class SubtitlePanel extends FileBotPanel {
searchFieldCompletion.addTerms(Settings.getSettings().getStringList(Settings.SUBTITLE_HISTORY));
searchFieldCompletion.hook();
historyPanel.setColumnHeader1("Show / Movie");
historyPanel.setColumnHeader2("Number of Subtitles");
historyPanel.setColumnHeader3("Duration");
JPanel mainPanel = new JPanel(new BorderLayout(5, 5));
Box searchBox = Box.createHorizontalBox();
@ -99,25 +105,7 @@ public class SubtitlePanel extends FileBotPanel {
this.add(mainPanel, BorderLayout.CENTER);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1));
FileBotUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1));
}
private class SpinClientAction extends AbstractAction {
private int spin;
public SpinClientAction(int spin) {
this.spin = spin;
}
public void actionPerformed(ActionEvent e) {
searchField.getSelectButton().spinValue(spin);
}
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
}
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
@ -251,25 +239,6 @@ public class SubtitlePanel extends FileBotPanel {
}
private class FetchSubtitleListTask extends SwingWorker<List<? extends SubtitleDescriptor>, Object> {
private final SubtitleClient client;
private final MovieDescriptor descriptor;
public FetchSubtitleListTask(MovieDescriptor descriptor, SubtitleClient client) {
this.descriptor = descriptor;
this.client = client;
}
@Override
protected List<? extends SubtitleDescriptor> doInBackground() throws Exception {
return client.getSubtitleList(descriptor);
}
}
private class FetchSubtitleListTaskListener extends SwingWorkerPropertyChangeAdapter {
private final SubtitleListPanel subtitleSearchResultPanel;
@ -295,14 +264,14 @@ public class SubtitlePanel extends FileBotPanel {
String info = (subtitleDescriptors.size() > 0) ? String.format("%d subtitles", subtitleDescriptors.size()) : "No subtitles found";
historyPanel.add(task.descriptor.toString(), null, info, 0, task.client.getIcon());
historyPanel.add(task.getDescriptor().toString(), null, task.getClient().getIcon(), info, NumberFormat.getInstance().format(task.getDuration()) + " ms");
if (subtitleDescriptors.isEmpty()) {
tabbedPane.remove(subtitleSearchResultPanel);
return;
}
tabComponent.setIcon(task.client.getIcon());
tabComponent.setIcon(task.getClient().getIcon());
//TODO icon view
//TODO sysout

View File

@ -7,15 +7,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager;
import redstone.xmlrpc.XmlRpcClient;
import redstone.xmlrpc.XmlRpcException;
import redstone.xmlrpc.XmlRpcFault;
@ -25,18 +21,7 @@ import redstone.xmlrpc.XmlRpcFault;
* Client for the OpenSubtitles XML-RPC API.
*
*/
public class OpenSubtitlesClient extends SubtitleClient {
@Override
public List<MovieDescriptor> search(String query) throws Exception {
return searchMoviesOnIMDB(query);
}
@Override
public List<OpenSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws Exception {
return searchSubtitles(descriptor.getImdbId());
}
public class OpenSubtitlesClient {
/**
* <table>
@ -52,65 +37,57 @@ public class OpenSubtitlesClient extends SubtitleClient {
*/
private String url = "http://www.opensubtitles.org/xml-rpc";
private String username = "";
private String password = "";
private String language = "en";
private String useragent = String.format("%s v%s", Settings.NAME, Settings.VERSION);
private String useragent;
private String token = null;
private Timer keepAliveDaemon = null;
/**
* Interval to call NoOperation to keep the session from expiring
*/
private static final int KEEP_ALIVE_INTERVAL = 12 * 60 * 1000; // 12 minutes
public OpenSubtitlesClient() {
super("OpenSubtitles", ResourceManager.getIcon("search.opensubtitles"));
public OpenSubtitlesClient(String useragent) {
this.useragent = useragent;
}
public void login(String username, String password) throws XmlRpcFault {
login(username, password, "en");
}
/**
* This will login user. This method should be called always when starting talking with
* server.
*
* @param username blank for anonymous user.
* @param password blank for anonymous user.
* @param language <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO639</a>
* 2 letter codes as language and later communication will be done in this
* language if applicable (error codes and so on).
*/
@SuppressWarnings("unchecked")
private synchronized void activate() throws XmlRpcFault {
if (isActive())
return;
public synchronized void login(String username, String password, String language) throws XmlRpcFault {
Map<String, String> response = (Map<String, String>) invoke("LogIn", username, password, language, useragent);
checkStatus(response.get("status"));
token = response.get("token");
keepAliveDaemon = new Timer(getClass().getSimpleName() + " Keepalive", true);
keepAliveDaemon.schedule(new KeepAliveTimerTask(), KEEP_ALIVE_INTERVAL, KEEP_ALIVE_INTERVAL);
}
@SuppressWarnings("unchecked")
private synchronized void deactivate() {
if (!isActive())
return;
public synchronized void logout() {
// anonymous users will always get a 401 Unauthorized when trying to logout
if (!username.isEmpty()) {
try {
Map<String, String> response = (Map<String, String>) invoke("LogOut", token);
checkStatus(response.get("status"));
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, "Exception while deactivating connection", e);
}
try {
Map<String, String> response = (Map<String, String>) invoke("LogOut", token);
checkStatus(response.get("status"));
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, "Exception while deactivating session", e);
}
token = null;
keepAliveDaemon.cancel();
keepAliveDaemon = null;
}
private boolean isActive() {
public synchronized boolean isLoggedOn() {
return token != null;
}
@ -151,15 +128,12 @@ public class OpenSubtitlesClient extends SubtitleClient {
*/
@SuppressWarnings("unchecked")
public Map<String, String> getServerInfo() throws XmlRpcFault {
activate();
return (Map<String, String>) invoke("ServerInfo", token);
}
@SuppressWarnings("unchecked")
public List<OpenSubtitleDescriptor> searchSubtitles(int... imdbidArray) throws XmlRpcFault {
activate();
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int... imdbidArray) throws XmlRpcFault {
List<Map<String, String>> imdbidList = new ArrayList<Map<String, String>>(imdbidArray.length);
@ -174,14 +148,14 @@ public class OpenSubtitlesClient extends SubtitleClient {
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, imdbidList);
ArrayList<OpenSubtitleDescriptor> subs = new ArrayList<OpenSubtitleDescriptor>();
ArrayList<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
if (!(response.get("data") instanceof List))
throw new XmlRpcException("Illegal response: " + response.toString());
// if there was an error data may not be a list
for (Map<String, String> subtitle : response.get("data")) {
subs.add(new OpenSubtitleDescriptor(subtitle));
subs.add(new OpenSubtitlesSubtitleDescriptor(subtitle));
}
return subs;
@ -190,7 +164,6 @@ public class OpenSubtitlesClient extends SubtitleClient {
@SuppressWarnings("unchecked")
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
activate();
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchMoviesOnIMDB", token, query);
@ -207,30 +180,13 @@ public class OpenSubtitlesClient extends SubtitleClient {
@SuppressWarnings("unchecked")
public boolean noOperation() {
try {
activate();
Map<String, String> response = (Map<String, String>) invoke("NoOperation", token);
checkStatus(response.get("status"));
return true;
} catch (Exception e) {
deactivate();
return false;
}
}
private class KeepAliveTimerTask extends TimerTask {
@Override
public void run() {
if (noOperation()) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Connection is OK");
} else {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Connection lost");
deactivate();
}
};
};
}

View File

@ -5,7 +5,8 @@ package net.sourceforge.filebot.web;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.nio.LongBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
@ -23,11 +24,6 @@ public class OpenSubtitlesHasher {
*/
private static final int HASH_CHUNK_SIZE = 64 * 1024;
/**
* Size of the checksum in bytes (64 Bit)
*/
private static final int HASH_SIZE = 8;
public static String computeHash(File file) throws IOException {
long size = file.length();
@ -35,63 +31,26 @@ public class OpenSubtitlesHasher {
FileChannel fileChannel = new FileInputStream(file).getChannel();
BigInteger head = computeHashForChunk(fileChannel, 0, chunkSizeForFile);
BigInteger tail = computeHashForChunk(fileChannel, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile);
long head = computeHashForChunk(fileChannel, 0, chunkSizeForFile);
long tail = computeHashForChunk(fileChannel, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile);
fileChannel.close();
// size + head + tail
BigInteger bigHash = BigInteger.valueOf(size).add(head.add(tail));
byte[] hash = getTrailingBytes(bigHash.toByteArray(), HASH_SIZE);
return String.format("%0" + HASH_SIZE * 2 + "x", new BigInteger(1, hash));
return String.format("%016x", size + head + tail);
}
private static BigInteger computeHashForChunk(FileChannel fileChannel, long start, long size) throws IOException {
MappedByteBuffer buffer = fileChannel.map(MapMode.READ_ONLY, start, size);
private static long computeHashForChunk(FileChannel fileChannel, long start, long size) throws IOException {
MappedByteBuffer byteBuffer = fileChannel.map(MapMode.READ_ONLY, start, size);
BigInteger bigHash = BigInteger.ZERO;
byte[] bytes = new byte[HASH_SIZE];
LongBuffer longBuffer = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
long hash = 0;
while (buffer.hasRemaining()) {
buffer.get(bytes, 0, Math.min(HASH_SIZE, buffer.remaining()));
// BigInteger expects a big-endian byte-order, so we reverse the byte array
bigHash = bigHash.add(new BigInteger(1, reverse(bytes)));
while (longBuffer.hasRemaining()) {
hash += longBuffer.get();
}
return bigHash;
}
/**
* copy the last n bytes to a new array
*
* @param bytes original array
* @param n number of trailing bytes
* @return new array
*/
private static byte[] getTrailingBytes(byte[] src, int n) {
int length = Math.min(src.length, n);
byte[] dest = new byte[length];
int offsetSrc = Math.max(src.length - n, 0);
System.arraycopy(src, offsetSrc, dest, 0, length);
return dest;
}
private static byte[] reverse(byte[] bytes) {
byte[] reverseBytes = new byte[bytes.length];
for (int forward = 0, backward = bytes.length; forward < bytes.length; ++forward)
reverseBytes[forward] = bytes[--backward];
return reverseBytes;
return hash;
}
}

View File

@ -0,0 +1,117 @@
package net.sourceforge.filebot.web;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager;
/**
* Client for the OpenSubtitles XML-RPC API.
*
*/
public class OpenSubtitlesSubtitleClient extends SubtitleClient {
private final OpenSubtitlesClient client = new OpenSubtitlesClient(String.format("%s v%s", Settings.NAME, Settings.VERSION));
private final LogoutTimer logoutTimer = new LogoutTimer();
public OpenSubtitlesSubtitleClient() {
super("OpenSubtitles", ResourceManager.getIcon("search.opensubtitles"));
Runtime.getRuntime().addShutdownHook(new Thread(doLogout));
}
@Override
public List<MovieDescriptor> search(String query) throws Exception {
activate();
return client.searchMoviesOnIMDB(query);
}
@Override
public List<OpenSubtitlesSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws Exception {
activate();
return client.searchSubtitles(descriptor.getImdbId());
}
private synchronized void activate() {
try {
if (!client.isLoggedOn()) {
client.login("", "");
}
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
}
logoutTimer.restart();
}
private final Runnable doLogout = new Runnable() {
public void run() {
logoutTimer.stop();
if (client.isLoggedOn()) {
client.logout();
}
}
};
private class LogoutTimer {
private static final int LOGOUT_DELAY = 15000; // 12 minutes
private Timer daemon = null;
private LogoutTimerTask currentTimerTask = null;
public synchronized void restart() {
if (daemon == null) {
daemon = new Timer(getClass().getName(), true);
}
if (currentTimerTask != null) {
currentTimerTask.cancel();
daemon.purge();
}
currentTimerTask = new LogoutTimerTask();
daemon.schedule(currentTimerTask, LOGOUT_DELAY);
}
public synchronized void stop() {
if (daemon == null)
return;
currentTimerTask.cancel();
currentTimerTask = null;
daemon.cancel();
daemon = null;
}
private class LogoutTimerTask extends TimerTask {
@Override
public void run() {
doLogout.run();
};
};
}
}

View File

@ -16,7 +16,7 @@ import net.sourceforge.tuned.DownloadTask;
*
* @see OpenSubtitlesClient
*/
public class OpenSubtitleDescriptor implements SubtitleDescriptor {
public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
private final Map<String, String> properties;
@ -57,7 +57,7 @@ public class OpenSubtitleDescriptor implements SubtitleDescriptor {
}
public OpenSubtitleDescriptor(Map<String, String> properties) {
public OpenSubtitlesSubtitleDescriptor(Map<String, String> properties) {
this.properties = properties;
}

View File

@ -25,14 +25,14 @@ import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class SubsceneClient extends SubtitleClient {
public class SubsceneSubtitleClient extends SubtitleClient {
private final Map<MovieDescriptor, URL> cache = Collections.synchronizedMap(new HashMap<MovieDescriptor, URL>());
private final String host = "subscene.com";
public SubsceneClient() {
public SubsceneSubtitleClient() {
super("Subscene", ResourceManager.getIcon("search.subscene"));
}

View File

@ -14,8 +14,8 @@ public abstract class SubtitleClient {
private static final List<SubtitleClient> registry = new ArrayList<SubtitleClient>();
static {
registry.add(new OpenSubtitlesClient());
registry.add(new SubsceneClient());
registry.add(new OpenSubtitlesSubtitleClient());
registry.add(new SubsceneSubtitleClient());
}

View File

@ -8,9 +8,11 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.border.Border;
import net.sourceforge.tuned.ui.SelectButton.Entry;
@ -40,6 +42,9 @@ public class TextFieldWithSelect<T> extends JPanel {
textfield.setColumns(20);
add(textfield, BorderLayout.CENTER);
add(selectButton, BorderLayout.WEST);
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1));
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1));
}
@ -71,4 +76,20 @@ public class TextFieldWithSelect<T> extends JPanel {
};
private class SpinClientAction extends AbstractAction {
private int spin;
public SpinClientAction(int spin) {
this.spin = spin;
}
public void actionPerformed(ActionEvent e) {
selectButton.spinValue(spin);
}
}
}

View File

@ -0,0 +1,35 @@
package net.sourceforge.tuned.ui;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.KeyStroke;
public class TunedUtil {
public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) {
Integer key = action.hashCode();
component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key);
component.getActionMap().put(key, action);
}
public static Point getPreferredLocation(JDialog dialog) {
Window owner = dialog.getOwner();
if (owner == null)
return new Point(120, 80);
Point p = owner.getLocation();
Dimension d = owner.getSize();
return new Point(p.x + d.width / 4, p.y + d.height / 7);
}
}