diff --git a/.classpath b/.classpath
index f4821441..ebb4c7d0 100644
--- a/.classpath
+++ b/.classpath
@@ -20,7 +20,6 @@
-
@@ -30,5 +29,6 @@
+
diff --git a/build.xml b/build.xml
index 1902362a..8f0d42fe 100644
--- a/build.xml
+++ b/build.xml
@@ -83,7 +83,7 @@
-
+
diff --git a/ivy.xml b/ivy.xml
index c375f3ce..2cab2ebf 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/source/ehcache.xml b/source/ehcache.xml
index f18676c9..646d75c7 100644
--- a/source/ehcache.xml
+++ b/source/ehcache.xml
@@ -19,14 +19,14 @@
/>
{
+
+ public CachedJsonResource(String resource) {
+ super(resource, String.class, ONE_DAY, 2, 1000);
+ }
+
+ public CachedJsonResource(String resource, long expirationTime, int retryCountLimit, long retryWaitTime) {
+ super(resource, String.class, expirationTime, retryCountLimit, retryWaitTime);
+ }
+
+ @Override
+ protected Cache getCache() {
+ return CacheManager.getInstance().getCache("web-datasource-lv3");
+ }
+
+ public JsonObject, ?> getJSON() throws IOException {
+ try {
+ return (JsonObject, ?>) JsonReader.jsonToMaps(get());
+ } catch (Exception e) {
+ throw new IOException(String.format("Error while loading JSON resource: %s (%s)", getResourceLocation(resource), e.getMessage()));
+ }
+ }
+
+ @Override
+ public String process(String data) throws IOException {
+ try {
+ JsonReader.jsonToMaps(data); // make sure JSON is valid
+ } catch (Exception e) {
+ throw new IOException(String.format("Malformed JSON: %s (%s)", getResourceLocation(resource), e.getMessage()));
+ }
+ return data;
+ }
+
+ @Override
+ protected String fetchData(URL url, long lastModified) throws IOException {
+ ByteBuffer data = WebRequest.fetchIfModified(url, lastModified);
+
+ if (data == null)
+ return null; // not modified
+
+ return StandardCharsets.UTF_8.decode(data).toString();
+ }
+
+}
diff --git a/source/net/filebot/web/CachedXmlResource.java b/source/net/filebot/web/CachedXmlResource.java
index ec8b0f6c..0ba0b7b9 100644
--- a/source/net/filebot/web/CachedXmlResource.java
+++ b/source/net/filebot/web/CachedXmlResource.java
@@ -20,7 +20,7 @@ import org.xml.sax.helpers.DefaultHandler;
public class CachedXmlResource extends AbstractCachedResource {
public CachedXmlResource(String resource) {
- super(resource, String.class, ONE_WEEK, 2, 1000);
+ super(resource, String.class, ONE_DAY, 2, 1000);
}
public CachedXmlResource(String resource, long expirationTime, int retryCountLimit, long retryWaitTime) {
diff --git a/source/net/filebot/web/TVMazeClient.java b/source/net/filebot/web/TVMazeClient.java
new file mode 100644
index 00000000..a7e60b92
--- /dev/null
+++ b/source/net/filebot/web/TVMazeClient.java
@@ -0,0 +1,148 @@
+package net.filebot.web;
+
+import static net.filebot.web.WebRequest.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import javax.swing.Icon;
+
+import net.filebot.Cache;
+import net.filebot.ResourceManager;
+
+import com.cedarsoftware.util.io.JsonObject;
+
+public class TVMazeClient extends AbstractEpisodeListProvider {
+
+ @Override
+ public String getName() {
+ return "TVmaze";
+ }
+
+ @Override
+ public Icon getIcon() {
+ return ResourceManager.getIcon("search.tvmaze");
+ }
+
+ @Override
+ public boolean hasSeasonSupport() {
+ return true;
+ }
+
+ @Override
+ protected SortOrder vetoRequestParameter(SortOrder order) {
+ return SortOrder.Airdate;
+ }
+
+ @Override
+ protected Locale vetoRequestParameter(Locale language) {
+ return Locale.ENGLISH;
+ }
+
+ @Override
+ protected SearchResult createSearchResult(int id) {
+ return new TVMazeSearchResult(id, null);
+ }
+
+ @Override
+ public ResultCache getCache() {
+ return new ResultCache(getName(), Cache.getCache("web-datasource"));
+ }
+
+ @Override
+ public List fetchSearchResult(String query, Locale locale) throws IOException {
+ // e.g. http://api.tvmaze.com/search/shows?q=girls
+ JsonObject, ?> response = request("search/shows?q=" + encode(query, true));
+
+ List results = new ArrayList();
+ if (response.isArray()) {
+ for (Object result : response.getArray()) {
+ Map, ?> show = (Map, ?>) ((Map, ?>) result).get("show");
+
+ int id = Integer.parseInt(show.get("id").toString());
+ String name = show.get("name").toString();
+
+ // FUTURE WORK: consider adding TVmaze aka titles for each result, e.g. http://api.tvmaze.com/shows/1/akas
+ results.add(new TVMazeSearchResult(id, name));
+ }
+ }
+
+ return results;
+ }
+
+ protected SeriesInfo fetchSeriesInfo(TVMazeSearchResult show, SortOrder sortOrder, Locale locale) throws IOException {
+ // e.g. http://api.tvmaze.com/shows/1
+ JsonObject, ?> response = request("shows/" + show.getId());
+
+ String status = (String) response.get("status");
+ String runtime = response.get("runtime").toString();
+ String premiered = (String) response.get("premiered");
+ JsonObject, ?> genres = (JsonObject, ?>) response.get("genres");
+ JsonObject, ?> rating = (JsonObject, ?>) response.get("rating");
+
+ SeriesInfo seriesInfo = new SeriesInfo(getName(), sortOrder, locale, show.getId());
+ seriesInfo.setName(show.getName());
+ seriesInfo.setAliasNames(show.getEffectiveNames());
+ seriesInfo.setStatus(status);
+ seriesInfo.setRuntime(new Integer(runtime));
+ seriesInfo.setStartDate(SimpleDate.parse(premiered, "yyyy-MM-dd"));
+
+ if (genres != null && genres.isArray()) {
+ seriesInfo.setGenres(Arrays.stream(genres.getArray()).map(Objects::toString).collect(Collectors.toList()));
+ }
+ if (rating != null && rating.isMap() && !rating.isEmpty()) {
+ seriesInfo.setRating(new Double((String) rating.get("average")));
+ }
+
+ return seriesInfo;
+ }
+
+ @Override
+ protected SeriesData fetchSeriesData(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
+ TVMazeSearchResult show = (TVMazeSearchResult) searchResult;
+
+ SeriesInfo seriesInfo = fetchSeriesInfo(show, sortOrder, locale);
+
+ // e.g. http://api.tvmaze.com/shows/1/episodes
+ JsonObject, ?> response = request("shows/" + show.getId() + "/episodes");
+
+ List episodes = new ArrayList(25);
+
+ if (response.isArray()) {
+ for (Object element : response.getArray()) {
+ JsonObject, ?> episode = (JsonObject, ?>) element;
+ try {
+ String episodeTitle = episode.get("name").toString();
+ Integer seasonNumber = Integer.parseInt(episode.get("season").toString());
+ Integer episodeNumber = Integer.parseInt(episode.get("number").toString());
+ SimpleDate airdate = SimpleDate.parse(episode.get("airdate").toString(), "yyyy-MM-dd");
+
+ episodes.add(new Episode(seriesInfo.getName(), seasonNumber, episodeNumber, episodeTitle, null, null, airdate, new SeriesInfo(seriesInfo)));
+ } catch (Exception e) {
+ Logger.getLogger(TVMazeClient.class.getName()).log(Level.WARNING, "Illegal episode data: " + e + ": " + episode);
+ }
+ }
+ }
+
+ return new SeriesData(seriesInfo, episodes);
+ }
+
+ public JsonObject, ?> request(String resource) throws IOException {
+ return new CachedJsonResource("http://api.tvmaze.com/" + resource).getJSON();
+ }
+
+ @Override
+ public URI getEpisodeListLink(SearchResult searchResult) {
+ return URI.create("http://www.tvmaze.com/shows/" + ((TVMazeSearchResult) searchResult).getId());
+ }
+
+}
diff --git a/source/net/filebot/web/TVMazeSearchResult.java b/source/net/filebot/web/TVMazeSearchResult.java
new file mode 100644
index 00000000..903642d8
--- /dev/null
+++ b/source/net/filebot/web/TVMazeSearchResult.java
@@ -0,0 +1,40 @@
+package net.filebot.web;
+
+public class TVMazeSearchResult extends SearchResult {
+
+ protected int id;
+
+ protected TVMazeSearchResult() {
+ // used by serializer
+ }
+
+ public TVMazeSearchResult(int id, String name) {
+ super(name, new String[0]);
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ @Override
+ public int hashCode() {
+ return id;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object instanceof TVMazeSearchResult) {
+ TVMazeSearchResult other = (TVMazeSearchResult) object;
+ return this.id == other.id;
+ }
+
+ return false;
+ }
+
+ @Override
+ public TVMazeSearchResult clone() {
+ return new TVMazeSearchResult(id, name);
+ }
+
+}
diff --git a/source/net/filebot/web/TVRageClient.java b/source/net/filebot/web/TVRageClient.java
deleted file mode 100644
index b8174c64..00000000
--- a/source/net/filebot/web/TVRageClient.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package net.filebot.web;
-
-import static java.util.Collections.*;
-import static net.filebot.util.XPathUtilities.*;
-import static net.filebot.web.EpisodeUtilities.*;
-import static net.filebot.web.WebRequest.*;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import javax.swing.Icon;
-
-import net.filebot.Cache;
-import net.filebot.ResourceManager;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Node;
-import org.xml.sax.SAXException;
-
-public class TVRageClient extends AbstractEpisodeListProvider {
-
- private String host = "services.tvrage.com";
-
- private String apikey;
-
- public TVRageClient(String apikey) {
- this.apikey = apikey;
- }
-
- @Override
- public String getName() {
- return "TVRage";
- }
-
- @Override
- public Icon getIcon() {
- return ResourceManager.getIcon("search.tvrage");
- }
-
- @Override
- public boolean hasSeasonSupport() {
- return true;
- }
-
- @Override
- protected SortOrder vetoRequestParameter(SortOrder order) {
- return SortOrder.Airdate;
- }
-
- @Override
- protected Locale vetoRequestParameter(Locale language) {
- return Locale.ENGLISH;
- }
-
- @Override
- public ResultCache getCache() {
- return new ResultCache(getName(), Cache.getCache("web-datasource"));
- }
-
- @Override
- public List fetchSearchResult(String query, Locale locale) throws IOException, SAXException {
- Document dom = request("/feeds/full_search.php", singletonMap("show", query));
-
- List nodes = selectNodes("Results/show", dom);
- List searchResults = new ArrayList(nodes.size());
-
- for (Node node : nodes) {
- int showid = Integer.parseInt(getTextContent("showid", node));
- String name = getTextContent("name", node);
- String link = getTextContent("link", node);
-
- searchResults.add(new TVRageSearchResult(name, showid, link));
- }
-
- return searchResults;
- }
-
- @Override
- protected SeriesData fetchSeriesData(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
- TVRageSearchResult series = (TVRageSearchResult) searchResult;
- Document dom = request("/feeds/full_show_info.php", singletonMap("sid", series.getId()));
-
- // parse series data
- Node seriesNode = selectNode("Show", dom);
- SeriesInfo seriesInfo = new SeriesInfo(getName(), sortOrder, locale, series.getId());
- seriesInfo.setAliasNames(searchResult.getEffectiveNames());
-
- seriesInfo.setName(getTextContent("name", seriesNode));
- seriesInfo.setNetwork(getTextContent("network", seriesNode));
- seriesInfo.setRuntime(getInteger(getTextContent("runtime", seriesNode)));
- seriesInfo.setStatus(getTextContent("status", seriesNode));
-
- seriesInfo.setGenres(getListContent("genre", null, getChild("genres", seriesNode)));
- seriesInfo.setStartDate(SimpleDate.parse(selectString("started", seriesNode), "MMM/dd/yyyy"));
-
- // parse episode data
- List episodes = new ArrayList(25);
- List specials = new ArrayList(5);
-
- // episodes and specials
- for (Node node : selectNodes("//episode", dom)) {
- String title = getTextContent("title", node);
- Integer episodeNumber = getInteger(getTextContent("seasonnum", node));
- String seasonIdentifier = getAttribute("no", node.getParentNode());
- Integer seasonNumber = seasonIdentifier == null ? null : new Integer(seasonIdentifier);
- SimpleDate airdate = SimpleDate.parse(getTextContent("airdate", node), "yyyy-MM-dd");
-
- // check if we have season and episode number, if not it must be a special episode
- if (episodeNumber == null || seasonNumber == null) {
- // handle as special episode
- seasonNumber = getInteger(getTextContent("season", node));
- int specialNumber = filterBySeason(specials, seasonNumber).size() + 1;
- specials.add(new Episode(seriesInfo.getName(), seasonNumber, null, title, null, specialNumber, airdate, new SeriesInfo(seriesInfo)));
- } else {
- // handle as normal episode
- if (sortOrder == SortOrder.Absolute) {
- episodeNumber = getInteger(getTextContent("epnum", node));
- seasonNumber = null;
- }
- episodes.add(new Episode(seriesInfo.getName(), seasonNumber, episodeNumber, title, null, null, airdate, new SeriesInfo(seriesInfo)));
- }
- }
-
- // add specials at the end
- episodes.addAll(specials);
-
- return new SeriesData(seriesInfo, episodes);
- }
-
- public Document request(String resource, Map parameters) throws IOException, SAXException {
- Map param = new LinkedHashMap(parameters);
- if (apikey != null) {
- param.put("key", apikey);
- }
- URL url = new URL("http", host, resource + "?" + encodeParameters(param, true));
- return getDocument(url);
- }
-
- @Override
- protected SearchResult createSearchResult(int id) {
- return new TVRageSearchResult(null, id, null);
- }
-
- @Override
- public URI getEpisodeListLink(SearchResult searchResult) {
- return URI.create(((TVRageSearchResult) searchResult).getLink() + "/episode_list/all");
- }
-
-}
diff --git a/source/net/filebot/web/TVRageSearchResult.java b/source/net/filebot/web/TVRageSearchResult.java
deleted file mode 100644
index f431adef..00000000
--- a/source/net/filebot/web/TVRageSearchResult.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package net.filebot.web;
-
-public class TVRageSearchResult extends SearchResult {
-
- protected int showId;
- protected String link;
-
- protected TVRageSearchResult() {
- // used by serializer
- }
-
- public TVRageSearchResult(String name, int showId, String link) {
- super(name, new String[0]);
- this.showId = showId;
- this.link = link;
- }
-
- public int getId() {
- return showId;
- }
-
- public int getSeriesId() {
- return showId;
- }
-
- public String getLink() {
- return link;
- }
-
- @Override
- public int hashCode() {
- return showId;
- }
-
- @Override
- public boolean equals(Object object) {
- if (object instanceof TVRageSearchResult) {
- TVRageSearchResult other = (TVRageSearchResult) object;
- return this.showId == other.showId;
- }
-
- return false;
- }
-
- @Override
- public TVRageSearchResult clone() {
- return new TVRageSearchResult(name, showId, link);
- }
-
-}
diff --git a/test/net/filebot/web/TVRageClientTest.java b/test/net/filebot/web/TVMazeClientTest.java
similarity index 65%
rename from test/net/filebot/web/TVRageClientTest.java
rename to test/net/filebot/web/TVMazeClientTest.java
index fcab3089..a13a4cfc 100644
--- a/test/net/filebot/web/TVRageClientTest.java
+++ b/test/net/filebot/web/TVMazeClientTest.java
@@ -7,29 +7,28 @@ import java.util.Locale;
import org.junit.Test;
-public class TVRageClientTest {
+public class TVMazeClientTest {
/**
* 145 episodes / 7 seasons
*/
- private static TVRageSearchResult buffySearchResult = new TVRageSearchResult("Buffy the Vampire Slayer", 2930, "http://www.tvrage.com/Buffy_The_Vampire_Slayer");
+ private static TVMazeSearchResult buffySearchResult = new TVMazeSearchResult(427, "Buffy the Vampire Slayer");
@Test
public void search() throws Exception {
- List results = tvrage.search("Buffy", Locale.ENGLISH);
+ List results = client.search("Buffy", Locale.ENGLISH);
- TVRageSearchResult result = (TVRageSearchResult) results.get(0);
+ TVMazeSearchResult result = (TVMazeSearchResult) results.get(0);
assertEquals(buffySearchResult.getName(), result.getName());
- assertEquals(buffySearchResult.getSeriesId(), result.getSeriesId());
- assertEquals(buffySearchResult.getLink(), result.getLink());
+ assertEquals(buffySearchResult.getId(), result.getId());
}
- private TVRageClient tvrage = new TVRageClient("5AhRvLfKAP10unE9Vnfr");
+ private TVMazeClient client = new TVMazeClient();
@Test
public void getEpisodeList() throws Exception {
- List list = EpisodeUtilities.filterBySeason(tvrage.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH), 7);
+ List list = EpisodeUtilities.filterBySeason(client.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH), 7);
assertEquals(22, list.size());
@@ -46,7 +45,7 @@ public class TVRageClientTest {
@Test
public void getEpisodeListAll() throws Exception {
- List list = tvrage.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH);
+ List list = client.getEpisodeList(buffySearchResult, SortOrder.Airdate, Locale.ENGLISH);
assertEquals(143, list.size());
@@ -62,7 +61,7 @@ public class TVRageClientTest {
@Test
public void getEpisodeListLinkAll() throws Exception {
- assertEquals(tvrage.getEpisodeListLink(buffySearchResult).toString(), "http://www.tvrage.com/Buffy_The_Vampire_Slayer/episode_list/all");
+ assertEquals("http://www.tvmaze.com/shows/427", client.getEpisodeListLink(buffySearchResult).toString());
}
}