* 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:
Reinhard Pointner 2009-01-04 18:28:28 +00:00
parent 0bed877344
commit 54b27e69b7
35 changed files with 1268 additions and 466 deletions

View File

@ -111,6 +111,7 @@
<copy todir="${dir.build}">
<fileset dir="${dir.source}">
<include name="**/*.png" />
<include name="**/*.xml" />
</fileset>
</copy>
</target>

BIN
lib/ehcache.jar Normal file

Binary file not shown.

136
source/ehcache.xml Normal file
View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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());

View File

@ -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());

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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;

View File

@ -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>> {

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}

View File

@ -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());
}

View File

@ -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);
}

View File

@ -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));
}
}

View File

@ -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 {
}