* 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:
parent
d1775cf1b4
commit
ea6a839aa8
|
@ -8,6 +8,7 @@ import java.awt.event.ActionEvent;
|
|||
import java.awt.event.ActionListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -30,7 +31,6 @@ import javax.swing.SwingWorker;
|
|||
import net.sourceforge.filebot.resources.ResourceManager;
|
||||
import net.sourceforge.filebot.web.SearchResult;
|
||||
import net.sourceforge.tuned.ExceptionUtil;
|
||||
import net.sourceforge.tuned.ProgressIterator;
|
||||
import net.sourceforge.tuned.ui.SelectButtonTextField;
|
||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||
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 S client;
|
||||
|
@ -153,7 +153,7 @@ public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends Fi
|
|||
|
||||
|
||||
@Override
|
||||
protected abstract List<SearchResult> doInBackground() throws Exception;
|
||||
protected abstract Collection<SearchResult> doInBackground() throws Exception;
|
||||
|
||||
|
||||
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 {
|
||||
List<SearchResult> searchResults = task.get();
|
||||
Collection<SearchResult> searchResults = task.get();
|
||||
|
||||
switch (searchResults.size()) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return searchResults.get(0);
|
||||
return searchResults.iterator().next();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
ProgressIterator<E> iterator = fetch();
|
||||
Collection<E> results = fetch();
|
||||
|
||||
while (!isCancelled() && iterator.hasNext()) {
|
||||
|
||||
try {
|
||||
publish(iterator.next());
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage());
|
||||
}
|
||||
|
||||
setProgress((iterator.getPosition() * 100) / iterator.getLength());
|
||||
for (E result : results) {
|
||||
publish(result);
|
||||
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
|
||||
|
|
|
@ -33,7 +33,7 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
|
|||
protected List<Episode> doInBackground() throws Exception {
|
||||
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>();
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.beans.PropertyChangeListener;
|
|||
import java.net.URI;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
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 EpisodeListClient client;
|
||||
|
@ -200,7 +199,7 @@ public class SearchPanel extends FileBotPanel {
|
|||
|
||||
|
||||
@Override
|
||||
protected List<SearchResult> doInBackground() throws Exception {
|
||||
protected Collection<SearchResult> doInBackground() throws Exception {
|
||||
return client.search(query);
|
||||
}
|
||||
|
||||
|
@ -242,7 +241,7 @@ public class SearchPanel extends FileBotPanel {
|
|||
|
||||
SearchTask task = (SearchTask) evt.getSource();
|
||||
|
||||
List<SearchResult> searchResults;
|
||||
Collection<SearchResult> searchResults;
|
||||
|
||||
try {
|
||||
searchResults = task.get();
|
||||
|
@ -268,7 +267,7 @@ public class SearchPanel extends FileBotPanel {
|
|||
|
||||
if (searchResults.size() == 1) {
|
||||
// only one show found, select this one
|
||||
selectedResult = searchResults.get(0);
|
||||
selectedResult = searchResults.iterator().next();
|
||||
} else if (searchResults.size() > 1) {
|
||||
// multiple shows found, let user selected one
|
||||
Window window = SwingUtilities.getWindowAncestor(SearchPanel.this);
|
||||
|
|
|
@ -16,7 +16,7 @@ public class LanguageResolver {
|
|||
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) {
|
||||
languageName = languageName.toLowerCase();
|
||||
|
||||
Locale locale = localeMap.get(languageName);
|
||||
Locale locale = cache.get(languageName);
|
||||
|
||||
if (locale == null) {
|
||||
locale = findLocale(languageName);
|
||||
localeMap.put(languageName, locale);
|
||||
cache.put(languageName, locale);
|
||||
}
|
||||
|
||||
return locale;
|
||||
|
|
|
@ -3,7 +3,10 @@ package net.sourceforge.filebot.ui.panel.subtitle;
|
|||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import net.sourceforge.filebot.ListChangeSynchronizer;
|
||||
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.SubtitleClient;
|
||||
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;
|
||||
|
||||
|
||||
|
@ -72,7 +72,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
|
|||
|
||||
|
||||
@Override
|
||||
protected List<SearchResult> doInBackground() throws Exception {
|
||||
protected Collection<SearchResult> doInBackground() throws Exception {
|
||||
return getClient().search(getSearchText());
|
||||
}
|
||||
|
||||
|
@ -87,10 +87,16 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitleP
|
|||
|
||||
|
||||
@Override
|
||||
protected ProgressIterator<SubtitlePackage> fetch() throws Exception {
|
||||
ProgressIterator<SubtitleDescriptor> descriptors = getClient().getSubtitleList(getSearchResult());
|
||||
protected Collection<SubtitlePackage> fetch() throws Exception {
|
||||
//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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
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.FunctionIterator.Function;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -88,33 +85,19 @@ public class AnidbClient extends EpisodeListClient {
|
|||
|
||||
|
||||
@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));
|
||||
|
||||
List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
|
||||
|
||||
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, nodes.size()));
|
||||
}
|
||||
|
||||
|
||||
private static class EpisodeFunction implements Function<Node, Episode> {
|
||||
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
private final SearchResult searchResult;
|
||||
private final NumberFormat numberFormat;
|
||||
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Episode evaluate(Node node) {
|
||||
for (Node node : nodes) {
|
||||
String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node);
|
||||
String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
|
||||
|
||||
|
@ -129,15 +112,16 @@ public class AnidbClient extends EpisodeListClient {
|
|||
}
|
||||
|
||||
// no seasons for anime
|
||||
return new Episode(searchResult.getName(), null, number, title);
|
||||
episodes.add(new Episode(searchResult.getName(), null, number, title));
|
||||
}
|
||||
|
||||
return episodes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||
return ((HyperLink) searchResult).getUri();
|
||||
return ((HyperLink) searchResult).toUri();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,13 +4,12 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import net.sourceforge.tuned.ProgressIterator;
|
||||
|
||||
|
||||
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);
|
||||
|
|
|
@ -52,8 +52,11 @@ public class HtmlUtil {
|
|||
|
||||
|
||||
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());
|
||||
String encoding = connection.getContentEncoding();
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
|
|
|
@ -8,23 +8,22 @@ import java.net.URL;
|
|||
|
||||
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) {
|
||||
super(name);
|
||||
this.uri = URI.create(url.toString());
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
|
||||
public URI getUri() {
|
||||
return uri;
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
|
||||
public URI toUri() {
|
||||
return URI.create(url.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
@ -155,20 +157,18 @@ public class OpenSubtitlesClient {
|
|||
|
||||
|
||||
@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) {
|
||||
Map<String, String> map = new HashMap<String, String>(1);
|
||||
|
||||
// pad id with zeros
|
||||
map.put("imdbid", String.format("%07d", imdbid));
|
||||
|
||||
imdbidList.add(map);
|
||||
}
|
||||
// pad imdbId with zeros
|
||||
//TODO needed???
|
||||
searchListEntry.put("imdbid", String.format("%07d", imdbid));
|
||||
searchListEntry.put("sublanguageid", getSubLanguageID(language));
|
||||
|
||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, imdbidList);
|
||||
List<Map<String, String>> searchList = Collections.singletonList(searchListEntry);
|
||||
|
||||
Map<String, List<Map<String, String>>> response = (Map<String, List<Map<String, String>>>) invoke("SearchSubtitles", token, searchList);
|
||||
|
||||
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")
|
||||
public List<MovieDescriptor> searchMoviesOnIMDB(String query) throws XmlRpcFault {
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.logging.Level;
|
||||
|
@ -11,9 +13,6 @@ import java.util.logging.Logger;
|
|||
|
||||
import net.sourceforge.filebot.Settings;
|
||||
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
|
||||
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
|
||||
public Collection<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception {
|
||||
login();
|
||||
|
||||
int imdbId = ((MovieDescriptor) searchResult).getImdbId();
|
||||
|
||||
List<OpenSubtitlesSubtitleDescriptor> subtitles = client.searchSubtitles(imdbId);
|
||||
|
||||
return new FunctionIterator<OpenSubtitlesSubtitleDescriptor, SubtitleDescriptor>(subtitles, new SubtitleFunction());
|
||||
return (Collection) client.searchSubtitles(imdbId, language);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,28 +2,26 @@
|
|||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
|
||||
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) {
|
||||
return cache.containsKey(name);
|
||||
return cache.containsKey(key(name));
|
||||
}
|
||||
|
||||
|
||||
public SearchResult get(String name) {
|
||||
return cache.get(name);
|
||||
return cache.get(key(name));
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String key(String name) {
|
||||
return name.toLowerCase();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,13 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
|
@ -18,10 +21,7 @@ import java.util.regex.Pattern;
|
|||
|
||||
import net.sourceforge.filebot.resources.ResourceManager;
|
||||
import net.sourceforge.tuned.FileUtil;
|
||||
import net.sourceforge.tuned.FunctionIterator;
|
||||
import net.sourceforge.tuned.ProgressIterator;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -70,69 +70,105 @@ public class SubsceneSubtitleClient extends SubtitleClient {
|
|||
return searchResults;
|
||||
}
|
||||
|
||||
HashMap<String, String> languageIdCache;
|
||||
|
||||
|
||||
public String getLanguageID(Locale language) {
|
||||
return languageIdCache.get(language.getDisplayLanguage(Locale.ENGLISH).toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
|
||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, Locale language) throws Exception {
|
||||
|
||||
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);
|
||||
|
||||
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());
|
||||
|
||||
for (Node node : nodes) {
|
||||
try {
|
||||
Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node);
|
||||
|
||||
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
|
||||
|
||||
String href = XPathUtil.selectString("@href", linkNode);
|
||||
|
||||
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
|
||||
|
||||
String author = XPathUtil.selectString("./TD[4]", node);
|
||||
|
||||
Matcher matcher = hrefPattern.matcher(href);
|
||||
|
||||
if (!matcher.matches())
|
||||
throw new IllegalArgumentException("Cannot extract download parameters: " + href);
|
||||
|
||||
String subtitleId = matcher.group(1);
|
||||
String typeId = matcher.group(2);
|
||||
|
||||
URL downloadUrl = getDownloadUrl(url, subtitleId, typeId);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
private static class SubtitleFunction implements Function<Node, SubtitleDescriptor> {
|
||||
|
||||
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);
|
||||
|
||||
String href = XPathUtil.selectString("@href", linkNode);
|
||||
|
||||
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
|
||||
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
|
||||
|
||||
String author = XPathUtil.selectString("./TD[4]", node);
|
||||
|
||||
Matcher matcher = hrefPattern.matcher(href);
|
||||
|
||||
if (!matcher.matches())
|
||||
throw new IllegalArgumentException("Cannot extract download parameters: " + href);
|
||||
|
||||
String subtitleId = matcher.group(1);
|
||||
String typeId = matcher.group(2);
|
||||
|
||||
URL downloadUrl = getDownloadUrl(url, subtitleId, typeId);
|
||||
|
||||
return new SubsceneSubtitleDescriptor(name, lang, author, typeId, downloadUrl, url);
|
||||
}
|
||||
|
||||
|
||||
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
|
||||
String basePath = FileUtil.getNameWithoutExtension(referer.getFile());
|
||||
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
|
||||
|
||||
return new URL(referer.getProtocol(), referer.getHost(), path);
|
||||
}
|
||||
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
|
||||
String basePath = FileUtil.getNameWithoutExtension(referer.getFile());
|
||||
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
|
||||
|
||||
return new URL(referer.getProtocol(), referer.getHost(), path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public URI getSubtitleListLink(SearchResult searchResult) {
|
||||
return ((HyperLink) searchResult).getUri();
|
||||
return ((HyperLink) searchResult).toUri();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ package net.sourceforge.filebot.web;
|
|||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import net.sourceforge.tuned.ProgressIterator;
|
||||
import javax.swing.Icon;
|
||||
|
||||
|
||||
public abstract class SubtitleClient {
|
||||
|
@ -28,20 +28,20 @@ public abstract class SubtitleClient {
|
|||
return Collections.unmodifiableList(registry);
|
||||
}
|
||||
|
||||
private String name;
|
||||
private ImageIcon icon;
|
||||
private final String name;
|
||||
private final Icon icon;
|
||||
|
||||
|
||||
public SubtitleClient(String name, ImageIcon icon) {
|
||||
public SubtitleClient(String name, Icon icon) {
|
||||
this.name = name;
|
||||
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);
|
||||
|
@ -52,7 +52,7 @@ public abstract class SubtitleClient {
|
|||
}
|
||||
|
||||
|
||||
public ImageIcon getIcon() {
|
||||
public Icon getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
|
|||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import net.sourceforge.filebot.resources.ResourceManager;
|
||||
import net.sourceforge.tuned.DefaultProgressIterator;
|
||||
import net.sourceforge.tuned.ProgressIterator;
|
||||
import net.sourceforge.tuned.XPathUtil;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
@ -63,7 +61,7 @@ public class TVRageClient extends EpisodeListClient {
|
|||
|
||||
|
||||
@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();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,10 +18,7 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
|
||||
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.FunctionIterator.Function;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
|
@ -77,39 +74,22 @@ public class TvdotcomClient extends EpisodeListClient {
|
|||
|
||||
|
||||
@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));
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
private static class EpisodeFunction implements Function<Node, Episode> {
|
||||
NumberFormat numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
||||
numberFormat.setGroupingUsed(false);
|
||||
|
||||
private final SearchResult searchResult;
|
||||
private final NumberFormat numberFormat;
|
||||
Integer episodeOffset = null;
|
||||
String seasonString = (season >= 1) ? Integer.toString(season) : null;
|
||||
|
||||
private Integer episodeOffset = null;
|
||||
private String seasonString = null;
|
||||
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||
|
||||
|
||||
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);
|
||||
|
||||
if (season >= 1)
|
||||
seasonString = Integer.toString(season);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Episode evaluate(Node node) {
|
||||
for (Node node : nodes) {
|
||||
String episodeNumber = XPathUtil.selectString("./TD[1]", node);
|
||||
String title = XPathUtil.selectString("./TD[2]/A", node);
|
||||
|
||||
|
@ -125,16 +105,17 @@ public class TvdotcomClient extends EpisodeListClient {
|
|||
// 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
|
||||
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 file = base + "episode_listings.html";
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -15,6 +15,11 @@ public class DefaultThreadFactory implements ThreadFactory {
|
|||
private final boolean daemon;
|
||||
|
||||
|
||||
public DefaultThreadFactory(String name) {
|
||||
this(name, Thread.NORM_PRIORITY);
|
||||
}
|
||||
|
||||
|
||||
public DefaultThreadFactory(String name, int priority) {
|
||||
this(name, priority, false);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -7,7 +7,6 @@ import static org.junit.Assert.assertEquals;
|
|||
import java.util.List;
|
||||
|
||||
import net.sourceforge.filebot.web.TVRageClient.TVRageSearchResult;
|
||||
import net.sourceforge.tuned.TestUtil;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -32,7 +31,7 @@ public class TVRageClientTest {
|
|||
|
||||
@Test
|
||||
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);
|
||||
|
||||
|
@ -45,7 +44,7 @@ public class TVRageClientTest {
|
|||
|
||||
@Test
|
||||
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());
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
|||
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses( { FunctionIteratorTest.class, PreferencesMapTest.class, PreferencesListTest.class })
|
||||
@SuiteClasses( { PreferencesMapTest.class, PreferencesListTest.class })
|
||||
public class TunedTestSuite {
|
||||
|
||||
public static Test suite() {
|
||||
|
|
Loading…
Reference in New Issue