1
0
mirror of https://github.com/mitb-archive/filebot synced 2024-11-16 06:15:02 -05:00

* switched back to using List as return value for EpisodeList- and SubtitleClients (lazy XPath evaluation not needed anymore, because we are fast enough anyway)

This commit is contained in:
Reinhard Pointner 2008-07-05 11:37:03 +00:00
parent d1775cf1b4
commit ea6a839aa8
22 changed files with 209 additions and 418 deletions

View File

@ -8,6 +8,7 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.net.URI; import java.net.URI;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -30,7 +31,6 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.tuned.ExceptionUtil; import net.sourceforge.tuned.ExceptionUtil;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.ui.SelectButtonTextField; import net.sourceforge.tuned.ui.SelectButtonTextField;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter; import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TunedUtil; import net.sourceforge.tuned.ui.TunedUtil;
@ -137,7 +137,7 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
}; };
protected abstract class SearchTask extends SwingWorker<List<SearchResult>, Void> { protected abstract class SearchTask extends SwingWorker<Collection<SearchResult>, Void> {
private final String searchText; private final String searchText;
private final S client; private final S client;
@ -153,7 +153,7 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
@Override @Override
protected abstract List<SearchResult> doInBackground() throws Exception; protected abstract Collection<SearchResult> doInBackground() throws Exception;
public String getSearchText() { public String getSearchText() {
@ -240,13 +240,13 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
private SearchResult selectSearchResult(SearchTask task) throws Exception { private SearchResult selectSearchResult(SearchTask task) throws Exception {
List<SearchResult> searchResults = task.get(); Collection<SearchResult> searchResults = task.get();
switch (searchResults.size()) { switch (searchResults.size()) {
case 0: case 0:
return null; return null;
case 1: case 1:
return searchResults.get(0); return searchResults.iterator().next();
} }
// multiple results have been found, user must selected one // multiple results have been found, user must selected one
@ -288,17 +288,10 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
protected final Void doInBackground() throws Exception { protected final Void doInBackground() throws Exception {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
ProgressIterator<E> iterator = fetch(); Collection<E> results = fetch();
while (!isCancelled() && iterator.hasNext()) { for (E result : results) {
publish(result);
try {
publish(iterator.next());
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage());
}
setProgress((iterator.getPosition() * 100) / iterator.getLength());
count++; count++;
} }
@ -308,7 +301,7 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
} }
protected abstract ProgressIterator<E> fetch() throws Exception; protected abstract Collection<E> fetch() throws Exception;
@Override @Override

View File

@ -33,7 +33,7 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
protected List<Episode> doInBackground() throws Exception { protected List<Episode> doInBackground() throws Exception {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
Iterator<Episode> itr = searchEngine.getEpisodeList(searchResult, numberOfSeason); Iterator<Episode> itr = searchEngine.getEpisodeList(searchResult, numberOfSeason).iterator();
ArrayList<Episode> list = new ArrayList<Episode>(); ArrayList<Episode> list = new ArrayList<Episode>();

View File

@ -11,7 +11,6 @@ import java.beans.PropertyChangeListener;
import java.net.URI; import java.net.URI;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -185,7 +184,7 @@ public class SearchPanel extends FileBotPanel {
}; };
private class SearchTask extends SwingWorker<List<SearchResult>, Void> { private class SearchTask extends SwingWorker<Collection<SearchResult>, Void> {
private final String query; private final String query;
private final EpisodeListClient client; private final EpisodeListClient client;
@ -200,7 +199,7 @@ public class SearchPanel extends FileBotPanel {
@Override @Override
protected List<SearchResult> doInBackground() throws Exception { protected Collection<SearchResult> doInBackground() throws Exception {
return client.search(query); return client.search(query);
} }
@ -242,7 +241,7 @@ public class SearchPanel extends FileBotPanel {
SearchTask task = (SearchTask) evt.getSource(); SearchTask task = (SearchTask) evt.getSource();
List<SearchResult> searchResults; Collection<SearchResult> searchResults;
try { try {
searchResults = task.get(); searchResults = task.get();
@ -268,7 +267,7 @@ public class SearchPanel extends FileBotPanel {
if (searchResults.size() == 1) { if (searchResults.size() == 1) {
// only one show found, select this one // only one show found, select this one
selectedResult = searchResults.get(0); selectedResult = searchResults.iterator().next();
} else if (searchResults.size() > 1) { } else if (searchResults.size() > 1) {
// multiple shows found, let user selected one // multiple shows found, let user selected one
Window window = SwingUtilities.getWindowAncestor(SearchPanel.this); Window window = SwingUtilities.getWindowAncestor(SearchPanel.this);

View File

@ -16,7 +16,7 @@ public class LanguageResolver {
return defaultInstance; return defaultInstance;
} }
private final Map<String, Locale> localeMap = new HashMap<String, Locale>(); private final Map<String, Locale> cache = new HashMap<String, Locale>();
/** /**
@ -28,11 +28,11 @@ public class LanguageResolver {
public synchronized Locale getLocale(String languageName) { public synchronized Locale getLocale(String languageName) {
languageName = languageName.toLowerCase(); languageName = languageName.toLowerCase();
Locale locale = localeMap.get(languageName); Locale locale = cache.get(languageName);
if (locale == null) { if (locale == null) {
locale = findLocale(languageName); locale = findLocale(languageName);
localeMap.put(languageName, locale); cache.put(languageName, locale);
} }
return locale; return locale;

View File

@ -3,7 +3,10 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import java.net.URI; import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import net.sourceforge.filebot.ListChangeSynchronizer; import net.sourceforge.filebot.ListChangeSynchronizer;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
@ -13,9 +16,6 @@ import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SubtitleClient; import net.sourceforge.filebot.web.SubtitleClient;
import net.sourceforge.filebot.web.SubtitleDescriptor; import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.FunctionIterator.Function;
import net.sourceforge.tuned.ui.SimpleIconProvider; import net.sourceforge.tuned.ui.SimpleIconProvider;
@ -72,7 +72,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
@Override @Override
protected List<SearchResult> doInBackground() throws Exception { protected Collection<SearchResult> doInBackground() throws Exception {
return getClient().search(getSearchText()); return getClient().search(getSearchText());
} }
@ -87,10 +87,16 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
@Override @Override
protected ProgressIterator<SubtitlePackage> fetch() throws Exception { protected Collection<SubtitlePackage> fetch() throws Exception {
ProgressIterator<SubtitleDescriptor> descriptors = getClient().getSubtitleList(getSearchResult()); //TODO language combobox
Collection<SubtitleDescriptor> descriptors = getClient().getSubtitleList(getSearchResult(), Locale.ENGLISH);
ArrayList<SubtitlePackage> packages = new ArrayList<SubtitlePackage>();
return new FunctionIterator<SubtitleDescriptor, SubtitlePackage>(descriptors, new SubtitlePackageFunction()); for (SubtitleDescriptor descriptor : descriptors) {
packages.add(new SubtitlePackage(descriptor));
}
return packages;
} }
@ -109,14 +115,4 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
} }
} }
private static class SubtitlePackageFunction implements Function<SubtitleDescriptor, SubtitlePackage> {
@Override
public SubtitlePackage evaluate(SubtitleDescriptor sourceValue) {
return new SubtitlePackage(sourceValue);
}
}
} }

View File

@ -17,10 +17,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.XPathUtil; import net.sourceforge.tuned.XPathUtil;
import net.sourceforge.tuned.FunctionIterator.Function;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
@ -88,33 +85,19 @@ public class AnidbClient extends EpisodeListClient {
@Override @Override
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException { public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season)); Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom); List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, nodes.size())); NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
} numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
private static class EpisodeFunction implements Function<Node, Episode> {
private final SearchResult searchResult;
private final NumberFormat numberFormat;
public EpisodeFunction(SearchResult searchResult, int nodeCount) {
this.searchResult = searchResult;
numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodeCount).length(), 2));
numberFormat.setGroupingUsed(false); numberFormat.setGroupingUsed(false);
}
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
@Override for (Node node : nodes) {
public Episode evaluate(Node node) {
String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node); String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node);
String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node); String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
@ -129,15 +112,16 @@ public class AnidbClient extends EpisodeListClient {
} }
// no seasons for anime // no seasons for anime
return new Episode(searchResult.getName(), null, number, title); episodes.add(new Episode(searchResult.getName(), null, number, title));
} }
return episodes;
} }
@Override @Override
public URI getEpisodeListLink(SearchResult searchResult, int season) { public URI getEpisodeListLink(SearchResult searchResult, int season) {
return ((HyperLink) searchResult).getUri(); return ((HyperLink) searchResult).toUri();
} }

View File

@ -4,13 +4,12 @@ package net.sourceforge.filebot.web;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import net.sourceforge.tuned.ProgressIterator;
public abstract class EpisodeListClient { public abstract class EpisodeListClient {
@ -41,10 +40,10 @@ public abstract class EpisodeListClient {
} }
public abstract List<SearchResult> search(String searchterm) throws Exception; public abstract Collection<SearchResult> search(String searchterm) throws Exception;
public abstract ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception; public abstract Collection<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception;
public abstract URI getEpisodeListLink(SearchResult searchResult, int season); public abstract URI getEpisodeListLink(SearchResult searchResult, int season);

View File

@ -52,8 +52,11 @@ public class HtmlUtil {
public static Document getHtmlDocument(URL url) throws IOException, SAXException { public static Document getHtmlDocument(URL url) throws IOException, SAXException {
URLConnection connection = url.openConnection(); return getHtmlDocument(url.openConnection());
}
public static Document getHtmlDocument(URLConnection connection) throws IOException, SAXException {
Charset charset = getCharset(connection.getContentType()); Charset charset = getCharset(connection.getContentType());
String encoding = connection.getContentEncoding(); String encoding = connection.getContentEncoding();
InputStream inputStream = connection.getInputStream(); InputStream inputStream = connection.getInputStream();

View File

@ -8,23 +8,22 @@ import java.net.URL;
public class HyperLink extends SearchResult { public class HyperLink extends SearchResult {
private final URI uri; private final URL url;
public HyperLink(String name, URI uri) {
super(name);
this.uri = uri;
}
public HyperLink(String name, URL url) { public HyperLink(String name, URL url) {
super(name); super(name);
this.uri = URI.create(url.toString()); this.url = url;
} }
public URI getUri() { public URL getUrl() {
return uri; return url;
}
public URI toUri() {
return URI.create(url.toString());
} }
} }

View File

@ -4,8 +4,10 @@ package net.sourceforge.filebot.web;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -155,20 +157,18 @@ public class OpenSubtitlesClient {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int... imdbidArray) throws XmlRpcFault { public List<OpenSubtitlesSubtitleDescriptor> searchSubtitles(int imdbid, Locale language) throws XmlRpcFault {
List<Map<String, String>> imdbidList = new ArrayList<Map<String, String>>(imdbidArray.length); Map<String, String> searchListEntry = new HashMap<String, String>(2);
for (int imdbid : imdbidArray) { // pad imdbId with zeros
Map<String, String> map = new HashMap<String, String>(1); //TODO needed???
searchListEntry.put("imdbid", String.format("%07d", imdbid));
searchListEntry.put("sublanguageid", getSubLanguageID(language));
// pad id with zeros List<Map<String, String>> searchList = Collections.singletonList(searchListEntry);
map.put("imdbid", String.format("%07d", imdbid));
imdbidList.add(map); Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, searchList);
}
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, imdbidList);
ArrayList<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>(); ArrayList<OpenSubtitlesSubtitleDescriptor> subs = new ArrayList<OpenSubtitlesSubtitleDescriptor>();
@ -184,6 +184,12 @@ public class OpenSubtitlesClient {
} }
private String getSubLanguageID(Locale locale) {
//TODO test if sublanguageid is really ISO3 language code
return locale.getISO3Language();
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault { public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {

View File

@ -3,7 +3,9 @@ package net.sourceforge.filebot.web;
import java.net.URI; import java.net.URI;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.logging.Level; import java.util.logging.Level;
@ -11,9 +13,6 @@ import java.util.logging.Logger;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.FunctionIterator.Function;
/** /**
@ -44,15 +43,14 @@ public class OpenSubtitlesSubtitleClient extends SubtitleClient {
} }
@SuppressWarnings("unchecked")
@Override @Override
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception { public Collection<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception {
login(); login();
int imdbId = ((MovieDescriptor) searchResult).getImdbId(); int imdbId = ((MovieDescriptor) searchResult).getImdbId();
List<OpenSubtitlesSubtitleDescriptor> subtitles = client.searchSubtitles(imdbId); return (Collection) client.searchSubtitles(imdbId, language);
return new FunctionIterator<OpenSubtitlesSubtitleDescriptor, SubtitleDescriptor>(subtitles, new SubtitleFunction());
} }
@ -135,13 +133,4 @@ public class OpenSubtitlesSubtitleClient extends SubtitleClient {
}; };
} }
private static class SubtitleFunction implements Function<OpenSubtitlesSubtitleDescriptor, SubtitleDescriptor> {
@Override
public SubtitleDescriptor evaluate(OpenSubtitlesSubtitleDescriptor sourceValue) {
return sourceValue;
}
}
} }

View File

@ -2,28 +2,26 @@
package net.sourceforge.filebot.web; package net.sourceforge.filebot.web;
import java.util.Collections; import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import java.util.TreeMap;
public class SearchResultCache { public class SearchResultCache {
private final Map<String, SearchResult> cache = Collections.synchronizedMap(new TreeMap<String, SearchResult>(String.CASE_INSENSITIVE_ORDER)); private final ConcurrentHashMap<String, SearchResult> cache = new ConcurrentHashMap<String, SearchResult>();
public boolean containsKey(String name) { public boolean containsKey(String name) {
return cache.containsKey(name); return cache.containsKey(key(name));
} }
public SearchResult get(String name) { public SearchResult get(String name) {
return cache.get(name); return cache.get(key(name));
} }
public void add(SearchResult searchResult) { public void add(SearchResult searchResult) {
cache.put(searchResult.getName(), searchResult); cache.putIfAbsent(key(searchResult.getName()), searchResult);
} }
@ -32,4 +30,10 @@ public class SearchResultCache {
add(searchResult); add(searchResult);
} }
} }
private String key(String name) {
return name.toLowerCase();
}
} }

View File

@ -7,10 +7,13 @@ import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -18,10 +21,7 @@ import java.util.regex.Pattern;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.FileUtil; import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.XPathUtil; import net.sourceforge.tuned.XPathUtil;
import net.sourceforge.tuned.FunctionIterator.Function;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
@ -70,38 +70,70 @@ public class SubsceneSubtitleClient extends SubtitleClient {
return searchResults; return searchResults;
} }
HashMap<String, String> languageIdCache;
public String getLanguageID(Locale language) {
return languageIdCache.get(language.getDisplayLanguage(Locale.ENGLISH).toLowerCase());
}
@Override @Override
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception { public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception {
URL url = getSubtitleListLink(searchResult).toURL(); URL url = getSubtitleListLink(searchResult).toURL();
Document dom = HtmlUtil.getHtmlDocument(url); Document dom = null;
if (languageIdCache != null) {
URLConnection connection = url.openConnection();
if (language != null && language != Locale.ROOT) {
System.out.println(getLanguageID(language));
connection.addRequestProperty("Cookie", "subscene_sLanguageIds=" + getLanguageID(language));
}
dom = HtmlUtil.getHtmlDocument(connection);
} else {
URLConnection connection = url.openConnection();
dom = HtmlUtil.getHtmlDocument(connection);
List<Node> nodes = XPathUtil.selectNodes("//DIV[@class='languageList']/DIV", dom);
Pattern onClickPattern = Pattern.compile("selectLanguage\\((\\d+)\\);");
languageIdCache = new HashMap<String, String>();
for (Node node : nodes) {
Matcher matcher = onClickPattern.matcher(XPathUtil.selectString("./INPUT/@onclick", node));
if (matcher.matches()) {
String name = XPathUtil.selectString("./LABEL/text()", node);
String id = matcher.group(1);
//TODO sysout
System.out.println(name + " = " + id);
languageIdCache.put(name.toLowerCase(), id);
}
}
}
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", dom); List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", dom);
return new FunctionIterator<Node, SubtitleDescriptor>(nodes, new SubtitleFunction(url)); Pattern hrefPattern = Pattern.compile("javascript:Subtitle\\((\\d+), '(\\w+)', '\\d+', '(\\d+)'\\);");
}
ArrayList<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>(nodes.size());
private static class SubtitleFunction implements Function<Node, SubtitleDescriptor> { for (Node node : nodes) {
try {
private final Pattern hrefPattern = Pattern.compile("javascript:Subtitle\\((\\d+), '(\\w+)', '0', '(\\d+)'\\);");
private final URL url;
public SubtitleFunction(URL url) {
this.url = url;
}
@Override
public SubtitleDescriptor evaluate(Node node) throws Exception {
Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node); Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node);
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
String href = XPathUtil.selectString("@href", linkNode); String href = XPathUtil.selectString("@href", linkNode);
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
String name = XPathUtil.selectString("./SPAN[2]", linkNode); String name = XPathUtil.selectString("./SPAN[2]", linkNode);
String author = XPathUtil.selectString("./TD[4]", node); String author = XPathUtil.selectString("./TD[4]", node);
@ -116,7 +148,13 @@ public class SubsceneSubtitleClient extends SubtitleClient {
URL downloadUrl = getDownloadUrl(url, subtitleId, typeId); URL downloadUrl = getDownloadUrl(url, subtitleId, typeId);
return new SubsceneSubtitleDescriptor(name, lang, author, typeId, downloadUrl, url); subtitles.add(new SubsceneSubtitleDescriptor(name, lang, author, typeId, downloadUrl, url));
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot parse subtitle node", e);
}
}
return subtitles;
} }
@ -127,12 +165,10 @@ public class SubsceneSubtitleClient extends SubtitleClient {
return new URL(referer.getProtocol(), referer.getHost(), path); return new URL(referer.getProtocol(), referer.getHost(), path);
} }
}
@Override @Override
public URI getSubtitleListLink(SearchResult searchResult) { public URI getSubtitleListLink(SearchResult searchResult) {
return ((HyperLink) searchResult).getUri(); return ((HyperLink) searchResult).toUri();
} }

View File

@ -4,12 +4,12 @@ package net.sourceforge.filebot.web;
import java.net.URI; import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.swing.ImageIcon; import javax.swing.Icon;
import net.sourceforge.tuned.ProgressIterator;
public abstract class SubtitleClient { public abstract class SubtitleClient {
@ -28,20 +28,20 @@ public abstract class SubtitleClient {
return Collections.unmodifiableList(registry); return Collections.unmodifiableList(registry);
} }
private String name; private final String name;
private ImageIcon icon; private final Icon icon;
public SubtitleClient(String name, ImageIcon icon) { public SubtitleClient(String name, Icon icon) {
this.name = name; this.name = name;
this.icon = icon; this.icon = icon;
} }
public abstract List<SearchResult> search(String searchterm) throws Exception; public abstract Collection<SearchResult> search(String searchterm) throws Exception;
public abstract ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception; public abstract Collection<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception;
public abstract URI getSubtitleListLink(SearchResult searchResult); public abstract URI getSubtitleListLink(SearchResult searchResult);
@ -52,7 +52,7 @@ public abstract class SubtitleClient {
} }
public ImageIcon getIcon() { public Icon getIcon() {
return icon; return icon;
} }

View File

@ -13,8 +13,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.DefaultProgressIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.XPathUtil; import net.sourceforge.tuned.XPathUtil;
import org.w3c.dom.Document; import org.w3c.dom.Document;
@ -63,7 +61,7 @@ public class TVRageClient extends EpisodeListClient {
@Override @Override
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException, ParserConfigurationException { public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException, ParserConfigurationException {
int showId = ((TVRageSearchResult) searchResult).getShowId(); int showId = ((TVRageSearchResult) searchResult).getShowId();
String episodeListUri = String.format("http://" + host + "/feeds/episode_list.php?sid=" + showId); String episodeListUri = String.format("http://" + host + "/feeds/episode_list.php?sid=" + showId);
@ -95,7 +93,7 @@ public class TVRageClient extends EpisodeListClient {
} }
} }
return new DefaultProgressIterator<Episode>(episodes); return episodes;
} }

View File

@ -18,10 +18,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.XPathUtil; import net.sourceforge.tuned.XPathUtil;
import net.sourceforge.tuned.FunctionIterator.Function;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Node; import org.w3c.dom.Node;
@ -77,39 +74,22 @@ public class TvdotcomClient extends EpisodeListClient {
@Override @Override
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException { public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season)); Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
List<Node> nodes = XPathUtil.selectNodes("id('episode-listing')/DIV/TABLE/TR/TD/ancestor::TR", dom); List<Node> nodes = XPathUtil.selectNodes("id('episode-listing')/DIV/TABLE/TR/TD/ancestor::TR", dom);
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, season, nodes.size())); NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
} numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
private static class EpisodeFunction implements Function<Node, Episode> {
private final SearchResult searchResult;
private final NumberFormat numberFormat;
private Integer episodeOffset = null;
private String seasonString = null;
public EpisodeFunction(SearchResult searchResult, int season, int nodeCount) {
this.searchResult = searchResult;
numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodeCount).length(), 2));
numberFormat.setGroupingUsed(false); numberFormat.setGroupingUsed(false);
if (season >= 1) Integer episodeOffset = null;
seasonString = Integer.toString(season); String seasonString = (season >= 1) ? Integer.toString(season) : null;
}
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
@Override for (Node node : nodes) {
public Episode evaluate(Node node) {
String episodeNumber = XPathUtil.selectString("./TD[1]", node); String episodeNumber = XPathUtil.selectString("./TD[1]", node);
String title = XPathUtil.selectString("./TD[2]/A", node); String title = XPathUtil.selectString("./TD[2]/A", node);
@ -125,16 +105,17 @@ public class TvdotcomClient extends EpisodeListClient {
// episode number may be "Pilot", "Special", ... // episode number may be "Pilot", "Special", ...
} }
return new Episode(searchResult.getName(), seasonString, episodeNumber, title); episodes.add(new Episode(searchResult.getName(), seasonString, episodeNumber, title));
} }
return episodes;
} }
@Override @Override
public URI getEpisodeListLink(SearchResult searchResult, int season) { public URI getEpisodeListLink(SearchResult searchResult, int season) {
String summaryFile = null;
summaryFile = ((HyperLink) searchResult).getUri().getPath(); String summaryFile = ((HyperLink) searchResult).getUrl().getPath();
String base = summaryFile.substring(0, summaryFile.indexOf("summary.html")); String base = summaryFile.substring(0, summaryFile.indexOf("summary.html"));
String file = base + "episode_listings.html"; String file = base + "episode_listings.html";

View File

@ -1,54 +0,0 @@
package net.sourceforge.tuned;
import java.util.Collection;
import java.util.Iterator;
public class DefaultProgressIterator<E> implements ProgressIterator<E> {
private final Iterator<E> sourceIterator;
private final int length;
private int position = 0;
public DefaultProgressIterator(Collection<E> source) {
this.sourceIterator = source.iterator();
this.length = source.size();
}
@Override
public int getLength() {
return length;
}
@Override
public int getPosition() {
return position;
}
@Override
public boolean hasNext() {
return sourceIterator.hasNext();
}
@Override
public E next() {
position++;
return sourceIterator.next();
}
@Override
public void remove() {
// TODO Auto-generated method stub
}
}

View File

@ -15,6 +15,11 @@ public class DefaultThreadFactory implements ThreadFactory {
private final boolean daemon; private final boolean daemon;
public DefaultThreadFactory(String name) {
this(name, Thread.NORM_PRIORITY);
}
public DefaultThreadFactory(String name, int priority) { public DefaultThreadFactory(String name, int priority) {
this(name, priority, false); this(name, priority, false);
} }

View File

@ -1,131 +0,0 @@
package net.sourceforge.tuned;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class FunctionIterator<S, T> implements ProgressIterator<T> {
/**
* A Function transforms one Object into another.
*
* @param <S> type of source Objects
* @param <T> type Objects are transformed into
*/
public static interface Function<S, T> {
/**
* Transform the given sourceValue into any kind of Object.
*
* @param sourceValue - the Object to transform
* @return the transformed version of the object
*/
public T evaluate(S sourceValue) throws Exception;
}
private final Iterator<S> sourceIterator;
private final Function<S, T> function;
private final int length;
private int position = 0;
public FunctionIterator(Collection<S> source, Function<S, T> function) {
this(source.iterator(), source.size(), function);
}
//TODO TEST case!!! for piped functions -> correct progress
public FunctionIterator(ProgressIterator<S> iterator, Function<S, T> function) {
this(iterator, iterator.getLength(), function);
}
public FunctionIterator(Iterator<S> iterator, int length, Function<S, T> function) {
this.sourceIterator = iterator;
this.length = length;
this.function = function;
}
@Override
public boolean hasNext() {
try {
return peekNext() != null;
} catch (Exception e) {
return true;
}
}
@Override
public T next() {
if (!hasNext())
throw new NoSuchElementException();
try {
return peekNext();
} finally {
cache = null;
currentException = null;
}
}
private T cache = null;
private RuntimeException currentException = null;
private T peekNext() {
while (cache == null && (sourceIterator.hasNext() || currentException != null)) {
if (currentException != null)
throw currentException;
try {
cache = transform(sourceIterator.next());
} catch (Exception e) {
currentException = ExceptionUtil.asRuntimeException(e);
}
position++;
}
return cache;
}
private T transform(S sourceValue) throws Exception {
return function.evaluate(sourceValue);
}
@Override
public int getPosition() {
if (sourceIterator instanceof FunctionIterator) {
return ((ProgressIterator<?>) sourceIterator).getPosition();
}
return position;
}
@Override
public int getLength() {
return length;
}
/**
* The remove operation is not supported by this implementation of <code>Iterator</code>.
*
* @throws UnsupportedOperationException if this method is invoked.
* @see java.util.Iterator
*/
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@ -1,15 +0,0 @@
package net.sourceforge.tuned;
import java.util.Iterator;
public interface ProgressIterator<E> extends Iterator<E> {
public int getPosition();
public int getLength();
}

View File

@ -7,7 +7,6 @@ import static org.junit.Assert.assertEquals;
import java.util.List; import java.util.List;
import net.sourceforge.filebot.web.TVRageClient.TVRageSearchResult; import net.sourceforge.filebot.web.TVRageClient.TVRageSearchResult;
import net.sourceforge.tuned.TestUtil;
import org.junit.Test; import org.junit.Test;
@ -32,7 +31,7 @@ public class TVRageClientTest {
@Test @Test
public void getEpisodeList() throws Exception { public void getEpisodeList() throws Exception {
List<Episode> list = TestUtil.asList(tvrage.getEpisodeList(testResult, 7)); List<Episode> list = tvrage.getEpisodeList(testResult, 7);
Episode chosen = list.get(21); Episode chosen = list.get(21);
@ -45,7 +44,7 @@ public class TVRageClientTest {
@Test @Test
public void getEpisodeListAll() throws Exception { public void getEpisodeListAll() throws Exception {
List<Episode> list = TestUtil.asList(tvrage.getEpisodeList(testResult, 0)); List<Episode> list = tvrage.getEpisodeList(testResult, 0);
assertEquals(145, list.size()); assertEquals(145, list.size());

View File

@ -11,7 +11,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses( { FunctionIteratorTest.class, PreferencesMapTest.class, PreferencesListTest.class }) @SuiteClasses( { PreferencesMapTest.class, PreferencesListTest.class })
public class TunedTestSuite { public class TunedTestSuite {
public static Test suite() { public static Test suite() {