PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/tvrenamer/controller/TheTVDBProvider.java

http://github.com/tvrenamer/tvrenamer
Java | 271 lines | 206 code | 35 blank | 30 comment | 12 complexity | 525503566befb0f5f6d088dc9f24cd2b MD5 | raw file
Possible License(s): GPL-2.0
  1. package org.tvrenamer.controller;
  2. import static org.tvrenamer.controller.util.XPathUtilities.nodeListValue;
  3. import static org.tvrenamer.controller.util.XPathUtilities.nodeTextValue;
  4. import static org.tvrenamer.model.util.Constants.*;
  5. import org.tvrenamer.controller.util.StringUtils;
  6. import org.tvrenamer.model.DiscontinuedApiException;
  7. import org.tvrenamer.model.EpisodeInfo;
  8. import org.tvrenamer.model.Series;
  9. import org.tvrenamer.model.ShowName;
  10. import org.tvrenamer.model.TVRenamerIOException;
  11. import org.w3c.dom.DOMException;
  12. import org.w3c.dom.Document;
  13. import org.w3c.dom.Node;
  14. import org.w3c.dom.NodeList;
  15. import org.xml.sax.InputSource;
  16. import org.xml.sax.SAXException;
  17. import java.io.FileNotFoundException;
  18. import java.io.IOException;
  19. import java.io.StringReader;
  20. import java.util.logging.Level;
  21. import java.util.logging.Logger;
  22. import javax.xml.parsers.DocumentBuilder;
  23. import javax.xml.parsers.DocumentBuilderFactory;
  24. import javax.xml.parsers.ParserConfigurationException;
  25. import javax.xml.xpath.XPathExpressionException;
  26. public class TheTVDBProvider {
  27. private static final Logger logger = Logger.getLogger(TheTVDBProvider.class.getName());
  28. // The unique API key for our application
  29. private static final String API_KEY = "4A9560FF0B2670B2";
  30. // Whether or not we should try making v1 API calls
  31. private static boolean apiIsDeprecated = false;
  32. // The base information for the provider
  33. private static final String DEFAULT_SITE_URL = "http://thetvdb.com/";
  34. private static final String API_URL = DEFAULT_SITE_URL + "api/";
  35. // The URL to get, to receive options for a given series search string.
  36. // Note, does not take API key.
  37. private static final String BASE_SEARCH_URL = API_URL + "GetSeries.php?seriesname=";
  38. // These are the tags that we use to extract the relevant information from the show document.
  39. private static final String XPATH_SERIES = "/Data/Series";
  40. private static final String XPATH_SERIES_ID = "seriesid";
  41. private static final String XPATH_NAME = "SeriesName";
  42. private static final String SERIES_NOT_PERMITTED = "** 403: Series Not Permitted **";
  43. // The URL to get, to receive listings for a specific given series.
  44. private static final String BASE_LIST_URL = API_URL + API_KEY + "/series/";
  45. private static final String BASE_LIST_FILENAME = "/all/" + DEFAULT_LANGUAGE + XML_SUFFIX;
  46. // These are the tags that we use to extract the episode information
  47. // from the listings document.
  48. private static final String XPATH_EPISODE_LIST = "/Data/Episode";
  49. private static final String XPATH_EPISODE_ID = "id";
  50. private static final String XPATH_SEASON_NUM = "SeasonNumber";
  51. private static final String XPATH_EPISODE_NUM = "EpisodeNumber";
  52. private static final String XPATH_EPISODE_NAME = "EpisodeName";
  53. private static final String XPATH_AIRDATE = "FirstAired";
  54. // private static final String XPATH_EPISODE_SERIES_ID = "seriesid";
  55. private static final String XPATH_DVD_SEASON_NUM = "DVD_season";
  56. private static final String XPATH_DVD_EPISODE_NUM = "DVD_episodenumber";
  57. // private static final String XPATH_EPISODE_NUM_ABS = "absolute_number";
  58. private static String getShowSearchXml(final String queryString)
  59. throws TVRenamerIOException, DiscontinuedApiException
  60. {
  61. if (apiIsDeprecated) {
  62. throw new DiscontinuedApiException();
  63. }
  64. String searchURL = BASE_SEARCH_URL + StringUtils.encodeUrlCharacters(queryString);
  65. logger.fine("About to download search results from " + searchURL);
  66. String content = new HttpConnectionHandler().downloadUrl(searchURL);
  67. return StringUtils.encodeSpecialCharacters(content);
  68. }
  69. private static String getSeriesListingXml(final Series series)
  70. throws TVRenamerIOException, DiscontinuedApiException
  71. {
  72. if (apiIsDeprecated) {
  73. throw new DiscontinuedApiException();
  74. }
  75. int seriesId = series.getId();
  76. String seriesURL = BASE_LIST_URL + seriesId + BASE_LIST_FILENAME;
  77. logger.fine("Downloading episode listing from " + seriesURL);
  78. String content = new HttpConnectionHandler().downloadUrl(seriesURL);
  79. return StringUtils.encodeSpecialCharacters(content);
  80. }
  81. private static void collectShowOptions(final NodeList shows, final ShowName showName)
  82. throws XPathExpressionException
  83. {
  84. for (int i = 0; i < shows.getLength(); i++) {
  85. Node eNode = shows.item(i);
  86. String seriesName = nodeTextValue(XPATH_NAME, eNode);
  87. String tvdbId = nodeTextValue(XPATH_SERIES_ID, eNode);
  88. if (SERIES_NOT_PERMITTED.equals(seriesName)) {
  89. logger.warning("ignoring unpermitted option for "
  90. + showName.getExampleFilename());
  91. } else {
  92. showName.addShowOption(tvdbId, seriesName);
  93. }
  94. }
  95. }
  96. private static void readShowsFromInputSource(final DocumentBuilder bld,
  97. final InputSource searchXmlSource,
  98. final ShowName showName)
  99. throws TVRenamerIOException
  100. {
  101. try {
  102. Document doc = bld.parse(searchXmlSource);
  103. NodeList shows = nodeListValue(XPATH_SERIES, doc);
  104. collectShowOptions(shows, showName);
  105. } catch (SAXException | XPathExpressionException | DOMException | IOException e) {
  106. logger.log(Level.WARNING, ERROR_PARSING_XML, e);
  107. throw new TVRenamerIOException(ERROR_PARSING_XML, e);
  108. }
  109. }
  110. private static synchronized boolean isApiDiscontinuedError(Throwable e) {
  111. if (apiIsDeprecated) {
  112. return true;
  113. }
  114. while (e != null) {
  115. if (e instanceof FileNotFoundException) {
  116. apiIsDeprecated = true;
  117. return true;
  118. }
  119. e = e.getCause();
  120. }
  121. return false;
  122. }
  123. /**
  124. * Fetch the show options from the provider, for the given show name.
  125. *
  126. * @param showName
  127. * the show name to fetch the options for
  128. * @throws DiscontinuedApiException if it appears that the API we are using
  129. * is no longer supported
  130. * @throws TVRenamerIOException if anything else goes wrong; this could
  131. * include network difficulties or difficulty parsing the XML.
  132. */
  133. public static void getShowOptions(final ShowName showName)
  134. throws TVRenamerIOException, DiscontinuedApiException
  135. {
  136. DocumentBuilder bld;
  137. try {
  138. bld = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  139. } catch (ParserConfigurationException e) {
  140. logger.log(Level.WARNING, "could not create DocumentBuilder: " + e.getMessage(), e);
  141. throw new TVRenamerIOException(ERROR_PARSING_XML, e);
  142. }
  143. String searchXml = "";
  144. try {
  145. searchXml = getShowSearchXml(showName.getQueryString());
  146. InputSource source = new InputSource(new StringReader(searchXml));
  147. readShowsFromInputSource(bld, source, showName);
  148. } catch (TVRenamerIOException tve) {
  149. String msg = "error parsing XML from " + searchXml + " for series "
  150. + showName.getExampleFilename();
  151. if (isApiDiscontinuedError(tve)) {
  152. throw new DiscontinuedApiException();
  153. } else {
  154. logger.log(Level.WARNING, msg, tve);
  155. }
  156. throw new TVRenamerIOException(msg, tve);
  157. }
  158. }
  159. private static EpisodeInfo createEpisodeInfo(final Node eNode) {
  160. try {
  161. return new EpisodeInfo.Builder()
  162. .episodeId(nodeTextValue(XPATH_EPISODE_ID, eNode))
  163. .seasonNumber(nodeTextValue(XPATH_SEASON_NUM, eNode))
  164. .episodeNumber(nodeTextValue(XPATH_EPISODE_NUM, eNode))
  165. .episodeName(nodeTextValue(XPATH_EPISODE_NAME, eNode))
  166. .firstAired(nodeTextValue(XPATH_AIRDATE, eNode))
  167. .dvdSeason(nodeTextValue(XPATH_DVD_SEASON_NUM, eNode))
  168. .dvdEpisodeNumber(nodeTextValue(XPATH_DVD_EPISODE_NUM, eNode))
  169. .build();
  170. } catch (Exception e) {
  171. logger.log(Level.WARNING, "exception parsing episode", e);
  172. }
  173. return null;
  174. }
  175. private static NodeList getEpisodeList(final Series series)
  176. throws TVRenamerIOException
  177. {
  178. NodeList episodeList;
  179. DocumentBuilder dbf;
  180. try {
  181. dbf = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  182. } catch (ParserConfigurationException e) {
  183. logger.log(Level.WARNING, "could not create DocumentBuilder: " + e.getMessage(), e);
  184. throw new TVRenamerIOException(ERROR_PARSING_XML, e);
  185. }
  186. try {
  187. String listingsXml = getSeriesListingXml(series);
  188. InputSource listingsXmlSource = new InputSource(new StringReader(listingsXml));
  189. Document doc = dbf.parse(listingsXmlSource);
  190. episodeList = nodeListValue(XPATH_EPISODE_LIST, doc);
  191. } catch (XPathExpressionException | SAXException | DOMException e) {
  192. logger.log(Level.WARNING, "exception parsing episodes for " + series + ": "
  193. + e.getMessage(), e);
  194. throw new TVRenamerIOException(ERROR_PARSING_XML, e);
  195. } catch (NumberFormatException nfe) {
  196. logger.log(Level.WARNING, nfe.getMessage(), nfe);
  197. throw new TVRenamerIOException(ERROR_PARSING_NUMBERS, nfe);
  198. } catch (IOException ioe) {
  199. logger.log(Level.WARNING, ioe.getMessage(), ioe);
  200. throw new TVRenamerIOException(DOWNLOADING_FAILED_MESSAGE, ioe);
  201. }
  202. return episodeList;
  203. }
  204. /**
  205. * Fetch the episode listings from the provider, for the given Series.
  206. *
  207. * @param series
  208. * the Series to fetch the episode listings for
  209. * @throws TVRenamerIOException if anything goes wrong; this could include
  210. * network difficulties, difficulty parsing the XML, or problems parsing
  211. * data expected to be numeric.
  212. */
  213. public static void getSeriesListing(final Series series)
  214. throws TVRenamerIOException
  215. {
  216. try {
  217. NodeList episodes = getEpisodeList(series);
  218. int episodeCount = episodes.getLength();
  219. EpisodeInfo[] episodeInfos = new EpisodeInfo[episodeCount];
  220. for (int i = 0; i < episodeCount; i++) {
  221. episodeInfos[i] = createEpisodeInfo(episodes.item(i));
  222. }
  223. series.addEpisodeInfos(episodeInfos);
  224. series.listingsSucceeded();
  225. } catch (DOMException dom) {
  226. logger.log(Level.WARNING, dom.getMessage(), dom);
  227. throw new TVRenamerIOException(ERROR_PARSING_XML, dom);
  228. } catch (NumberFormatException nfe) {
  229. logger.log(Level.WARNING, nfe.getMessage(), nfe);
  230. throw new TVRenamerIOException(ERROR_PARSING_NUMBERS, nfe);
  231. } catch (IOException ioe) {
  232. logger.warning(ioe.getMessage());
  233. throw new TVRenamerIOException(DOWNLOADING_FAILED_MESSAGE, ioe);
  234. }
  235. }
  236. }