Changes:
* added TheTVDB support * added ehcache to libs (now used in TheTVDBClient and TVRageClient) * Season spinner will remember unlocked value Refactoring: * renamed HtmlUtil to WebRequest * added getDocument() convenience methods to WebRequest * added lots of static imports (XPathUtil, WebRequest, FileBotUtil, ...) * TheTVDBClient and TVRageClient will throw SeasonOutOfBoundsException if for illegal season numbers * XPathUtil will wrap NodeList with NodeListDecorator instead of creating a new ArrayList * added DOM convenience methods to XPathUtil for performance reasons * formatting of episode number in EpisodeListClient, EpisodeListRequestProcesser will take care of this * added initial size to some StringBuilders
This commit is contained in:
parent
0bed877344
commit
54b27e69b7
|
@ -111,6 +111,7 @@
|
|||
<copy todir="${dir.build}">
|
||||
<fileset dir="${dir.source}">
|
||||
<include name="**/*.png" />
|
||||
<include name="**/*.xml" />
|
||||
</fileset>
|
||||
</copy>
|
||||
</target>
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,136 @@
|
|||
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd">
|
||||
|
||||
<!--
|
||||
CacheManager Configuration
|
||||
==========================
|
||||
An ehcache.xml corresponds to a single CacheManager.
|
||||
|
||||
See instructions below or the ehcache schema (ehcache.xsd) on how to configure.
|
||||
|
||||
System property tokens can be specified in this file which are replaced when the configuration
|
||||
is loaded. For example multicastGroupPort=${multicastGroupPort} can be replaced with the
|
||||
System property either from an environment variable or a system property specified with a
|
||||
command line switch such as -DmulticastGroupPort=4446.
|
||||
|
||||
DiskStore configuration
|
||||
=======================
|
||||
|
||||
The diskStore element is optional. To turn off disk store path creation, comment out the diskStore
|
||||
element below.
|
||||
|
||||
Configure it if you have overflowToDisk or diskPersistent enabled for any cache.
|
||||
|
||||
If it is not configured, and a cache is created which requires a disk store, a warning will be
|
||||
issued and java.io.tmpdir will automatically be used.
|
||||
|
||||
diskStore has only one attribute - "path". It is the path to the directory where
|
||||
.data and .index files will be created.
|
||||
|
||||
If the path is one of the following Java System Property it is replaced by its value in the
|
||||
running VM. For backward compatibility these are not specified without being enclosed in the ${token}
|
||||
replacement syntax.
|
||||
|
||||
The following properties are translated:
|
||||
* user.home - User's home directory
|
||||
* user.dir - User's current working directory
|
||||
* java.io.tmpdir - Default temp file path
|
||||
* ehcache.disk.store.dir - A system property you would normally specify on the command line
|
||||
e.g. java -Dehcache.disk.store.dir=/u01/myapp/diskdir ...
|
||||
|
||||
Subdirectories can be specified below the property e.g. java.io.tmpdir/one
|
||||
-->
|
||||
<diskStore path="java.io.tmpdir/filebot-ehcache" />
|
||||
|
||||
<!--
|
||||
Cache configuration
|
||||
===================
|
||||
|
||||
The following attributes are required.
|
||||
|
||||
name:
|
||||
Sets the name of the cache. This is used to identify the cache. It must be unique.
|
||||
|
||||
maxElementsInMemory:
|
||||
Sets the maximum number of objects that will be created in memory
|
||||
|
||||
maxElementsOnDisk:
|
||||
Sets the maximum number of objects that will be maintained in the DiskStore
|
||||
The default value is zero, meaning unlimited.
|
||||
|
||||
eternal:
|
||||
Sets whether elements are eternal. If eternal, timeouts are ignored and the
|
||||
element is never expired.
|
||||
|
||||
overflowToDisk:
|
||||
Sets whether elements can overflow to disk when the memory store
|
||||
has reached the maxInMemory limit.
|
||||
|
||||
The following attributes and elements are optional.
|
||||
|
||||
timeToIdleSeconds:
|
||||
Sets the time to idle for an element before it expires.
|
||||
i.e. The maximum amount of time between accesses before an element expires
|
||||
Is only used if the element is not eternal.
|
||||
Optional attribute. A value of 0 means that an Element can idle for infinity.
|
||||
The default value is 0.
|
||||
|
||||
timeToLiveSeconds:
|
||||
Sets the time to live for an element before it expires.
|
||||
i.e. The maximum time between creation time and when an element expires.
|
||||
Is only used if the element is not eternal.
|
||||
Optional attribute. A value of 0 means that and Element can live for infinity.
|
||||
The default value is 0.
|
||||
|
||||
diskPersistent:
|
||||
Whether the disk store persists between restarts of the Virtual Machine.
|
||||
The default value is false.
|
||||
|
||||
diskExpiryThreadIntervalSeconds:
|
||||
The number of seconds between runs of the disk expiry thread. The default value
|
||||
is 120 seconds.
|
||||
|
||||
diskSpoolBufferSizeMB:
|
||||
This is the size to allocate the DiskStore for a spool buffer. Writes are made
|
||||
to this area and then asynchronously written to disk. The default size is 30MB.
|
||||
Each spool buffer is used only by its cache. If you get OutOfMemory errors consider
|
||||
lowering this value. To improve DiskStore performance consider increasing it. Trace level
|
||||
logging in the DiskStore will show if put back ups are occurring.
|
||||
|
||||
memoryStoreEvictionPolicy:
|
||||
Policy would be enforced upon reaching the maxElementsInMemory limit. Default
|
||||
policy is Least Recently Used (specified as LRU). Other policies available -
|
||||
First In First Out (specified as FIFO) and Less Frequently Used
|
||||
(specified as LFU)
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
Mandatory Default Cache configuration. These settings will be applied to caches
|
||||
created programmtically using CacheManager.add(String cacheName).
|
||||
-->
|
||||
<defaultCache
|
||||
maxElementsInMemory="100"
|
||||
eternal="false"
|
||||
timeToIdleSeconds="120"
|
||||
timeToLiveSeconds="120"
|
||||
overflowToDisk="false"
|
||||
maxElementsOnDisk="1000"
|
||||
diskPersistent="false"
|
||||
diskExpiryThreadIntervalSeconds="120"
|
||||
memoryStoreEvictionPolicy="LRU"
|
||||
/>
|
||||
|
||||
|
||||
<!--
|
||||
Simple memory cache named web. Time to live is 5 min. This cache is used by TheTVDBClient and TVRageClient.
|
||||
-->
|
||||
<cache name="web"
|
||||
maxElementsInMemory="40"
|
||||
eternal="false"
|
||||
timeToIdleSeconds="300"
|
||||
timeToLiveSeconds="300"
|
||||
diskPersistent="false"
|
||||
memoryStoreEvictionPolicy="FIFO"
|
||||
/>
|
||||
|
||||
</ehcache>
|
|
@ -14,12 +14,12 @@ import net.sourceforge.tuned.FileUtil;
|
|||
|
||||
public final class FileBotUtil {
|
||||
|
||||
public static final String getApplicationName() {
|
||||
public static String getApplicationName() {
|
||||
return "FileBot";
|
||||
};
|
||||
|
||||
|
||||
public static final String getApplicationVersion() {
|
||||
public static String getApplicationVersion() {
|
||||
return "1.9";
|
||||
};
|
||||
|
||||
|
@ -55,7 +55,7 @@ public final class FileBotUtil {
|
|||
|
||||
|
||||
public static String getEmbeddedChecksum(String string) {
|
||||
Matcher matcher = FileBotUtil.EMBEDDED_CHECKSUM_PATTERN.matcher(string);
|
||||
Matcher matcher = EMBEDDED_CHECKSUM_PATTERN.matcher(string);
|
||||
String embeddedChecksum = null;
|
||||
|
||||
// get last match
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
|
||||
package net.sourceforge.filebot;
|
||||
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
|
@ -21,10 +24,10 @@ public class Main {
|
|||
*/
|
||||
public static void main(String... args) {
|
||||
|
||||
setupLogging();
|
||||
|
||||
final ArgumentBean argumentBean = handleArguments(args);
|
||||
|
||||
setupLogging();
|
||||
|
||||
try {
|
||||
// UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
|
||||
// UIManager.setLookAndFeel("a03.swing.plaf.A03LookAndFeel");
|
||||
|
@ -82,7 +85,7 @@ public class Main {
|
|||
if (argumentBean.isClear()) {
|
||||
// clear preferences
|
||||
try {
|
||||
Preferences.userNodeForPackage(FileBotUtil.class).removeNode();
|
||||
Preferences.userNodeForPackage(Main.class).removeNode();
|
||||
} catch (BackingStoreException e) {
|
||||
Logger.getLogger("global").log(Level.SEVERE, e.toString(), e);
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
|
@ -69,7 +69,7 @@ public class Torrent {
|
|||
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
|
||||
List<?> pathList = (List<?>) fileMap.get("path");
|
||||
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
StringBuilder pathBuilder = new StringBuilder(80);
|
||||
String entryName = null;
|
||||
|
||||
Iterator<?> iterator = pathList.iterator();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
package net.sourceforge.filebot.ui;
|
||||
|
||||
|
||||
import static javax.swing.JTabbedPane.WRAP_TAB_LAYOUT;
|
||||
import static javax.swing.JTabbedPane.SCROLL_TAB_LAYOUT;
|
||||
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
|
||||
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;
|
||||
import static javax.swing.SwingConstants.TOP;
|
||||
|
@ -43,7 +43,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
|
||||
protected final JPanel tabbedPaneGroup = new JPanel(new MigLayout("nogrid, fill, insets 0"));
|
||||
|
||||
protected final JTabbedPane tabbedPane = new JTabbedPane(TOP, WRAP_TAB_LAYOUT);
|
||||
protected final JTabbedPane tabbedPane = new JTabbedPane(TOP, SCROLL_TAB_LAYOUT);
|
||||
|
||||
protected final HistoryPanel historyPanel = new HistoryPanel();
|
||||
|
||||
|
@ -96,7 +96,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
protected abstract LabelProvider<S> createSearchEngineLabelProvider();
|
||||
|
||||
|
||||
protected abstract RequestProcessor<?> createRequestProcessor();
|
||||
protected abstract RequestProcessor<?, E> createRequestProcessor();
|
||||
|
||||
|
||||
public EventList<String> getSearchHistory() {
|
||||
|
@ -104,13 +104,12 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
}
|
||||
|
||||
|
||||
private void search(RequestProcessor<?> requestProcessor) {
|
||||
private void search(RequestProcessor<?, E> requestProcessor) {
|
||||
FileBotTab<?> tab = requestProcessor.tab;
|
||||
Request request = requestProcessor.request;
|
||||
|
||||
tab.setTitle(requestProcessor.getTitle());
|
||||
tab.setLoading(true);
|
||||
tab.setIcon(searchTextField.getSelectButton().getLabelProvider().getIcon(request.getClient()));
|
||||
tab.setIcon(requestProcessor.getIcon());
|
||||
|
||||
tab.addTo(tabbedPane);
|
||||
|
||||
|
@ -133,141 +132,18 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
};
|
||||
|
||||
|
||||
protected class Request {
|
||||
private class SearchTask extends SwingWorker<Collection<? extends SearchResult>, Void> {
|
||||
|
||||
private final S client;
|
||||
private final String searchText;
|
||||
private final RequestProcessor<?, E> requestProcessor;
|
||||
|
||||
|
||||
public Request(S client, String searchText) {
|
||||
this.client = client;
|
||||
this.searchText = searchText;
|
||||
}
|
||||
|
||||
|
||||
public S getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected abstract class RequestProcessor<R extends Request> {
|
||||
|
||||
protected final R request;
|
||||
|
||||
private FileBotTab<JComponent> tab;
|
||||
|
||||
private SearchResult searchResult = null;
|
||||
|
||||
private long duration = 0;
|
||||
|
||||
|
||||
public RequestProcessor(R request, JComponent component) {
|
||||
this.request = request;
|
||||
this.tab = new FileBotTab<JComponent>(component);
|
||||
}
|
||||
|
||||
|
||||
public abstract Collection<SearchResult> search() throws Exception;
|
||||
|
||||
|
||||
public abstract Collection<E> fetch() throws Exception;
|
||||
|
||||
|
||||
public abstract void process(Collection<E> elements);
|
||||
|
||||
|
||||
public abstract URI getLink();
|
||||
|
||||
|
||||
public JComponent getComponent() {
|
||||
return tab.getComponent();
|
||||
}
|
||||
|
||||
|
||||
public SearchResult getSearchResult() {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
|
||||
private void setSearchResult(SearchResult searchResult) {
|
||||
this.searchResult = searchResult;
|
||||
}
|
||||
|
||||
|
||||
public String getStatusMessage(Collection<E> result) {
|
||||
return String.format("%d elements found", result.size());
|
||||
}
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
if (searchResult != null)
|
||||
return searchResult.getName();
|
||||
|
||||
return request.getSearchText();
|
||||
}
|
||||
|
||||
|
||||
protected SearchResult chooseSearchResult(Collection<SearchResult> searchResults) throws Exception {
|
||||
|
||||
switch (searchResults.size()) {
|
||||
case 0:
|
||||
Logger.getLogger("ui").warning(String.format("\"%s\" has not been found.", request.getSearchText()));
|
||||
return null;
|
||||
case 1:
|
||||
return searchResults.iterator().next();
|
||||
}
|
||||
|
||||
// check if an exact match has been found
|
||||
for (SearchResult searchResult : searchResults) {
|
||||
if (request.getSearchText().equalsIgnoreCase(searchResult.getName()))
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
// multiple results have been found, user must select one
|
||||
Window window = SwingUtilities.getWindowAncestor(AbstractSearchPanel.this);
|
||||
|
||||
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(window, searchResults);
|
||||
|
||||
configureSelectDialog(selectDialog);
|
||||
|
||||
selectDialog.setVisible(true);
|
||||
|
||||
// selected value or null if the dialog was canceled by the user
|
||||
return selectDialog.getSelectedValue();
|
||||
}
|
||||
|
||||
|
||||
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
|
||||
selectDialog.setIconImage(TunedUtil.getImage(searchTextField.getSelectButton().getLabelProvider().getIcon(request.getClient())));
|
||||
}
|
||||
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class SearchTask extends SwingWorker<Collection<SearchResult>, Void> {
|
||||
|
||||
private final RequestProcessor<?> requestProcessor;
|
||||
|
||||
|
||||
public SearchTask(RequestProcessor<?> requestProcessor) {
|
||||
public SearchTask(RequestProcessor<?, E> requestProcessor) {
|
||||
this.requestProcessor = requestProcessor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Collection<SearchResult> doInBackground() throws Exception {
|
||||
protected Collection<? extends SearchResult> doInBackground() throws Exception {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
|
@ -288,7 +164,7 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
|
||||
try {
|
||||
// choose search result
|
||||
requestProcessor.setSearchResult(requestProcessor.chooseSearchResult(get()));
|
||||
requestProcessor.setSearchResult(requestProcessor.chooseSearchResult(get(), SwingUtilities.getWindowAncestor(AbstractSearchPanel.this)));
|
||||
|
||||
if (requestProcessor.getSearchResult() == null) {
|
||||
tab.close();
|
||||
|
@ -313,16 +189,15 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class FetchTask extends SwingWorker<Collection<E>, Void> {
|
||||
|
||||
private final RequestProcessor<?> requestProcessor;
|
||||
private final RequestProcessor<?, E> requestProcessor;
|
||||
|
||||
|
||||
public FetchTask(RequestProcessor<?> requestProcessor) {
|
||||
public FetchTask(RequestProcessor<?, E> requestProcessor) {
|
||||
this.requestProcessor = requestProcessor;
|
||||
}
|
||||
|
||||
|
@ -342,7 +217,6 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
@Override
|
||||
public void done() {
|
||||
FileBotTab<?> tab = requestProcessor.tab;
|
||||
Request request = requestProcessor.request;
|
||||
|
||||
if (tab.isClosed())
|
||||
return;
|
||||
|
@ -353,8 +227,8 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
|
||||
requestProcessor.process(elements);
|
||||
|
||||
String title = requestProcessor.getSearchResult().toString();
|
||||
Icon icon = searchTextField.getSelectButton().getLabelProvider().getIcon(request.getClient());
|
||||
String title = requestProcessor.getTitle();
|
||||
Icon icon = requestProcessor.getIcon();
|
||||
String statusMessage = requestProcessor.getStatusMessage(elements);
|
||||
|
||||
historyPanel.add(title, requestProcessor.getLink(), icon, statusMessage, String.format("%,d ms", requestProcessor.getDuration()));
|
||||
|
@ -375,4 +249,123 @@ public abstract class AbstractSearchPanel<S, E> extends FileBotPanel {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
protected static class Request {
|
||||
|
||||
private final String searchText;
|
||||
|
||||
|
||||
public Request(String searchText) {
|
||||
this.searchText = searchText;
|
||||
}
|
||||
|
||||
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected abstract static class RequestProcessor<R extends Request, E> {
|
||||
|
||||
protected final R request;
|
||||
|
||||
private FileBotTab<JComponent> tab;
|
||||
|
||||
private SearchResult searchResult;
|
||||
|
||||
private long duration = 0;
|
||||
|
||||
|
||||
public RequestProcessor(R request, JComponent component) {
|
||||
this.request = request;
|
||||
this.tab = new FileBotTab<JComponent>(component);
|
||||
}
|
||||
|
||||
|
||||
public abstract Collection<? extends SearchResult> search() throws Exception;
|
||||
|
||||
|
||||
public abstract Collection<E> fetch() throws Exception;
|
||||
|
||||
|
||||
public abstract void process(Collection<E> elements);
|
||||
|
||||
|
||||
public abstract URI getLink();
|
||||
|
||||
|
||||
public JComponent getComponent() {
|
||||
return tab.getComponent();
|
||||
}
|
||||
|
||||
|
||||
public SearchResult getSearchResult() {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
|
||||
public void setSearchResult(SearchResult searchResult) {
|
||||
this.searchResult = searchResult;
|
||||
}
|
||||
|
||||
|
||||
public String getStatusMessage(Collection<E> result) {
|
||||
return String.format("%d elements found", result.size());
|
||||
}
|
||||
|
||||
|
||||
public String getTitle() {
|
||||
if (searchResult != null)
|
||||
return searchResult.getName();
|
||||
|
||||
return request.getSearchText();
|
||||
}
|
||||
|
||||
|
||||
public Icon getIcon() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected SearchResult chooseSearchResult(Collection<? extends SearchResult> searchResults, Window window) throws Exception {
|
||||
|
||||
switch (searchResults.size()) {
|
||||
case 0:
|
||||
Logger.getLogger("ui").warning(String.format("\"%s\" has not been found.", request.getSearchText()));
|
||||
return null;
|
||||
case 1:
|
||||
return searchResults.iterator().next();
|
||||
}
|
||||
|
||||
// check if an exact match has been found
|
||||
for (SearchResult searchResult : searchResults) {
|
||||
if (request.getSearchText().equalsIgnoreCase(searchResult.getName()))
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
// multiple results have been found, user must select one
|
||||
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(window, searchResults);
|
||||
|
||||
configureSelectDialog(selectDialog);
|
||||
|
||||
selectDialog.setVisible(true);
|
||||
|
||||
// selected value or null if the dialog was canceled by the user
|
||||
return selectDialog.getSelectedValue();
|
||||
}
|
||||
|
||||
|
||||
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
|
||||
selectDialog.setIconImage(TunedUtil.getImage(getIcon()));
|
||||
}
|
||||
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
package net.sourceforge.filebot.ui;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Image;
|
||||
|
@ -19,7 +21,6 @@ import javax.swing.border.EmptyBorder;
|
|||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanel;
|
||||
import net.sourceforge.filebot.ui.panel.episodelist.EpisodeListPanel;
|
||||
|
@ -42,7 +43,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
|
|||
|
||||
|
||||
public FileBotWindow() {
|
||||
super(FileBotUtil.getApplicationName());
|
||||
super(getApplicationName());
|
||||
|
||||
setLocationByPlatform(true);
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
package net.sourceforge.filebot.ui;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.LogRecord;
|
||||
|
@ -10,7 +12,6 @@ import javax.swing.Icon;
|
|||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.ui.notification.MessageNotification;
|
||||
import net.sourceforge.tuned.ui.notification.NotificationManager;
|
||||
|
@ -54,7 +55,7 @@ public class NotificationLoggingHandler extends Handler {
|
|||
|
||||
|
||||
private void show(String message, Icon icon, int timeout) {
|
||||
notificationManager.show(new MessageNotification(FileBotUtil.getApplicationName(), message, icon, timeout));
|
||||
notificationManager.show(new MessageNotification(getApplicationName(), message, icon, timeout));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public class SelectDialog<T> extends JDialog {
|
|||
private boolean valueSelected = false;
|
||||
|
||||
|
||||
public SelectDialog(Window owner, Collection<T> options) {
|
||||
public SelectDialog(Window owner, Collection<? extends T> options) {
|
||||
super(owner, "Select", ModalityType.DOCUMENT_MODAL);
|
||||
|
||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
|
|
|
@ -15,6 +15,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JSpinner;
|
||||
import javax.swing.KeyStroke;
|
||||
|
@ -33,6 +34,7 @@ import net.sourceforge.filebot.web.EpisodeListClient;
|
|||
import net.sourceforge.filebot.web.SearchResult;
|
||||
import net.sourceforge.filebot.web.TVDotComClient;
|
||||
import net.sourceforge.filebot.web.TVRageClient;
|
||||
import net.sourceforge.filebot.web.TheTVDBClient;
|
||||
import net.sourceforge.tuned.ui.LabelProvider;
|
||||
import net.sourceforge.tuned.ui.SelectButton;
|
||||
import net.sourceforge.tuned.ui.SimpleLabelProvider;
|
||||
|
@ -74,6 +76,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
|
||||
engines.add(new TVRageClient());
|
||||
engines.add(new AnidbClient());
|
||||
engines.add(new TheTVDBClient("58B4AA94C59AD656"));
|
||||
engines.add(new TVDotComClient());
|
||||
|
||||
return engines;
|
||||
|
@ -101,7 +104,11 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
EpisodeListClient client = searchTextField.getSelectButton().getSelectedValue();
|
||||
|
||||
// lock season spinner on "All Seasons" if client doesn't support fetching of single seasons
|
||||
seasonSpinnerModel.lock(!client.hasSingleSeasonSupport());
|
||||
if (!client.hasSingleSeasonSupport()) {
|
||||
seasonSpinnerModel.lock(ALL_SEASONS);
|
||||
} else {
|
||||
seasonSpinnerModel.unlock();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -161,17 +168,24 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
}
|
||||
|
||||
|
||||
private class EpisodeListRequest extends Request {
|
||||
protected static class EpisodeListRequest extends Request {
|
||||
|
||||
private final EpisodeListClient client;
|
||||
private final int season;
|
||||
|
||||
|
||||
public EpisodeListRequest(EpisodeListClient client, String searchText, int season) {
|
||||
super(client, searchText);
|
||||
super(searchText);
|
||||
this.client = client;
|
||||
this.season = season;
|
||||
}
|
||||
|
||||
|
||||
public EpisodeListClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
public int getSeason() {
|
||||
return season;
|
||||
}
|
||||
|
@ -179,7 +193,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
}
|
||||
|
||||
|
||||
private class EpisodeListRequestProcessor extends RequestProcessor<EpisodeListRequest> {
|
||||
protected static class EpisodeListRequestProcessor extends RequestProcessor<EpisodeListRequest, Episode> {
|
||||
|
||||
public EpisodeListRequestProcessor(EpisodeListRequest request) {
|
||||
super(request, new EpisodeListTab());
|
||||
|
@ -194,10 +208,37 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
|
||||
@Override
|
||||
public Collection<Episode> fetch() throws Exception {
|
||||
if (request.getSeason() != ALL_SEASONS)
|
||||
return request.getClient().getEpisodeList(getSearchResult(), request.getSeason());
|
||||
else
|
||||
return request.getClient().getEpisodeList(getSearchResult());
|
||||
Collection<Episode> episodes;
|
||||
|
||||
if (request.getSeason() != ALL_SEASONS) {
|
||||
episodes = request.getClient().getEpisodeList(getSearchResult(), request.getSeason());
|
||||
} else {
|
||||
episodes = request.getClient().getEpisodeList(getSearchResult());
|
||||
}
|
||||
|
||||
// find max. episode number string length
|
||||
int maxLength = 1;
|
||||
|
||||
for (Episode episode : episodes) {
|
||||
String num = episode.getEpisodeNumber();
|
||||
|
||||
if (num.matches("\\d+") && num.length() > maxLength) {
|
||||
maxLength = num.length();
|
||||
}
|
||||
}
|
||||
|
||||
// pad episode numbers with zeros (e.g. %02d) so all episode numbers have the same number of digits
|
||||
String format = "%0" + maxLength + "d";
|
||||
for (Episode episode : episodes) {
|
||||
|
||||
try {
|
||||
episode.setEpisodeNumber(String.format(format, Integer.parseInt(episode.getEpisodeNumber())));
|
||||
} catch (NumberFormatException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
|
@ -241,6 +282,12 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return request.getClient().getIcon();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
|
||||
super.configureSelectDialog(selectDialog);
|
||||
|
@ -250,7 +297,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
|||
}
|
||||
|
||||
|
||||
private static class EpisodeListTab extends FileBotList<Episode> {
|
||||
protected static class EpisodeListTab extends FileBotList<Episode> {
|
||||
|
||||
public EpisodeListTab() {
|
||||
// set export handler for episode list
|
||||
|
|
|
@ -9,13 +9,17 @@ public class SeasonSpinnerModel extends SpinnerNumberModel {
|
|||
|
||||
public static final int ALL_SEASONS = 0;
|
||||
|
||||
public static final int MAX_VALUE = 99;
|
||||
|
||||
private Number valueBeforeLock = null;
|
||||
|
||||
|
||||
public SeasonSpinnerModel() {
|
||||
super(ALL_SEASONS, ALL_SEASONS, Integer.MAX_VALUE, 1);
|
||||
super(ALL_SEASONS, ALL_SEASONS, MAX_VALUE, 1);
|
||||
}
|
||||
|
||||
|
||||
public Integer getSeason() {
|
||||
public int getSeason() {
|
||||
return getNumber().intValue();
|
||||
}
|
||||
|
||||
|
@ -25,18 +29,28 @@ public class SeasonSpinnerModel extends SpinnerNumberModel {
|
|||
|
||||
if (next < ALL_SEASONS)
|
||||
next = ALL_SEASONS;
|
||||
else if (next > MAX_VALUE)
|
||||
next = MAX_VALUE;
|
||||
|
||||
setValue(next);
|
||||
}
|
||||
|
||||
|
||||
public void lock(boolean lock) {
|
||||
if (lock) {
|
||||
setValue(ALL_SEASONS);
|
||||
setMaximum(ALL_SEASONS);
|
||||
} else {
|
||||
setMaximum(Integer.MAX_VALUE);
|
||||
}
|
||||
public void lock(int value) {
|
||||
valueBeforeLock = getNumber();
|
||||
setMinimum(value);
|
||||
setMaximum(value);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
|
||||
public void unlock() {
|
||||
setMinimum(ALL_SEASONS);
|
||||
setMaximum(MAX_VALUE);
|
||||
|
||||
if (valueBeforeLock != null) {
|
||||
setValue(valueBeforeLock);
|
||||
valueBeforeLock = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package net.sourceforge.filebot.ui.panel.rename;
|
|||
import static net.sourceforge.filebot.FileBotUtil.LIST_FILE_EXTENSIONS;
|
||||
import static net.sourceforge.filebot.FileBotUtil.TORRENT_FILE_EXTENSIONS;
|
||||
import static net.sourceforge.filebot.FileBotUtil.containsOnly;
|
||||
import static net.sourceforge.filebot.FileBotUtil.isInvalidFileName;
|
||||
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.io.BufferedReader;
|
||||
|
@ -19,7 +20,6 @@ import java.util.logging.Logger;
|
|||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.filebot.torrent.Torrent;
|
||||
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
||||
import net.sourceforge.filebot.ui.panel.rename.entry.StringEntry;
|
||||
|
@ -60,7 +60,7 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy {
|
|||
List<ListEntry> invalidEntries = new ArrayList<ListEntry>();
|
||||
|
||||
for (ListEntry entry : entries) {
|
||||
if (FileBotUtil.isInvalidFileName(entry.getName()))
|
||||
if (isInvalidFileName(entry.getName()))
|
||||
invalidEntries.add(entry);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
package net.sourceforge.filebot.ui.panel.rename;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtil.INVALID_CHARACTERS_PATTERN;
|
||||
import static net.sourceforge.filebot.FileBotUtil.validateFileName;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
|
@ -21,7 +24,6 @@ import javax.swing.JScrollPane;
|
|||
import javax.swing.KeyStroke;
|
||||
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
||||
import net.sourceforge.tuned.ui.ArrayListModel;
|
||||
|
@ -49,7 +51,7 @@ public class ValidateNamesDialog extends JDialog {
|
|||
JList list = new JList(new ArrayListModel(entries));
|
||||
list.setEnabled(false);
|
||||
|
||||
list.setCellRenderer(new HighlightListCellRenderer(FileBotUtil.INVALID_CHARACTERS_PATTERN, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4));
|
||||
list.setCellRenderer(new HighlightListCellRenderer(INVALID_CHARACTERS_PATTERN, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4));
|
||||
|
||||
JLabel label = new JLabel("Some names contain invalid characters:");
|
||||
|
||||
|
@ -94,8 +96,7 @@ public class ValidateNamesDialog extends JDialog {
|
|||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
for (ListEntry entry : entries) {
|
||||
String validatedName = FileBotUtil.validateFileName(entry.getName());
|
||||
entry.setName(validatedName);
|
||||
entry.setName(validateFileName(entry.getName()));
|
||||
}
|
||||
|
||||
setEnabled(false);
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
|
||||
import static net.sourceforge.filebot.FileBotUtil.getApplicationVersion;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -49,7 +52,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
|
|||
protected List<SubtitleClient> createSearchEngines() {
|
||||
List<SubtitleClient> engines = new ArrayList<SubtitleClient>(2);
|
||||
|
||||
engines.add(new OpenSubtitlesSubtitleClient());
|
||||
engines.add(new OpenSubtitlesSubtitleClient(String.format("%s v%s", getApplicationName(), getApplicationVersion())));
|
||||
engines.add(new SubsceneSubtitleClient());
|
||||
|
||||
return engines;
|
||||
|
@ -74,17 +77,24 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
|
|||
}
|
||||
|
||||
|
||||
private class SubtitleRequest extends Request {
|
||||
protected static class SubtitleRequest extends Request {
|
||||
|
||||
private final SubtitleClient client;
|
||||
private final Locale language;
|
||||
|
||||
|
||||
public SubtitleRequest(SubtitleClient client, String searchText, Locale language) {
|
||||
super(client, searchText);
|
||||
super(searchText);
|
||||
this.client = client;
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
|
||||
public SubtitleClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
|
||||
public Locale getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
@ -92,7 +102,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
|
|||
}
|
||||
|
||||
|
||||
private class SubtitleRequestProcessor extends RequestProcessor<SubtitleRequest> {
|
||||
protected static class SubtitleRequestProcessor extends RequestProcessor<SubtitleRequest, SubtitlePackage> {
|
||||
|
||||
public SubtitleRequestProcessor(SubtitleRequest request) {
|
||||
super(request, new SubtitleDownloadPanel());
|
||||
|
|
|
@ -57,7 +57,7 @@ public class FileTransferable implements Transferable {
|
|||
* @return line separated list of file URIs
|
||||
*/
|
||||
private String getUriList() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder(80 * files.size());
|
||||
|
||||
for (File file : files) {
|
||||
sb.append("file://" + file.toURI().getPath());
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
package net.sourceforge.filebot.ui.transfer;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.FileBotUtil.getApplicationName;
|
||||
import static net.sourceforge.filebot.FileBotUtil.validateFileName;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
|
@ -11,7 +14,6 @@ import java.io.IOException;
|
|||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.tuned.TemporaryFolder;
|
||||
|
||||
|
||||
|
@ -52,10 +54,10 @@ public class LazyTextFileTransferable implements Transferable {
|
|||
|
||||
private FileTransferable createFileTransferable() throws IOException {
|
||||
// remove invalid characters from file name
|
||||
String validFileName = FileBotUtil.validateFileName(defaultFileName);
|
||||
String validFileName = validateFileName(defaultFileName);
|
||||
|
||||
// create new temporary file in TEMP/APP_NAME [UUID]/dnd
|
||||
File temporaryFile = TemporaryFolder.getFolder(FileBotUtil.getApplicationName()).subFolder("dnd").createFile(validFileName);
|
||||
File temporaryFile = TemporaryFolder.getFolder(getApplicationName()).subFolder("dnd").createFile(validFileName);
|
||||
|
||||
// write text to file
|
||||
FileChannel fileChannel = new FileOutputStream(temporaryFile).getChannel();
|
||||
|
|
|
@ -2,24 +2,26 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
|
||||
import static net.sourceforge.tuned.XPathUtil.exists;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNode;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNodes;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -44,19 +46,22 @@ public class AnidbClient implements EpisodeListClient {
|
|||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||
public List<SearchResult> search(String query) throws IOException, SAXException {
|
||||
|
||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||
// type=2 -> only TV Series
|
||||
URL searchUrl = new URL("http", host, "/perl-bin/animedb.pl?type=2&show=animelist&orderby.name=0.1&orderbar=0&noalias=1&do.search=Search&adb.search=" + URLEncoder.encode(query, "UTF-8"));
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='animelist']//TR/TD/ancestor::TR", dom);
|
||||
Document dom = getHtmlDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = selectNodes("//TABLE[@class='animelist']//TR/TD/ancestor::TR", dom);
|
||||
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
Node titleNode = XPathUtil.selectNode("./TD[@class='name']/A", node);
|
||||
Node titleNode = selectNode("./TD[@class='name']/A", node);
|
||||
|
||||
String title = XPathUtil.selectString(".", titleNode);
|
||||
String href = XPathUtil.selectString("@href", titleNode);
|
||||
String title = selectString(".", titleNode);
|
||||
String href = selectString("@href", titleNode);
|
||||
|
||||
String path = "/perl-bin/" + href;
|
||||
|
||||
|
@ -70,12 +75,12 @@ public class AnidbClient implements EpisodeListClient {
|
|||
// we might have been redirected to the episode list page
|
||||
if (searchResults.isEmpty()) {
|
||||
// check if current page contains an episode list
|
||||
if (XPathUtil.exists("//TABLE[@class='eplist']", dom)) {
|
||||
if (exists("//TABLE[@class='eplist']", dom)) {
|
||||
// get show's name from the document
|
||||
String header = XPathUtil.selectString("id('layout-content')//H1[1]", dom);
|
||||
String header = selectString("id('layout-content')//H1[1]", dom);
|
||||
String name = header.replaceFirst("Anime:\\s*", "");
|
||||
|
||||
String episodeListUrl = XPathUtil.selectString("id('layout-main')//DIV[@class='data']//A[@class='short_link']/@href", dom);
|
||||
String episodeListUrl = selectString("id('layout-main')//DIV[@class='data']//A[@class='short_link']/@href", dom);
|
||||
|
||||
try {
|
||||
searchResults.add(new HyperLink(name, new URL(episodeListUrl)));
|
||||
|
@ -92,33 +97,25 @@ public class AnidbClient implements EpisodeListClient {
|
|||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult) throws IOException, SAXException {
|
||||
|
||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult));
|
||||
Document dom = getHtmlDocument(getEpisodeListLink(searchResult).toURL());
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
|
||||
|
||||
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
||||
numberFormat.setGroupingUsed(false);
|
||||
List<Node> nodes = selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
|
||||
|
||||
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node);
|
||||
String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
|
||||
String number = selectString("./TD[contains(@class,'id')]/A", node);
|
||||
String title = selectString("./TD[@class='title']/LABEL/text()", node);
|
||||
|
||||
if (title.startsWith("recap"))
|
||||
if (title.startsWith("recap")) {
|
||||
title = title.replaceFirst("recap", "");
|
||||
|
||||
try {
|
||||
// try to format number of episode
|
||||
number = numberFormat.format(Integer.parseInt(number));
|
||||
|
||||
// no seasons for anime
|
||||
episodes.add(new Episode(searchResult.getName(), number, title));
|
||||
} catch (NumberFormatException ex) {
|
||||
// ignore node, episode is probably some kind of special (S1, S2, ...)
|
||||
}
|
||||
|
||||
// if number does not match, episode is probably some kind of special (S1, S2, ...)
|
||||
if (number.matches("\\d+")) {
|
||||
// no seasons for anime
|
||||
episodes.add(new Episode(searchResult.getName(), number, title));
|
||||
}
|
||||
}
|
||||
|
||||
return episodes;
|
||||
|
@ -148,14 +145,4 @@ public class AnidbClient implements EpisodeListClient {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
||||
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
||||
|
||||
// type=2 -> only TV Series
|
||||
String path = "/perl-bin/animedb.pl?type=2&show=animelist&orderby.name=0.1&orderbar=0&noalias=1&do.search=Search&adb.search=" + qs;
|
||||
|
||||
return new URL("http", host, path);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,34 +2,37 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
public class Episode {
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
public class Episode implements Serializable {
|
||||
|
||||
private final String showName;
|
||||
private final String numberOfSeason;
|
||||
private final String numberOfEpisode;
|
||||
private final String title;
|
||||
private String showName;
|
||||
private String seasonNumber;
|
||||
private String episodeNumber;
|
||||
private String title;
|
||||
|
||||
|
||||
public Episode(String showname, String numberOfSeason, String numberOfEpisode, String title) {
|
||||
this.showName = showname;
|
||||
this.numberOfSeason = numberOfSeason;
|
||||
this.numberOfEpisode = numberOfEpisode;
|
||||
public Episode(String showName, String seasonNumber, String episodeNumber, String title) {
|
||||
this.showName = showName;
|
||||
this.seasonNumber = seasonNumber;
|
||||
this.episodeNumber = episodeNumber;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
|
||||
public Episode(String showname, String numberOfEpisode, String title) {
|
||||
this(showname, null, numberOfEpisode, title);
|
||||
public Episode(String showName, String episodeNumber, String title) {
|
||||
this(showName, null, episodeNumber, title);
|
||||
}
|
||||
|
||||
|
||||
public String getNumberOfEpisode() {
|
||||
return numberOfEpisode;
|
||||
public String getEpisodeNumber() {
|
||||
return episodeNumber;
|
||||
}
|
||||
|
||||
|
||||
public String getNumberOfSeason() {
|
||||
return numberOfSeason;
|
||||
public String getSeasonNumber() {
|
||||
return seasonNumber;
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,16 +46,36 @@ public class Episode {
|
|||
}
|
||||
|
||||
|
||||
public void setShowName(String seriesName) {
|
||||
this.showName = seriesName;
|
||||
}
|
||||
|
||||
|
||||
public void setSeasonNumber(String seasonNumber) {
|
||||
this.seasonNumber = seasonNumber;
|
||||
}
|
||||
|
||||
|
||||
public void setEpisodeNumber(String episodeNumber) {
|
||||
this.episodeNumber = episodeNumber;
|
||||
}
|
||||
|
||||
|
||||
public void setTitle(String episodeName) {
|
||||
this.title = episodeName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
|
||||
sb.append(showName + " - ");
|
||||
|
||||
if (numberOfSeason != null)
|
||||
sb.append(numberOfSeason + "x");
|
||||
if (seasonNumber != null)
|
||||
sb.append(seasonNumber + "x");
|
||||
|
||||
sb.append(numberOfEpisode);
|
||||
sb.append(episodeNumber);
|
||||
|
||||
sb.append(" - " + title);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import javax.swing.Icon;
|
|||
|
||||
public interface EpisodeListClient {
|
||||
|
||||
public Collection<SearchResult> search(String searchterm) throws Exception;
|
||||
public Collection<SearchResult> search(String query) throws Exception;
|
||||
|
||||
|
||||
public boolean hasSingleSeasonSupport();
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.util.logging.Logger;
|
|||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.FileBotUtil;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.Timer;
|
||||
|
||||
|
@ -21,9 +20,14 @@ import net.sourceforge.tuned.Timer;
|
|||
*/
|
||||
public class OpenSubtitlesSubtitleClient implements SubtitleClient {
|
||||
|
||||
private final OpenSubtitlesClient client = new OpenSubtitlesClient(String.format("%s v%s", FileBotUtil.getApplicationName(), FileBotUtil.getApplicationVersion()));
|
||||
private final OpenSubtitlesClient client;
|
||||
|
||||
|
||||
public OpenSubtitlesSubtitleClient(String useragent) {
|
||||
this.client = new OpenSubtitlesClient(useragent);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "OpenSubtitles";
|
||||
|
@ -38,10 +42,10 @@ public class OpenSubtitlesSubtitleClient implements SubtitleClient {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<SearchResult> search(String searchterm) throws Exception {
|
||||
public List<SearchResult> search(String query) throws Exception {
|
||||
login();
|
||||
|
||||
return (List) client.searchMoviesOnIMDB(searchterm);
|
||||
return (List) client.searchMoviesOnIMDB(query);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
public class SeasonOutOfBoundsException extends IndexOutOfBoundsException {
|
||||
|
||||
private final String showName;
|
||||
private final int season;
|
||||
private final int maxSeason;
|
||||
|
||||
|
||||
public SeasonOutOfBoundsException(String showName, int season, int maxSeason) {
|
||||
this.showName = showName;
|
||||
this.season = season;
|
||||
this.maxSeason = maxSeason;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("%s has only %d season%s.", showName, maxSeason, maxSeason != 1 ? "s" : "");
|
||||
}
|
||||
|
||||
|
||||
public String getShowName() {
|
||||
return showName;
|
||||
}
|
||||
|
||||
|
||||
public int getSeason() {
|
||||
return season;
|
||||
}
|
||||
|
||||
|
||||
public int getMaxSeason() {
|
||||
return maxSeason;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,8 +2,12 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNode;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNodes;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
@ -24,7 +28,6 @@ import javax.swing.Icon;
|
|||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.FileUtil;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -51,18 +54,20 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||
public List<SearchResult> search(String query) throws IOException, SAXException {
|
||||
|
||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||
URL searchUrl = new URL("http", host, "/filmsearch.aspx?q=" + URLEncoder.encode(query, "UTF-8"));
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("id('filmSearch')/A", dom);
|
||||
Document dom = getHtmlDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = selectNodes("id('filmSearch')/A", dom);
|
||||
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String title = XPathUtil.selectString("text()", node);
|
||||
String href = XPathUtil.selectString("@href", node);
|
||||
String count = XPathUtil.selectString("./DFN", node).replaceAll("\\D+", "");
|
||||
String title = selectString("text()", node);
|
||||
String href = selectString("@href", node);
|
||||
String count = selectString("./DFN", node).replaceAll("\\D+", "");
|
||||
|
||||
try {
|
||||
URL subtitleListUrl = new URL("http", host, href);
|
||||
|
@ -82,10 +87,10 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
if (subtitleNodeCount > 0) {
|
||||
|
||||
// get name of current search result
|
||||
String name = XPathUtil.selectString("id('leftWrapperWide')//H1/text()", dom);
|
||||
String name = selectString("id('leftWrapperWide')//H1/text()", dom);
|
||||
|
||||
// get current location
|
||||
String file = XPathUtil.selectString("id('aspnetForm')/@action", dom);
|
||||
String file = selectString("id('aspnetForm')/@action", dom);
|
||||
|
||||
try {
|
||||
URL url = new URL("http", host, file);
|
||||
|
@ -103,15 +108,15 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
|
||||
private void updateLanguageFilterMap(Document subtitleListDocument) {
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("//DIV[@class='languageList']/DIV", subtitleListDocument);
|
||||
List<Node> nodes = selectNodes("//DIV[@class='languageList']/DIV", subtitleListDocument);
|
||||
|
||||
for (Node node : nodes) {
|
||||
String onClick = XPathUtil.selectString("./INPUT/@onclick", node);
|
||||
String onClick = selectString("./INPUT/@onclick", node);
|
||||
|
||||
String filter = new Scanner(onClick).findInLine("\\d+");
|
||||
|
||||
if (filter != null) {
|
||||
String name = XPathUtil.selectString("./LABEL/text()", node);
|
||||
String name = selectString("./LABEL/text()", node);
|
||||
|
||||
languageFilterMap.put(name.toLowerCase(), Integer.valueOf(filter));
|
||||
}
|
||||
|
@ -175,8 +180,7 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
|
||||
|
||||
private boolean useFilteredDocument(SearchResult searchResult) {
|
||||
SubsceneSearchResult sr = (SubsceneSearchResult) searchResult;
|
||||
return sr.getSubtitleCount() > 50;
|
||||
return ((SubsceneSearchResult) searchResult).getSubtitleCount() > 50;
|
||||
}
|
||||
|
||||
|
||||
|
@ -187,12 +191,12 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
connection.addRequestProperty("Cookie", "subscene_sLanguageIds=" + languageFilter);
|
||||
}
|
||||
|
||||
return HtmlUtil.getHtmlDocument(connection);
|
||||
return getHtmlDocument(connection);
|
||||
}
|
||||
|
||||
|
||||
private List<Node> getSubtitleNodes(Document subtitleListDocument) {
|
||||
return XPathUtil.selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", subtitleListDocument);
|
||||
return selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", subtitleListDocument);
|
||||
}
|
||||
|
||||
|
||||
|
@ -204,14 +208,14 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
|
||||
for (Node node : subtitleNodes) {
|
||||
try {
|
||||
Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node);
|
||||
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
|
||||
Node linkNode = selectNode("./TD[1]/A", node);
|
||||
String lang = selectString("./SPAN[1]", linkNode);
|
||||
|
||||
if (languageName == null || languageName.equalsIgnoreCase(lang)) {
|
||||
|
||||
String href = XPathUtil.selectString("@href", linkNode);
|
||||
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
|
||||
String author = XPathUtil.selectString("./TD[4]", node);
|
||||
String href = selectString("@href", linkNode);
|
||||
String name = selectString("./SPAN[2]", linkNode);
|
||||
String author = selectString("./TD[4]", node);
|
||||
|
||||
Matcher matcher = hrefPattern.matcher(href);
|
||||
|
||||
|
@ -247,13 +251,6 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
|||
return ((HyperLink) searchResult).toURI();
|
||||
}
|
||||
|
||||
|
||||
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
||||
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
||||
String file = "/filmsearch.aspx?q=" + qs;
|
||||
return new URL("http", host, file);
|
||||
}
|
||||
|
||||
|
||||
protected static class SubsceneSearchResult extends HyperLink {
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import javax.swing.Icon;
|
|||
|
||||
public interface SubtitleClient {
|
||||
|
||||
public Collection<SearchResult> search(String searchterm) throws Exception;
|
||||
public Collection<SearchResult> search(String query) throws Exception;
|
||||
|
||||
|
||||
public Collection<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception;
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNodes;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -23,7 +23,6 @@ import java.util.logging.Logger;
|
|||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -54,17 +53,20 @@ public class TVDotComClient implements EpisodeListClient {
|
|||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||
public List<SearchResult> search(String query) throws IOException, SAXException {
|
||||
|
||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||
// use ajax search request, because we don't need the whole search result page
|
||||
URL searchUrl = new URL("http", host, "/search.php?type=Search&stype=ajax_search&search_type=program&qs=" + URLEncoder.encode(query, "UTF-8"));
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("//H3[@class='title']/A", dom);
|
||||
Document dom = getHtmlDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = selectNodes("//H3[@class='title']/A", dom);
|
||||
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String title = node.getTextContent();
|
||||
String href = XPathUtil.selectString("@href", node);
|
||||
String href = selectString("@href", node);
|
||||
|
||||
try {
|
||||
URL episodeListingUrl = new URL(href.replaceFirst("summary.html\\?.*", "episode_listings.html"));
|
||||
|
@ -83,10 +85,10 @@ public class TVDotComClient implements EpisodeListClient {
|
|||
public List<Episode> getEpisodeList(SearchResult searchResult) throws Exception {
|
||||
|
||||
// get document for season 1
|
||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, 1));
|
||||
Document dom = getHtmlDocument(getEpisodeListLink(searchResult, 1).toURL());
|
||||
|
||||
// seasons are ordered in reverse, first element is latest season
|
||||
String latestSeasonString = XPathUtil.selectString("id('eps_table')//*[starts-with(text(),'Season:')]/*[1]/text()", dom);
|
||||
String latestSeasonString = selectString("id('eps_table')//*[starts-with(text(),'Season:')]/*[1]/text()", dom);
|
||||
|
||||
if (latestSeasonString.isEmpty()) {
|
||||
// assume single season series
|
||||
|
@ -129,7 +131,7 @@ public class TVDotComClient implements EpisodeListClient {
|
|||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
|
||||
|
||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
|
||||
Document dom = getHtmlDocument(getEpisodeListLink(searchResult, season).toURL());
|
||||
|
||||
return getEpisodeList(searchResult, season, dom);
|
||||
}
|
||||
|
@ -137,32 +139,31 @@ public class TVDotComClient implements EpisodeListClient {
|
|||
|
||||
private List<Episode> getEpisodeList(SearchResult searchResult, int seasonNumber, Document dom) {
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("id('eps_table')//TD[@class='ep_title']/parent::TR", dom);
|
||||
List<Node> nodes = selectNodes("id('eps_table')//TD[@class='ep_title']/parent::TR", dom);
|
||||
|
||||
// create mutable list from nodes so we can reverse the list
|
||||
nodes = new ArrayList<Node>(nodes);
|
||||
|
||||
// episodes are ordered in reverse ... we definitely don't want that!
|
||||
Collections.reverse(nodes);
|
||||
|
||||
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
Integer episodeOffset = null;
|
||||
|
||||
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String episode = XPathUtil.selectString("./TD[1]", node);
|
||||
String title = XPathUtil.selectString("./TD[2]//A", node);
|
||||
String season = Integer.toString(seasonNumber);
|
||||
String episode = selectString("./TD[1]", node);
|
||||
String title = selectString("./TD[2]//A", node);
|
||||
String season = String.valueOf(seasonNumber);
|
||||
|
||||
try {
|
||||
// format number of episode
|
||||
// convert the absolute episode number to the season episode number
|
||||
int n = Integer.parseInt(episode);
|
||||
|
||||
if (episodeOffset == null)
|
||||
episodeOffset = n - 1;
|
||||
|
||||
episode = numberFormat.format(n - episodeOffset);
|
||||
episode = String.valueOf(n - episodeOffset);
|
||||
} catch (NumberFormatException e) {
|
||||
// episode may be "Pilot", "Special", "TV Movie" ...
|
||||
season = null;
|
||||
|
@ -188,14 +189,6 @@ public class TVDotComClient implements EpisodeListClient {
|
|||
return URI.create(episodeListingUrl + "?season=" + season);
|
||||
}
|
||||
|
||||
|
||||
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
||||
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
||||
String file = "/search.php?type=Search&stype=ajax_search&search_type=program&qs=" + qs;
|
||||
|
||||
return new URL("http", host, file);
|
||||
}
|
||||
|
||||
|
||||
private class GetEpisodeList implements Callable<List<Episode>> {
|
||||
|
||||
|
|
|
@ -2,18 +2,28 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.getDocument;
|
||||
import static net.sourceforge.tuned.XPathUtil.getTextContent;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectInteger;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNodes;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -24,6 +34,8 @@ public class TVRageClient implements EpisodeListClient {
|
|||
|
||||
private static final String host = "www.tvrage.com";
|
||||
|
||||
private final Cache cache = CacheManager.getInstance().getCache("web");
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -44,20 +56,20 @@ public class TVRageClient implements EpisodeListClient {
|
|||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String searchterm) throws SAXException, IOException, ParserConfigurationException {
|
||||
public List<SearchResult> search(String query) throws SAXException, IOException, ParserConfigurationException {
|
||||
|
||||
String searchUri = String.format("http://" + host + "/feeds/search.php?show=" + URLEncoder.encode(searchterm, "UTF-8"));
|
||||
URL searchUrl = new URL("http", host, "/feeds/full_search.php?show=" + URLEncoder.encode(query, "UTF-8"));
|
||||
|
||||
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(searchUri);
|
||||
Document dom = getDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("Results/show", dom);
|
||||
List<Node> nodes = selectNodes("Results/show", dom);
|
||||
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
int showid = XPathUtil.selectInteger("showid", node);
|
||||
String name = XPathUtil.selectString("name", node);
|
||||
String link = XPathUtil.selectString("link", node);
|
||||
int showid = selectInteger("showid", node);
|
||||
String name = selectString("name", node);
|
||||
String link = selectString("link", node);
|
||||
|
||||
searchResults.add(new TVRageSearchResult(name, showid, link));
|
||||
}
|
||||
|
@ -66,25 +78,71 @@ public class TVRageClient implements EpisodeListClient {
|
|||
}
|
||||
|
||||
|
||||
private EpisodeListFeed getEpisodeListFeed(SearchResult searchResult) throws SAXException, IOException, ParserConfigurationException {
|
||||
int showId = ((TVRageSearchResult) searchResult).getShowId();
|
||||
String episodeListUri = String.format("http://" + host + "/feeds/episode_list.php?sid=" + showId);
|
||||
|
||||
Document dom = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(episodeListUri);
|
||||
|
||||
return new EpisodeListFeed(dom);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult) throws Exception {
|
||||
return getEpisodeListFeed(searchResult).getEpisodeList();
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult) throws IOException, SAXException, ParserConfigurationException {
|
||||
int showId = ((TVRageSearchResult) searchResult).getShowId();
|
||||
|
||||
URL episodeListUrl = new URL("http", host, "/feeds/episode_list.php?sid=" + showId);
|
||||
|
||||
// try to load from cache
|
||||
Element cacheEntry = cache.get(episodeListUrl.toString());
|
||||
|
||||
if (cacheEntry != null) {
|
||||
return (List<Episode>) cacheEntry.getValue();
|
||||
}
|
||||
|
||||
Document dom = getDocument(episodeListUrl);
|
||||
|
||||
String showName = selectString("Show/name", dom);
|
||||
List<Node> nodes = selectNodes("Show/Episodelist/Season/episode", dom);
|
||||
|
||||
List<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String title = getTextContent("title", node);
|
||||
String episodeNumber = getTextContent("seasonnum", node);
|
||||
String seasonNumber = node.getParentNode().getAttributes().getNamedItem("no").getTextContent();
|
||||
|
||||
episodes.add(new Episode(showName, seasonNumber, episodeNumber, title));
|
||||
}
|
||||
|
||||
// populate cache
|
||||
cache.put(new Element(episodeListUrl.toString(), episodes));
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException, ParserConfigurationException {
|
||||
return getEpisodeListFeed(searchResult).getEpisodeList(season);
|
||||
|
||||
List<Episode> episodes = new ArrayList<Episode>(25);
|
||||
|
||||
// remember max. season, so we can throw a proper exception, in case an illegal season number was requested
|
||||
int maxSeason = 0;
|
||||
|
||||
// filter given season from all seasons
|
||||
for (Episode episode : getEpisodeList(searchResult)) {
|
||||
try {
|
||||
int seasonNumber = Integer.parseInt(episode.getSeasonNumber());
|
||||
|
||||
if (season == seasonNumber) {
|
||||
episodes.add(episode);
|
||||
}
|
||||
|
||||
if (seasonNumber > maxSeason) {
|
||||
maxSeason = seasonNumber;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, "Illegal season number", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (episodes.isEmpty())
|
||||
throw new SeasonOutOfBoundsException(searchResult.getName(), season, maxSeason);
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
|
@ -96,7 +154,7 @@ public class TVRageClient implements EpisodeListClient {
|
|||
|
||||
@Override
|
||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||
return getEpisodeListLink(searchResult, Integer.toString(season));
|
||||
return getEpisodeListLink(searchResult, String.valueOf(season));
|
||||
}
|
||||
|
||||
|
||||
|
@ -107,7 +165,7 @@ public class TVRageClient implements EpisodeListClient {
|
|||
}
|
||||
|
||||
|
||||
protected static class TVRageSearchResult extends SearchResult {
|
||||
public static class TVRageSearchResult extends SearchResult {
|
||||
|
||||
private final int showId;
|
||||
private final String link;
|
||||
|
@ -131,63 +189,4 @@ public class TVRageClient implements EpisodeListClient {
|
|||
|
||||
}
|
||||
|
||||
|
||||
private static class EpisodeListFeed {
|
||||
|
||||
private final String name;
|
||||
|
||||
private final int totalSeasons;
|
||||
|
||||
private final Document feed;
|
||||
|
||||
|
||||
public EpisodeListFeed(Document feed) {
|
||||
name = XPathUtil.selectString("Show/name", feed);
|
||||
totalSeasons = XPathUtil.selectInteger("Show/totalseasons", feed);
|
||||
|
||||
this.feed = feed;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
public int getTotalSeasons() {
|
||||
return totalSeasons;
|
||||
}
|
||||
|
||||
|
||||
public List<Episode> getEpisodeList() {
|
||||
List<Episode> episodes = new ArrayList<Episode>(150);
|
||||
|
||||
for (int i = 0; i <= getTotalSeasons(); i++) {
|
||||
episodes.addAll(getEpisodeList(i));
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
public List<Episode> getEpisodeList(int season) {
|
||||
if (season > getTotalSeasons() || season < 0)
|
||||
throw new IndexOutOfBoundsException(String.format("%s has only %d season%s.", getName(), getTotalSeasons(), getTotalSeasons() != 1 ? "s" : ""));
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("//Season[@no='" + season + "']/episode", feed);
|
||||
|
||||
List<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
String numberOfSeason = Integer.toString(season);
|
||||
|
||||
for (Node node : nodes) {
|
||||
String title = XPathUtil.selectString("title", node);
|
||||
String episodeNumber = XPathUtil.selectString("seasonnum", node);
|
||||
|
||||
episodes.add(new Episode(getName(), numberOfSeason, episodeNumber, title));
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.getDocument;
|
||||
import static net.sourceforge.tuned.XPathUtil.getTextContent;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectInteger;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectNodes;
|
||||
import static net.sourceforge.tuned.XPathUtil.selectString;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
public class TheTVDBClient implements EpisodeListClient {
|
||||
|
||||
private static final String host = "www.thetvdb.com";
|
||||
|
||||
private final String apikey;
|
||||
|
||||
private final Map<MirrorType, String> mirrors = new EnumMap<MirrorType, String>(MirrorType.class);
|
||||
|
||||
private final TheTVDBCache cache = new TheTVDBCache(CacheManager.getInstance().getCache("web"));
|
||||
|
||||
|
||||
public TheTVDBClient(String apikey) {
|
||||
this.apikey = apikey;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TheTVDB";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.thetvdb");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean hasSingleSeasonSupport() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String query) throws Exception {
|
||||
return search(query, Locale.ENGLISH);
|
||||
}
|
||||
|
||||
|
||||
public List<SearchResult> search(String query, Locale language) throws Exception {
|
||||
|
||||
URL searchUrl = new URL("http", host, "/api/GetSeries.php?seriesname=" + URLEncoder.encode(query, "UTF-8") + "&language=" + language.getLanguage());
|
||||
|
||||
Document dom = getDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = selectNodes("Data/Series", dom);
|
||||
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
int seriesId = selectInteger("seriesid", node);
|
||||
String seriesName = selectString("SeriesName", node);
|
||||
|
||||
searchResults.add(new TheTVDBSearchResult(seriesName, seriesId));
|
||||
}
|
||||
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult) throws Exception {
|
||||
return getEpisodeList((TheTVDBSearchResult) searchResult, Locale.ENGLISH);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
||||
|
||||
List<Episode> episodes = new ArrayList<Episode>(25);
|
||||
|
||||
// remember max. season, so we can throw a proper exception, in case an illegal season number was requested
|
||||
int maxSeason = 0;
|
||||
|
||||
// filter given season from all seasons
|
||||
for (Episode episode : getEpisodeList(searchResult)) {
|
||||
try {
|
||||
int seasonNumber = Integer.parseInt(episode.getSeasonNumber());
|
||||
|
||||
if (season == seasonNumber) {
|
||||
episodes.add(episode);
|
||||
}
|
||||
|
||||
if (seasonNumber > maxSeason) {
|
||||
maxSeason = seasonNumber;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, "Illegal season number", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (episodes.isEmpty())
|
||||
throw new SeasonOutOfBoundsException(searchResult.getName(), season, maxSeason);
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
public List<Episode> getEpisodeList(TheTVDBSearchResult searchResult, Locale language) throws Exception {
|
||||
|
||||
List<Episode> episodes = cache.getEpisodeList(searchResult.getSeriesId(), language);
|
||||
|
||||
if (episodes != null)
|
||||
return episodes;
|
||||
|
||||
Document seriesRecord = getSeriesRecord(searchResult, language);
|
||||
|
||||
// we could get the series name from the search result, but the language may not match the given parameter
|
||||
String seriesName = selectString("Data/Series/SeriesName", seriesRecord);
|
||||
|
||||
List<Node> nodes = selectNodes("Data/Episode", seriesRecord);
|
||||
|
||||
episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
for (Node node : nodes) {
|
||||
String episodeName = getTextContent("EpisodeName", node);
|
||||
String episodeNumber = getTextContent("EpisodeNumber", node);
|
||||
String seasonNumber = getTextContent("SeasonNumber", node);
|
||||
|
||||
episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, episodeName));
|
||||
|
||||
if (episodeNumber.equals("1")) {
|
||||
// cache seasonId for each season (always when we are at the first episode)
|
||||
// because it might be required by getEpisodeListLink
|
||||
cache.putSeasonId(searchResult.getSeriesId(), Integer.parseInt(seasonNumber), Integer.parseInt(getTextContent("seasonid", node)));
|
||||
}
|
||||
}
|
||||
|
||||
cache.putEpisodeList(searchResult.getSeriesId(), language, episodes);
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
public Document getSeriesRecord(TheTVDBSearchResult searchResult, Locale language) throws IOException, SAXException, ParserConfigurationException {
|
||||
|
||||
URL seriesRecordUrl = new URL(getMirror(MirrorType.ZIP) + "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + language.getLanguage() + ".zip");
|
||||
|
||||
ZipInputStream zipInputStream = new ZipInputStream(seriesRecordUrl.openStream());
|
||||
ZipEntry zipEntry;
|
||||
|
||||
try {
|
||||
String seriesRecordName = language.getLanguage() + ".xml";
|
||||
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
if (seriesRecordName.equals(zipEntry.getName())) {
|
||||
return getDocument(zipInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
// zip file must contain the series record
|
||||
throw new FileNotFoundException(String.format("Archive must contain %s: %s", seriesRecordName, seriesRecordUrl));
|
||||
} finally {
|
||||
zipInputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||
|
||||
return URI.create("http://www.thetvdb.com/?tab=seasonall&id=" + seriesId);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||
|
||||
try {
|
||||
Integer seasonId = cache.getSeasonId(seriesId, season);
|
||||
|
||||
if (seasonId == null) {
|
||||
// get episode xml from first episode of given season
|
||||
Document dom = getDocument(new URL("http", host, "/api/" + apikey + "/series/" + seriesId + "/default/" + season + "/1/en.xml"));
|
||||
|
||||
seasonId = selectInteger("Data/Episode/seasonid", dom);
|
||||
|
||||
cache.putSeasonId(seriesId, season, seasonId);
|
||||
}
|
||||
|
||||
return new URI("http://www.thetvdb.com/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, "Failed to retrieve season id", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected String getMirror(MirrorType mirrorType) throws IOException, SAXException, ParserConfigurationException {
|
||||
synchronized (mirrors) {
|
||||
if (mirrors.isEmpty()) {
|
||||
// initialize mirrors
|
||||
URL mirrorUrl = new URL("http", host, "/api/" + apikey + "/mirrors.xml");
|
||||
|
||||
Document dom = getDocument(mirrorUrl);
|
||||
|
||||
// all mirrors by type
|
||||
Map<MirrorType, List<String>> mirrorListMap = new EnumMap<MirrorType, List<String>>(MirrorType.class);
|
||||
|
||||
// initialize mirror list per type
|
||||
for (MirrorType type : MirrorType.values()) {
|
||||
mirrorListMap.put(type, new ArrayList<String>(5));
|
||||
}
|
||||
|
||||
// traverse all mirrors
|
||||
for (Node node : selectNodes("Mirrors/Mirror", dom)) {
|
||||
// mirror data
|
||||
String mirror = selectString("mirrorpath", node);
|
||||
int typeMask = selectInteger("typemask", node);
|
||||
|
||||
// add mirror to the according type lists
|
||||
for (MirrorType type : MirrorType.fromTypeMask(typeMask)) {
|
||||
mirrorListMap.get(type).add(mirror);
|
||||
}
|
||||
}
|
||||
|
||||
// put random entry from each type list into mirrors
|
||||
Random random = new Random();
|
||||
|
||||
for (MirrorType type : MirrorType.values()) {
|
||||
List<String> list = mirrorListMap.get(type);
|
||||
|
||||
if (!list.isEmpty()) {
|
||||
mirrors.put(type, list.get(random.nextInt(list.size())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mirrors.get(mirrorType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class TheTVDBSearchResult extends SearchResult {
|
||||
|
||||
private final int seriesId;
|
||||
|
||||
|
||||
public TheTVDBSearchResult(String seriesName, int seriesId) {
|
||||
super(seriesName);
|
||||
this.seriesId = seriesId;
|
||||
}
|
||||
|
||||
|
||||
public int getSeriesId() {
|
||||
return seriesId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected static enum MirrorType {
|
||||
XML(1),
|
||||
BANNER(2),
|
||||
ZIP(4);
|
||||
|
||||
private final int bitMask;
|
||||
|
||||
|
||||
private MirrorType(int bitMask) {
|
||||
this.bitMask = bitMask;
|
||||
}
|
||||
|
||||
|
||||
public static EnumSet<MirrorType> fromTypeMask(int typeMask) {
|
||||
// initialize enum set with all types
|
||||
EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class);
|
||||
|
||||
for (MirrorType type : values()) {
|
||||
if ((typeMask & type.bitMask) == 0) {
|
||||
// remove types that are not set
|
||||
enumSet.remove(type);
|
||||
}
|
||||
}
|
||||
|
||||
return enumSet;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class TheTVDBCache {
|
||||
|
||||
private final Cache cache;
|
||||
|
||||
|
||||
public TheTVDBCache(Cache cache) {
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
|
||||
public void putSeasonId(int seriesId, int seasonNumber, int seasonId) {
|
||||
cache.put(new Element(key(host, seriesId, seasonNumber, "SeasonId"), seasonId));
|
||||
}
|
||||
|
||||
|
||||
public Integer getSeasonId(int seriesId, int seasonNumber) {
|
||||
Element element = cache.get(key(host, seriesId, seasonNumber, "SeasonId"));
|
||||
|
||||
if (element != null)
|
||||
return (Integer) element.getValue();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public void putEpisodeList(int seriesId, Locale language, List<Episode> episodes) {
|
||||
cache.put(new Element(key(host, seriesId, language, "EpisodeList"), episodes));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Episode> getEpisodeList(int seriesId, Locale language) {
|
||||
Element element = cache.get(key(host, seriesId, language.getLanguage(), "EpisodeList"));
|
||||
|
||||
if (element != null)
|
||||
return (List<Episode>) element.getValue();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private String key(Object... key) {
|
||||
return Arrays.toString(key);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -16,41 +15,17 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.cyberneko.html.parsers.DOMParser;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
public class HtmlUtil {
|
||||
public final class WebRequest {
|
||||
|
||||
private static Charset getCharset(String contentType) {
|
||||
if (contentType != null) {
|
||||
// e.g. Content-Type: text/html; charset=iso-8859-1
|
||||
Pattern pattern = Pattern.compile(".*;\\s*charset=(\\S+).*", Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(contentType);
|
||||
|
||||
if (matcher.matches()) {
|
||||
String charsetName = matcher.group(1);
|
||||
|
||||
try {
|
||||
return Charset.forName(charsetName);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use UTF-8 if charset cannot be determined
|
||||
return Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
|
||||
public static Document getHtmlDocument(URI uri) throws IOException, SAXException {
|
||||
return getHtmlDocument(uri.toURL());
|
||||
}
|
||||
|
||||
|
||||
public static Document getHtmlDocument(URL url) throws IOException, SAXException {
|
||||
return getHtmlDocument(url.openConnection());
|
||||
}
|
||||
|
@ -81,4 +56,45 @@ public class HtmlUtil {
|
|||
return parser.getDocument();
|
||||
}
|
||||
|
||||
|
||||
public static Document getDocument(URL url) throws SAXException, IOException, ParserConfigurationException {
|
||||
return getDocument(url.toString());
|
||||
}
|
||||
|
||||
|
||||
public static Document getDocument(String url) throws SAXException, IOException, ParserConfigurationException {
|
||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(url);
|
||||
}
|
||||
|
||||
|
||||
public static Document getDocument(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException {
|
||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream);
|
||||
}
|
||||
|
||||
|
||||
private static Charset getCharset(String contentType) {
|
||||
if (contentType != null) {
|
||||
// e.g. Content-Type: text/html; charset=iso-8859-1
|
||||
Pattern pattern = Pattern.compile(".*;\\s*charset=(\\S+).*", Pattern.CASE_INSENSITIVE);
|
||||
Matcher matcher = pattern.matcher(contentType);
|
||||
|
||||
if (matcher.matches()) {
|
||||
String charsetName = matcher.group(1);
|
||||
|
||||
try {
|
||||
return Charset.forName(charsetName);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger("global").log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use UTF-8 if charset cannot be determined
|
||||
return Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
|
||||
private WebRequest() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
package net.sourceforge.tuned;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
|
@ -25,31 +25,9 @@ public final class XPathUtil {
|
|||
}
|
||||
|
||||
|
||||
public static Node selectFirstNode(String xpath, Object node) {
|
||||
try {
|
||||
NodeList nodeList = (NodeList) getXPath(xpath).evaluate(node, XPathConstants.NODESET);
|
||||
|
||||
if (nodeList.getLength() <= 0)
|
||||
return null;
|
||||
|
||||
return nodeList.item(0);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<Node> selectNodes(String xpath, Object node) {
|
||||
try {
|
||||
NodeList nodeList = (NodeList) getXPath(xpath).evaluate(node, XPathConstants.NODESET);
|
||||
|
||||
ArrayList<Node> nodes = new ArrayList<Node>(nodeList.getLength());
|
||||
|
||||
for (int i = 0; i < nodeList.getLength(); i++) {
|
||||
nodes.add(nodeList.item(i));
|
||||
}
|
||||
|
||||
return nodes;
|
||||
return new NodeListDecorator((NodeList) getXPath(xpath).evaluate(node, XPathConstants.NODESET));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -65,6 +43,40 @@ public final class XPathUtil {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param nodeName search for nodes with this name
|
||||
* @param parentNode search in the child nodes of this nodes
|
||||
* @return text content of the child node or null if no child with the given name was found
|
||||
*/
|
||||
public static Node getChild(String nodeName, Node parentNode) {
|
||||
for (Node child : new NodeListDecorator(parentNode.getChildNodes())) {
|
||||
if (nodeName.equals(child.getNodeName()))
|
||||
return child;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get text content of the first child node matching the given node name. Use this method
|
||||
* instead of {@link #selectString(String, Object)} whenever xpath support is not required,
|
||||
* because it is much faster, especially for large documents.
|
||||
*
|
||||
* @param nodeName search for nodes with this name
|
||||
* @param parentNode search in the child nodes of this nodes
|
||||
* @return text content of the child node or null if no child with the given name was found
|
||||
*/
|
||||
public static String getTextContent(String nodeName, Node parentNode) {
|
||||
Node child = getChild(nodeName, parentNode);
|
||||
|
||||
if (child == null)
|
||||
return null;
|
||||
|
||||
return child.getTextContent();
|
||||
}
|
||||
|
||||
|
||||
public static int selectInteger(String xpath, Object node) {
|
||||
return Integer.parseInt(selectString(xpath, node));
|
||||
}
|
||||
|
@ -87,4 +99,28 @@ public final class XPathUtil {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
protected static class NodeListDecorator extends AbstractList<Node> {
|
||||
|
||||
private final NodeList nodes;
|
||||
|
||||
|
||||
public NodeListDecorator(NodeList nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Node get(int index) {
|
||||
return nodes.item(index);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return nodes.getLength();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -67,8 +67,8 @@ public class AnidbClientTest {
|
|||
|
||||
assertEquals("Monster", first.getShowName());
|
||||
assertEquals("Herr Dr. Tenma", first.getTitle());
|
||||
assertEquals("01", first.getNumberOfEpisode());
|
||||
assertEquals(null, first.getNumberOfSeason());
|
||||
assertEquals("1", first.getEpisodeNumber());
|
||||
assertEquals(null, first.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
@ -82,8 +82,8 @@ public class AnidbClientTest {
|
|||
|
||||
assertEquals("Juuni Kokuki", first.getShowName());
|
||||
assertEquals("Shadow of the Moon, The Sea of Shadow - Chapter 1", first.getTitle());
|
||||
assertEquals("01", first.getNumberOfEpisode());
|
||||
assertEquals(null, first.getNumberOfSeason());
|
||||
assertEquals("1", first.getEpisodeNumber());
|
||||
assertEquals(null, first.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -56,16 +56,16 @@ public class TVDotComClientTest {
|
|||
|
||||
@Test
|
||||
public void getEpisodeList() throws Exception {
|
||||
List<Episode> results = tvdotcom.getEpisodeList(buffySearchResult, 7);
|
||||
List<Episode> list = tvdotcom.getEpisodeList(buffySearchResult, 7);
|
||||
|
||||
assertEquals(22, results.size());
|
||||
assertEquals(22, list.size());
|
||||
|
||||
Episode chosen = results.get(21);
|
||||
Episode chosen = list.get(21);
|
||||
|
||||
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
|
||||
assertEquals("Chosen", chosen.getTitle());
|
||||
assertEquals("22", chosen.getNumberOfEpisode());
|
||||
assertEquals("7", chosen.getNumberOfSeason());
|
||||
assertEquals("22", chosen.getEpisodeNumber());
|
||||
assertEquals("7", chosen.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,8 +79,8 @@ public class TVDotComClientTest {
|
|||
|
||||
assertEquals("Buffy the Vampire Slayer", first.getShowName());
|
||||
assertEquals("Unaired Pilot", first.getTitle());
|
||||
assertEquals("Pilot", first.getNumberOfEpisode());
|
||||
assertEquals(null, first.getNumberOfSeason());
|
||||
assertEquals("Pilot", first.getEpisodeNumber());
|
||||
assertEquals(null, first.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
@ -94,8 +94,8 @@ public class TVDotComClientTest {
|
|||
|
||||
assertEquals("Firefly", fourth.getShowName());
|
||||
assertEquals("Jaynestown", fourth.getTitle());
|
||||
assertEquals("04", fourth.getNumberOfEpisode());
|
||||
assertEquals("1", fourth.getNumberOfSeason());
|
||||
assertEquals("4", fourth.getEpisodeNumber());
|
||||
assertEquals("1", fourth.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
@ -118,8 +118,8 @@ public class TVDotComClientTest {
|
|||
|
||||
assertEquals("Lost", episode.getShowName());
|
||||
assertEquals("Exposé", episode.getTitle());
|
||||
assertEquals("14", episode.getNumberOfEpisode());
|
||||
assertEquals("3", episode.getNumberOfSeason());
|
||||
assertEquals("14", episode.getEpisodeNumber());
|
||||
assertEquals("3", episode.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,8 +43,8 @@ public class TVRageClientTest {
|
|||
|
||||
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
|
||||
assertEquals("Chosen", chosen.getTitle());
|
||||
assertEquals("22", chosen.getNumberOfEpisode());
|
||||
assertEquals("7", chosen.getNumberOfSeason());
|
||||
assertEquals("22", chosen.getEpisodeNumber());
|
||||
assertEquals("7", chosen.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
|
@ -58,12 +58,12 @@ public class TVRageClientTest {
|
|||
|
||||
assertEquals("Buffy the Vampire Slayer", first.getShowName());
|
||||
assertEquals("Unaired Pilot", first.getTitle());
|
||||
assertEquals("00", first.getNumberOfEpisode());
|
||||
assertEquals("0", first.getNumberOfSeason());
|
||||
assertEquals("00", first.getEpisodeNumber());
|
||||
assertEquals("0", first.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = SeasonOutOfBoundsException.class)
|
||||
public void getEpisodeListIllegalSeason() throws Exception {
|
||||
tvrage.getEpisodeList(buffySearchResult, 42);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sourceforge.filebot.web.TheTVDBClient.MirrorType;
|
||||
import net.sourceforge.filebot.web.TheTVDBClient.TheTVDBSearchResult;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class TheTVDBClientTest {
|
||||
|
||||
private TheTVDBClient thetvdb = new TheTVDBClient("BA864DEE427E384A");
|
||||
|
||||
|
||||
@After
|
||||
public void clearCache() {
|
||||
CacheManager.getInstance().clearAll();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void search() throws Exception {
|
||||
// test default language and query escaping (blanks)
|
||||
List<SearchResult> results = thetvdb.search("babylon 5");
|
||||
|
||||
assertEquals(2, results.size());
|
||||
|
||||
TheTVDBSearchResult first = (TheTVDBSearchResult) results.get(0);
|
||||
|
||||
assertEquals("Babylon 5", first.getName());
|
||||
assertEquals(70726, first.getSeriesId());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void searchGerman() throws Exception {
|
||||
List<SearchResult> results = thetvdb.search("buffy", Locale.GERMAN);
|
||||
|
||||
assertEquals(3, results.size());
|
||||
|
||||
TheTVDBSearchResult first = (TheTVDBSearchResult) results.get(0);
|
||||
|
||||
// test encoding (umlauts)
|
||||
assertEquals("Buffy - Im Bann der Dämonen", first.getName());
|
||||
assertEquals(70327, first.getSeriesId());
|
||||
|
||||
TheTVDBSearchResult second = (TheTVDBSearchResult) results.get(1);
|
||||
|
||||
assertEquals("Buffy the Vampire Slayer", second.getName());
|
||||
assertEquals(70327, second.getSeriesId());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getEpisodeListAll() throws Exception {
|
||||
List<Episode> list = thetvdb.getEpisodeList(new TheTVDBSearchResult("Buffy the Vampire Slayer", 70327));
|
||||
|
||||
assertEquals(147, list.size());
|
||||
|
||||
Episode first = list.get(0);
|
||||
|
||||
assertEquals("Buffy the Vampire Slayer", first.getShowName());
|
||||
assertEquals("Unaired Pilot", first.getTitle());
|
||||
assertEquals("1", first.getEpisodeNumber());
|
||||
assertEquals("0", first.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getEpisodeListSingleSeason() throws Exception {
|
||||
List<Episode> list = thetvdb.getEpisodeList(new TheTVDBSearchResult("Buffy the Vampire Slayer", 70327), 7);
|
||||
|
||||
assertEquals(22, list.size());
|
||||
|
||||
Episode chosen = list.get(21);
|
||||
|
||||
assertEquals("Buffy the Vampire Slayer", chosen.getShowName());
|
||||
assertEquals("Chosen", chosen.getTitle());
|
||||
assertEquals("22", chosen.getEpisodeNumber());
|
||||
assertEquals("7", chosen.getSeasonNumber());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getEpisodeListLink() {
|
||||
assertEquals("http://www.thetvdb.com/?tab=seasonall&id=78874", thetvdb.getEpisodeListLink(new TheTVDBSearchResult("Firefly", 78874)).toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getEpisodeListLinkSingleSeason() {
|
||||
assertEquals("http://www.thetvdb.com/?tab=season&seriesid=73965&seasonid=6749", thetvdb.getEpisodeListLink(new TheTVDBSearchResult("Roswell", 73965), 3).toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getMirror() throws Exception {
|
||||
assertNotNull(thetvdb.getMirror(MirrorType.XML));
|
||||
assertNotNull(thetvdb.getMirror(MirrorType.BANNER));
|
||||
assertNotNull(thetvdb.getMirror(MirrorType.ZIP));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveTypeMask() {
|
||||
// no flags set
|
||||
assertEquals(EnumSet.noneOf(MirrorType.class), MirrorType.fromTypeMask(0));
|
||||
|
||||
// xml and zip flags set
|
||||
assertEquals(EnumSet.of(MirrorType.ZIP, MirrorType.XML), MirrorType.fromTypeMask(5));
|
||||
|
||||
// all flags set
|
||||
assertEquals(EnumSet.allOf(MirrorType.class), MirrorType.fromTypeMask(7));
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
|||
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, SubsceneSubtitleClientTest.class, OpenSubtitlesHasherTest.class })
|
||||
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SubsceneSubtitleClientTest.class, OpenSubtitlesHasherTest.class })
|
||||
public class WebTestSuite {
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue