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