mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-07 02:23:38 +00:00
Merge branch 'master' into develop
This commit is contained in:
commit
8b384e22d7
11 changed files with 13 additions and 1104 deletions
|
@ -74,6 +74,11 @@
|
||||||
* Update cachecontrol library 0.11.5 to 0.11.7 (3b3b776)
|
* Update cachecontrol library 0.11.5 to 0.11.7 (3b3b776)
|
||||||
|
|
||||||
|
|
||||||
|
### 0.12.23 (2017-07-18 16:55:00 UTC)
|
||||||
|
|
||||||
|
* Remove obsolete tvrage_api lib
|
||||||
|
|
||||||
|
|
||||||
### 0.12.22 (2017-07-13 20:20:00 UTC)
|
### 0.12.22 (2017-07-13 20:20:00 UTC)
|
||||||
|
|
||||||
* Fix "Server failed to return anything useful" when should be using cached .torrent file
|
* Fix "Server failed to return anything useful" when should be using cached .torrent file
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
Original Author:
|
|
||||||
------------
|
|
||||||
* Christian Kreutzer
|
|
||||||
|
|
||||||
Contributors
|
|
||||||
------------
|
|
||||||
* topdeck (http://bitbucket.org/topdeck)
|
|
||||||
* samueltardieu (http://bitbucket.org/samueltardieu)
|
|
||||||
* chevox (https://bitbucket.org/chexov)
|
|
|
@ -1,26 +0,0 @@
|
||||||
Copyright (c) 2009, Christian Kreutzer
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions, and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions, and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the author of this software nor the name of
|
|
||||||
contributors to this software may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written consent.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,29 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from distutils.core import setup
|
|
||||||
from tvrage import __version__, __author__, __license__
|
|
||||||
|
|
||||||
setup(name='python-tvrage',
|
|
||||||
description='python client for the tvrage.com XML API',
|
|
||||||
long_description = file(
|
|
||||||
os.path.join(os.path.dirname(__file__),'README.rst')).read(),
|
|
||||||
license=__license__,
|
|
||||||
version=__version__,
|
|
||||||
author=__author__,
|
|
||||||
author_email='herr.kreutzer@gmail.com',
|
|
||||||
# url='http://bitbucket.org/ckreutzer/python-tvrage/',
|
|
||||||
url='https://github.com/ckreutzer/python-tvrage',
|
|
||||||
packages=['tvrage'],
|
|
||||||
install_requires = ["BeautifulSoup"],
|
|
||||||
classifiers = [
|
|
||||||
'Development Status :: 4 - Beta',
|
|
||||||
'Intended Audience :: Developers',
|
|
||||||
'License :: OSI Approved :: BSD License',
|
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
||||||
'Programming Language :: Python',
|
|
||||||
'Operating System :: OS Independent'
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,701 +0,0 @@
|
||||||
# !/usr/bin/env python2
|
|
||||||
# encoding:utf-8
|
|
||||||
#author:dbr/Ben (ripped from tvdb:echel0n)
|
|
||||||
#project:tvrage_api
|
|
||||||
#license:unlicense (http://unlicense.org/)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Modified from http://github.com/dbr/tvrage_api
|
|
||||||
Simple-to-use Python interface to The TVRage's API (tvrage.com)
|
|
||||||
"""
|
|
||||||
from functools import wraps
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import getpass
|
|
||||||
import tempfile
|
|
||||||
import warnings
|
|
||||||
import logging
|
|
||||||
import datetime as dt
|
|
||||||
import requests
|
|
||||||
import requests.exceptions
|
|
||||||
import xmltodict
|
|
||||||
from sickbeard.network_timezones import standardize_network
|
|
||||||
|
|
||||||
try:
|
|
||||||
import xml.etree.cElementTree as ElementTree
|
|
||||||
except ImportError:
|
|
||||||
import xml.etree.ElementTree as ElementTree
|
|
||||||
|
|
||||||
from lib.dateutil.parser import parse
|
|
||||||
from lib.cachecontrol import CacheControl, caches
|
|
||||||
|
|
||||||
from tvrage_ui import BaseUI
|
|
||||||
from tvrage_exceptions import (tvrage_error, tvrage_userabort, tvrage_shownotfound,
|
|
||||||
tvrage_seasonnotfound, tvrage_episodenotfound, tvrage_attributenotfound)
|
|
||||||
|
|
||||||
|
|
||||||
def log():
|
|
||||||
return logging.getLogger("tvrage_api")
|
|
||||||
|
|
||||||
|
|
||||||
def retry(ExceptionToCheck, tries=4, delay=3, backoff=2, logger=None):
|
|
||||||
"""Retry calling the decorated function using an exponential backoff.
|
|
||||||
|
|
||||||
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
|
|
||||||
original from: http://wiki.python.org/moin/PythonDecoratorLibrary#Retry
|
|
||||||
|
|
||||||
:param ExceptionToCheck: the exception to check. may be a tuple of
|
|
||||||
exceptions to check
|
|
||||||
:type ExceptionToCheck: Exception or tuple
|
|
||||||
:param tries: number of times to try (not retry) before giving up
|
|
||||||
:type tries: int
|
|
||||||
:param delay: initial delay between retries in seconds
|
|
||||||
:type delay: int
|
|
||||||
:param backoff: backoff multiplier e.g. value of 2 will double the delay
|
|
||||||
each retry
|
|
||||||
:type backoff: int
|
|
||||||
:param logger: logger to use. If None, print
|
|
||||||
:type logger: logging.Logger instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
def deco_retry(f):
|
|
||||||
|
|
||||||
@wraps(f)
|
|
||||||
def f_retry(*args, **kwargs):
|
|
||||||
mtries, mdelay = tries, delay
|
|
||||||
while mtries > 1:
|
|
||||||
try:
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
except ExceptionToCheck, e:
|
|
||||||
msg = "%s, Retrying in %d seconds..." % (str(e), mdelay)
|
|
||||||
if logger:
|
|
||||||
logger.warning(msg)
|
|
||||||
else:
|
|
||||||
print msg
|
|
||||||
time.sleep(mdelay)
|
|
||||||
mtries -= 1
|
|
||||||
mdelay *= backoff
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return f_retry # true decorator
|
|
||||||
|
|
||||||
return deco_retry
|
|
||||||
|
|
||||||
|
|
||||||
class ShowContainer(dict):
|
|
||||||
"""Simple dict that holds a series of Show instances
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._stack = []
|
|
||||||
self._lastgc = time.time()
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
self._stack.append(key)
|
|
||||||
|
|
||||||
#keep only the 100th latest results
|
|
||||||
if time.time() - self._lastgc > 20:
|
|
||||||
for o in self._stack[:-100]:
|
|
||||||
del self[o]
|
|
||||||
|
|
||||||
self._stack = self._stack[-100:]
|
|
||||||
|
|
||||||
self._lastgc = time.time()
|
|
||||||
|
|
||||||
super(ShowContainer, self).__setitem__(key, value)
|
|
||||||
|
|
||||||
|
|
||||||
class Show(dict):
|
|
||||||
"""Holds a dict of seasons, and show data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
dict.__init__(self)
|
|
||||||
self.data = {}
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Show %s (containing %s seasons)>" % (
|
|
||||||
self.data.get(u'seriesname', 'instance'),
|
|
||||||
len(self)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
if key in self:
|
|
||||||
# Key is an episode, return it
|
|
||||||
return self[key]
|
|
||||||
|
|
||||||
if key in self.data:
|
|
||||||
# Non-numeric request is for show-data
|
|
||||||
return self.data[key]
|
|
||||||
|
|
||||||
raise AttributeError
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
if key in self:
|
|
||||||
# Key is an episode, return it
|
|
||||||
return dict.__getitem__(self, key)
|
|
||||||
|
|
||||||
if key in self.data:
|
|
||||||
# Non-numeric request is for show-data
|
|
||||||
return dict.__getitem__(self.data, key)
|
|
||||||
|
|
||||||
# Data wasn't found, raise appropriate error
|
|
||||||
if isinstance(key, int) or key.isdigit():
|
|
||||||
# Episode number x was not found
|
|
||||||
raise tvrage_seasonnotfound("Could not find season %s" % (repr(key)))
|
|
||||||
else:
|
|
||||||
# If it's not numeric, it must be an attribute name, which
|
|
||||||
# doesn't exist, so attribute error.
|
|
||||||
raise tvrage_attributenotfound("Cannot find attribute %s" % (repr(key)))
|
|
||||||
|
|
||||||
def aired_on(self, date):
|
|
||||||
ret = self.search(str(date), 'firstaired')
|
|
||||||
if len(ret) == 0:
|
|
||||||
raise tvrage_episodenotfound("Could not find any episodes that aired on %s" % date)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def search(self, term=None, key=None):
|
|
||||||
"""
|
|
||||||
Search all episodes in show. Can search all data, or a specific key (for
|
|
||||||
example, episodename)
|
|
||||||
|
|
||||||
Always returns an array (can be empty). First index contains the first
|
|
||||||
match, and so on.
|
|
||||||
|
|
||||||
Each array index is an Episode() instance, so doing
|
|
||||||
search_results[0]['episodename'] will retrieve the episode name of the
|
|
||||||
first match.
|
|
||||||
|
|
||||||
Search terms are converted to lower case (unicode) strings.
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for cur_season in self.values():
|
|
||||||
searchresult = cur_season.search(term=term, key=key)
|
|
||||||
if len(searchresult) != 0:
|
|
||||||
results.extend(searchresult)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
class Season(dict):
|
|
||||||
def __init__(self, show=None):
|
|
||||||
"""The show attribute points to the parent show
|
|
||||||
"""
|
|
||||||
self.show = show
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<Season instance (containing %s episodes)>" % (
|
|
||||||
len(self.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
def __getattr__(self, episode_number):
|
|
||||||
if episode_number in self:
|
|
||||||
return self[episode_number]
|
|
||||||
raise AttributeError
|
|
||||||
|
|
||||||
def __getitem__(self, episode_number):
|
|
||||||
if episode_number not in self:
|
|
||||||
raise tvrage_episodenotfound("Could not find episode %s" % (repr(episode_number)))
|
|
||||||
else:
|
|
||||||
return dict.__getitem__(self, episode_number)
|
|
||||||
|
|
||||||
def search(self, term=None, key=None):
|
|
||||||
"""Search all episodes in season, returns a list of matching Episode
|
|
||||||
instances.
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for ep in self.values():
|
|
||||||
searchresult = ep.search(term=term, key=key)
|
|
||||||
if searchresult is not None:
|
|
||||||
results.append(
|
|
||||||
searchresult
|
|
||||||
)
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
class Episode(dict):
|
|
||||||
def __init__(self, season=None):
|
|
||||||
"""The season attribute points to the parent season
|
|
||||||
"""
|
|
||||||
self.season = season
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
seasno = int(self.get(u'seasonnumber', 0))
|
|
||||||
epno = int(self.get(u'episodenumber', 0))
|
|
||||||
epname = self.get(u'episodename')
|
|
||||||
if epname is not None:
|
|
||||||
return "<Episode %02dx%02d - %s>" % (seasno, epno, epname)
|
|
||||||
else:
|
|
||||||
return "<Episode %02dx%02d>" % (seasno, epno)
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
if key in self:
|
|
||||||
return self[key]
|
|
||||||
raise AttributeError
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
try:
|
|
||||||
return dict.__getitem__(self, key)
|
|
||||||
except KeyError:
|
|
||||||
raise tvrage_attributenotfound("Cannot find attribute %s" % (repr(key)))
|
|
||||||
|
|
||||||
def search(self, term=None, key=None):
|
|
||||||
"""Search episode data for term, if it matches, return the Episode (self).
|
|
||||||
The key parameter can be used to limit the search to a specific element,
|
|
||||||
for example, episodename.
|
|
||||||
|
|
||||||
This primarily for use use by Show.search and Season.search.
|
|
||||||
"""
|
|
||||||
if term == None:
|
|
||||||
raise TypeError("must supply string to search for (contents)")
|
|
||||||
|
|
||||||
term = unicode(term).lower()
|
|
||||||
for cur_key, cur_value in self.items():
|
|
||||||
cur_key, cur_value = unicode(cur_key).lower(), unicode(cur_value).lower()
|
|
||||||
if key is not None and cur_key != key:
|
|
||||||
# Do not search this key
|
|
||||||
continue
|
|
||||||
if cur_value.find(unicode(term).lower()) > -1:
|
|
||||||
return self
|
|
||||||
|
|
||||||
|
|
||||||
class TVRage:
|
|
||||||
"""Create easy-to-use interface to name of season/episode name"""
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
interactive=False,
|
|
||||||
select_first=False,
|
|
||||||
debug=False,
|
|
||||||
cache=True,
|
|
||||||
banners=False,
|
|
||||||
actors=False,
|
|
||||||
custom_ui=None,
|
|
||||||
language=None,
|
|
||||||
search_all_languages=False,
|
|
||||||
apikey=None,
|
|
||||||
forceConnect=False,
|
|
||||||
useZip=False,
|
|
||||||
dvdorder=False,
|
|
||||||
proxy=None):
|
|
||||||
|
|
||||||
"""
|
|
||||||
cache (True/False/str/unicode/urllib2 opener):
|
|
||||||
Retrieved XML are persisted to to disc. If true, stores in
|
|
||||||
tvrage_api folder under your systems TEMP_DIR, if set to
|
|
||||||
str/unicode instance it will use this as the cache
|
|
||||||
location. If False, disables caching. Can also be passed
|
|
||||||
an arbitrary Python object, which is used as a urllib2
|
|
||||||
opener, which should be created by urllib2.build_opener
|
|
||||||
|
|
||||||
forceConnect (bool):
|
|
||||||
If true it will always try to connect to tvrage.com even if we
|
|
||||||
recently timed out. By default it will wait one minute before
|
|
||||||
trying again, and any requests within that one minute window will
|
|
||||||
return an exception immediately.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.shows = ShowContainer() # Holds all Show classes
|
|
||||||
self.corrections = {} # Holds show-name to show_id mapping
|
|
||||||
|
|
||||||
self.config = {}
|
|
||||||
|
|
||||||
if apikey is not None:
|
|
||||||
self.config['apikey'] = apikey
|
|
||||||
else:
|
|
||||||
self.config['apikey'] = "Uhewg1Rr0o62fvZvUIZt" # tvdb_api's API key
|
|
||||||
|
|
||||||
self.config['debug_enabled'] = debug # show debugging messages
|
|
||||||
|
|
||||||
self.config['custom_ui'] = custom_ui
|
|
||||||
|
|
||||||
self.config['proxy'] = proxy
|
|
||||||
|
|
||||||
if cache is True:
|
|
||||||
self.config['cache_enabled'] = True
|
|
||||||
self.config['cache_location'] = self._getTempDir()
|
|
||||||
elif cache is False:
|
|
||||||
self.config['cache_enabled'] = False
|
|
||||||
elif isinstance(cache, basestring):
|
|
||||||
self.config['cache_enabled'] = True
|
|
||||||
self.config['cache_location'] = cache
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid value for Cache %r (type was %s)" % (cache, type(cache)))
|
|
||||||
|
|
||||||
if self.config['debug_enabled']:
|
|
||||||
warnings.warn("The debug argument to tvrage_api.__init__ will be removed in the next version. "
|
|
||||||
"To enable debug messages, use the following code before importing: "
|
|
||||||
"import logging; logging.basicConfig(level=logging.DEBUG)")
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
# List of language from http://tvrage.com/api/0629B785CE550C8D/languages.xml
|
|
||||||
# Hard-coded here as it is realtively static, and saves another HTTP request, as
|
|
||||||
# recommended on http://tvrage.com/wiki/index.php/API:languages.xml
|
|
||||||
self.config['valid_languages'] = [
|
|
||||||
"da", "fi", "nl", "de", "it", "es", "fr", "pl", "hu", "el", "tr",
|
|
||||||
"ru", "he", "ja", "pt", "zh", "cs", "sl", "hr", "ko", "en", "sv", "no"
|
|
||||||
]
|
|
||||||
|
|
||||||
# tvrage.com should be based around numeric language codes,
|
|
||||||
# but to link to a series like http://tvrage.com/?tab=series&id=79349&lid=16
|
|
||||||
# requires the language ID, thus this mapping is required (mainly
|
|
||||||
# for usage in tvrage_ui - internally tvrage_api will use the language abbreviations)
|
|
||||||
self.config['langabbv_to_id'] = {'el': 20, 'en': 7, 'zh': 27,
|
|
||||||
'it': 15, 'cs': 28, 'es': 16, 'ru': 22, 'nl': 13, 'pt': 26, 'no': 9,
|
|
||||||
'tr': 21, 'pl': 18, 'fr': 17, 'hr': 31, 'de': 14, 'da': 10, 'fi': 11,
|
|
||||||
'hu': 19, 'ja': 25, 'he': 24, 'ko': 32, 'sv': 8, 'sl': 30}
|
|
||||||
|
|
||||||
if language is None:
|
|
||||||
self.config['language'] = 'en'
|
|
||||||
else:
|
|
||||||
if language not in self.config['valid_languages']:
|
|
||||||
raise ValueError("Invalid language %s, options are: %s" % (
|
|
||||||
language, self.config['valid_languages']
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
self.config['language'] = language
|
|
||||||
|
|
||||||
# The following url_ configs are based of the
|
|
||||||
# http://tvrage.com/wiki/index.php/Programmers_API
|
|
||||||
|
|
||||||
self.config['base_url'] = "http://services.tvrage.com"
|
|
||||||
|
|
||||||
self.config['url_getSeries'] = u"%(base_url)s/feeds/full_search.php" % self.config
|
|
||||||
self.config['params_getSeries'] = {"show": ""}
|
|
||||||
|
|
||||||
self.config['url_epInfo'] = u"%(base_url)s/myfeeds/episode_list.php" % self.config
|
|
||||||
self.config['params_epInfo'] = {"key": self.config['apikey'], "sid": ""}
|
|
||||||
|
|
||||||
self.config['url_seriesInfo'] = u"%(base_url)s/myfeeds/showinfo.php" % self.config
|
|
||||||
self.config['params_seriesInfo'] = {"key": self.config['apikey'], "sid": ""}
|
|
||||||
|
|
||||||
self.config['url_updtes_all'] = u"%(base_url)s/myfeeds/currentshows.php" % self.config
|
|
||||||
|
|
||||||
def _getTempDir(self):
|
|
||||||
"""Returns the [system temp dir]/tvrage_api-u501 (or
|
|
||||||
tvrage_api-myuser)
|
|
||||||
"""
|
|
||||||
if hasattr(os, 'getuid'):
|
|
||||||
uid = "u%d" % (os.getuid())
|
|
||||||
else:
|
|
||||||
# For Windows
|
|
||||||
try:
|
|
||||||
uid = getpass.getuser()
|
|
||||||
except ImportError:
|
|
||||||
return os.path.join(tempfile.gettempdir(), "tvrage_api")
|
|
||||||
|
|
||||||
return os.path.join(tempfile.gettempdir(), "tvrage_api-%s" % (uid))
|
|
||||||
|
|
||||||
#@retry(tvrage_error)
|
|
||||||
def _loadUrl(self, url, params=None):
|
|
||||||
log().debug('Retrieving URL %s' % url)
|
|
||||||
|
|
||||||
session = requests.session()
|
|
||||||
|
|
||||||
if self.config['cache_enabled']:
|
|
||||||
session = CacheControl(session, cache=caches.FileCache(self.config['cache_location']))
|
|
||||||
|
|
||||||
if self.config['proxy']:
|
|
||||||
log().debug('Using proxy for URL: %s' % url)
|
|
||||||
session.proxies = {'http': self.config['proxy'], 'https': self.config['proxy']}
|
|
||||||
|
|
||||||
session.headers.update({'Accept-Encoding': 'gzip,deflate'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp = session.get(url.strip(), params=params)
|
|
||||||
except requests.exceptions.HTTPError, e:
|
|
||||||
raise tvrage_error('HTTP error %s while loading URL %s' % (e.errno, url))
|
|
||||||
except requests.exceptions.ConnectionError, e:
|
|
||||||
raise tvrage_error('Connection error %s while loading URL %s' % (e.message, url))
|
|
||||||
except requests.exceptions.Timeout, e:
|
|
||||||
raise tvrage_error('Connection timed out %s while loading URL %s' % (e.message, url))
|
|
||||||
except Exception:
|
|
||||||
raise tvrage_error('Unknown exception while loading URL %s: %s' % (url, traceback.format_exc()))
|
|
||||||
|
|
||||||
def remap_keys(path, key, value):
|
|
||||||
name_map = {
|
|
||||||
'showid': 'id',
|
|
||||||
'showname': 'seriesname',
|
|
||||||
'name': 'seriesname',
|
|
||||||
'summary': 'overview',
|
|
||||||
'started': 'firstaired',
|
|
||||||
'genres': 'genre',
|
|
||||||
'airtime': 'airs_time',
|
|
||||||
'airday': 'airs_dayofweek',
|
|
||||||
'image': 'fanart',
|
|
||||||
'epnum': 'absolute_number',
|
|
||||||
'title': 'episodename',
|
|
||||||
'airdate': 'firstaired',
|
|
||||||
'screencap': 'filename',
|
|
||||||
'seasonnum': 'episodenumber'
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
key = name_map[key.lower()]
|
|
||||||
except (ValueError, TypeError, KeyError):
|
|
||||||
key = key.lower()
|
|
||||||
|
|
||||||
# clean up value and do type changes
|
|
||||||
if value:
|
|
||||||
if isinstance(value, dict):
|
|
||||||
if key == 'network':
|
|
||||||
network = value['#text']
|
|
||||||
country = value['@country']
|
|
||||||
value = standardize_network(network, country)
|
|
||||||
if key == 'genre':
|
|
||||||
value = value['genre']
|
|
||||||
if not value:
|
|
||||||
value = []
|
|
||||||
if not isinstance(value, list):
|
|
||||||
value = [value]
|
|
||||||
value = filter(None, value)
|
|
||||||
value = '|' + '|'.join(value) + '|'
|
|
||||||
try:
|
|
||||||
if key == 'firstaired' and value in '0000-00-00':
|
|
||||||
new_value = str(dt.date.fromordinal(1))
|
|
||||||
new_value = re.sub('([-]0{2})+', '', new_value)
|
|
||||||
fix_date = parse(new_value, fuzzy=True).date()
|
|
||||||
value = fix_date.strftime('%Y-%m-%d')
|
|
||||||
elif key == 'firstaired':
|
|
||||||
value = parse(value, fuzzy=True).date()
|
|
||||||
value = value.strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
#if key == 'airs_time':
|
|
||||||
# value = parse(value).time()
|
|
||||||
# value = value.strftime('%I:%M %p')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return key, value
|
|
||||||
|
|
||||||
if resp.ok:
|
|
||||||
try:
|
|
||||||
return xmltodict.parse(resp.content.strip(), postprocessor=remap_keys)
|
|
||||||
except:
|
|
||||||
return dict([(u'data', None)])
|
|
||||||
|
|
||||||
def _getetsrc(self, url, params=None):
|
|
||||||
"""Loads a URL using caching, returns an ElementTree of the source
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
|
||||||
src = self._loadUrl(url, params).values()[0]
|
|
||||||
return src
|
|
||||||
except:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _setItem(self, sid, seas, ep, attrib, value):
|
|
||||||
"""Creates a new episode, creating Show(), Season() and
|
|
||||||
Episode()s as required. Called by _getShowData to populate show
|
|
||||||
|
|
||||||
Since the nice-to-use tvrage[1][24]['name] interface
|
|
||||||
makes it impossible to do tvrage[1][24]['name] = "name"
|
|
||||||
and still be capable of checking if an episode exists
|
|
||||||
so we can raise tvrage_shownotfound, we have a slightly
|
|
||||||
less pretty method of setting items.. but since the API
|
|
||||||
is supposed to be read-only, this is the best way to
|
|
||||||
do it!
|
|
||||||
The problem is that calling tvrage[1][24]['episodename'] = "name"
|
|
||||||
calls __getitem__ on tvrage[1], there is no way to check if
|
|
||||||
tvrage.__dict__ should have a key "1" before we auto-create it
|
|
||||||
"""
|
|
||||||
if sid not in self.shows:
|
|
||||||
self.shows[sid] = Show()
|
|
||||||
if seas not in self.shows[sid]:
|
|
||||||
self.shows[sid][seas] = Season(show=self.shows[sid])
|
|
||||||
if ep not in self.shows[sid][seas]:
|
|
||||||
self.shows[sid][seas][ep] = Episode(season=self.shows[sid][seas])
|
|
||||||
self.shows[sid][seas][ep][attrib] = value
|
|
||||||
|
|
||||||
def _setShowData(self, sid, key, value):
|
|
||||||
"""Sets self.shows[sid] to a new Show instance, or sets the data
|
|
||||||
"""
|
|
||||||
if sid not in self.shows:
|
|
||||||
self.shows[sid] = Show()
|
|
||||||
|
|
||||||
if not isinstance(key, dict or list) and not isinstance(value, dict or list):
|
|
||||||
self.shows[sid].data[key] = value
|
|
||||||
|
|
||||||
def _cleanData(self, data):
|
|
||||||
"""Cleans up strings returned by tvrage.com
|
|
||||||
|
|
||||||
Issues corrected:
|
|
||||||
- Replaces & with &
|
|
||||||
- Trailing whitespace
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not isinstance(data, dict or list):
|
|
||||||
data = data.replace(u"&", u"&")
|
|
||||||
data = data.strip()
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def search(self, series):
|
|
||||||
"""This searches tvrage.com for the series name
|
|
||||||
and returns the result list
|
|
||||||
"""
|
|
||||||
series = series.encode("utf-8")
|
|
||||||
log().debug("Searching for show %s" % series)
|
|
||||||
self.config['params_getSeries']['show'] = series
|
|
||||||
|
|
||||||
try:
|
|
||||||
seriesFound = self._getetsrc(self.config['url_getSeries'], self.config['params_getSeries'])
|
|
||||||
if seriesFound:
|
|
||||||
return seriesFound.values()[0]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _getSeries(self, series):
|
|
||||||
"""This searches tvrage.com for the series name,
|
|
||||||
If a custom_ui UI is configured, it uses this to select the correct
|
|
||||||
series. If not, and interactive == True, ConsoleUI is used, if not
|
|
||||||
BaseUI is used to select the first result.
|
|
||||||
"""
|
|
||||||
allSeries = self.search(series)
|
|
||||||
if not isinstance(allSeries, list):
|
|
||||||
allSeries = [allSeries]
|
|
||||||
|
|
||||||
if len(allSeries) == 0:
|
|
||||||
log().debug('Series result returned zero')
|
|
||||||
raise tvrage_shownotfound("Show-name search returned zero results (cannot find show on TVRAGE)")
|
|
||||||
|
|
||||||
if self.config['custom_ui'] is not None:
|
|
||||||
log().debug("Using custom UI %s" % (repr(self.config['custom_ui'])))
|
|
||||||
CustomUI = self.config['custom_ui']
|
|
||||||
ui = CustomUI(config=self.config)
|
|
||||||
else:
|
|
||||||
log().debug('Auto-selecting first search result using BaseUI')
|
|
||||||
ui = BaseUI(config=self.config)
|
|
||||||
|
|
||||||
return ui.selectSeries(allSeries)
|
|
||||||
|
|
||||||
def _getShowData(self, sid, getEpInfo=False):
|
|
||||||
"""Takes a series ID, gets the epInfo URL and parses the TVRAGE
|
|
||||||
XML file into the shows dict in layout:
|
|
||||||
shows[series_id][season_number][episode_number]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Parse show information
|
|
||||||
log().debug('Getting all series data for %s' % (sid))
|
|
||||||
self.config['params_seriesInfo']['sid'] = sid
|
|
||||||
seriesInfoEt = self._getetsrc(
|
|
||||||
self.config['url_seriesInfo'],
|
|
||||||
self.config['params_seriesInfo']
|
|
||||||
)
|
|
||||||
|
|
||||||
# check and make sure we have data to process and that it contains a series name
|
|
||||||
if not len(seriesInfoEt) or (isinstance(seriesInfoEt, dict) and 'seriesname' not in seriesInfoEt):
|
|
||||||
return False
|
|
||||||
|
|
||||||
for k, v in seriesInfoEt.items():
|
|
||||||
if v is not None:
|
|
||||||
v = self._cleanData(v)
|
|
||||||
|
|
||||||
self._setShowData(sid, k, v)
|
|
||||||
|
|
||||||
# series search ends here
|
|
||||||
if getEpInfo:
|
|
||||||
# Parse episode data
|
|
||||||
log().debug('Getting all episodes of %s' % (sid))
|
|
||||||
|
|
||||||
self.config['params_epInfo']['sid'] = sid
|
|
||||||
epsEt = self._getetsrc(self.config['url_epInfo'], self.config['params_epInfo'])
|
|
||||||
if 'episodelist' not in epsEt or 'season' not in epsEt['episodelist']:
|
|
||||||
return False
|
|
||||||
|
|
||||||
seasons = epsEt['episodelist']['season']
|
|
||||||
if not isinstance(seasons, list):
|
|
||||||
seasons = [seasons]
|
|
||||||
|
|
||||||
for season in seasons:
|
|
||||||
seas_no = int(season['@no'])
|
|
||||||
episodes = season['episode']
|
|
||||||
if not isinstance(episodes, list):
|
|
||||||
episodes = [episodes]
|
|
||||||
|
|
||||||
for episode in episodes:
|
|
||||||
ep_no = int(episode['episodenumber'])
|
|
||||||
self._setItem(sid, seas_no, ep_no, 'seasonnumber', seas_no)
|
|
||||||
|
|
||||||
for k, v in episode.items():
|
|
||||||
try:
|
|
||||||
k = k.lower()
|
|
||||||
if v is not None:
|
|
||||||
if k == 'link':
|
|
||||||
v = v.rsplit('/', 1)[1]
|
|
||||||
k = 'id'
|
|
||||||
v = self._cleanData(v)
|
|
||||||
|
|
||||||
self._setItem(sid, seas_no, ep_no, k, v)
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _nameToSid(self, name):
|
|
||||||
"""Takes show name, returns the correct series ID (if the show has
|
|
||||||
already been grabbed), or grabs all episodes and returns
|
|
||||||
the correct SID.
|
|
||||||
"""
|
|
||||||
if name in self.corrections:
|
|
||||||
log().debug('Correcting %s to %s' % (name, self.corrections[name]))
|
|
||||||
return self.corrections[name]
|
|
||||||
else:
|
|
||||||
log().debug('Getting show %s' % (name))
|
|
||||||
selected_series = self._getSeries(name)
|
|
||||||
if isinstance(selected_series, dict):
|
|
||||||
selected_series = [selected_series]
|
|
||||||
sids = list(int(x['id']) for x in selected_series if self._getShowData(int(x['id'])))
|
|
||||||
self.corrections.update(dict((x['seriesname'], int(x['id'])) for x in selected_series))
|
|
||||||
return sids
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
"""Handles tvrage_instance['seriesname'] calls.
|
|
||||||
The dict index should be the show id
|
|
||||||
"""
|
|
||||||
arg = None
|
|
||||||
if isinstance(key, tuple) and 2 == len(key):
|
|
||||||
key, arg = key
|
|
||||||
if not isinstance(arg, bool):
|
|
||||||
arg = None
|
|
||||||
|
|
||||||
if isinstance(key, (int, long)):
|
|
||||||
# Item is integer, treat as show id
|
|
||||||
if key not in self.shows:
|
|
||||||
self._getShowData(key, (True, arg)[arg is not None])
|
|
||||||
return None if key not in self.shows else self.shows[key]
|
|
||||||
|
|
||||||
key = key.lower()
|
|
||||||
self.config['searchterm'] = key
|
|
||||||
selected_series = self._getSeries(key)
|
|
||||||
if isinstance(selected_series, dict):
|
|
||||||
selected_series = [selected_series]
|
|
||||||
[[self._setShowData(show['id'], k, v) for k, v in show.items()] for show in selected_series]
|
|
||||||
return selected_series
|
|
||||||
#test = self._getSeries(key)
|
|
||||||
#sids = self._nameToSid(key)
|
|
||||||
#return list(self.shows[sid] for sid in sids)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.shows)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Simple example of using tvrage_api - it just
|
|
||||||
grabs an episode name interactively.
|
|
||||||
"""
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
|
|
||||||
tvrage_instance = TVRage(cache=False)
|
|
||||||
print tvrage_instance['Lost']['seriesname']
|
|
||||||
print tvrage_instance['Lost'][1][4]['episodename']
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,247 +0,0 @@
|
||||||
#!/usr/bin/env python2
|
|
||||||
#encoding:utf-8
|
|
||||||
#author:dbr/Ben (ripped from tvdb:echel0n)
|
|
||||||
#project:tvrage_api
|
|
||||||
#license:unlicense (http://unlicense.org/)
|
|
||||||
|
|
||||||
"""
|
|
||||||
urllib2 caching handler
|
|
||||||
Modified from http://code.activestate.com/recipes/491261/
|
|
||||||
"""
|
|
||||||
from __future__ import with_statement
|
|
||||||
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import errno
|
|
||||||
import httplib
|
|
||||||
import urllib2
|
|
||||||
import StringIO
|
|
||||||
from hashlib import md5
|
|
||||||
from threading import RLock
|
|
||||||
|
|
||||||
cache_lock = RLock()
|
|
||||||
|
|
||||||
def locked_function(origfunc):
|
|
||||||
"""Decorator to execute function under lock"""
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
cache_lock.acquire()
|
|
||||||
try:
|
|
||||||
return origfunc(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
cache_lock.release()
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
def calculate_cache_path(cache_location, url):
|
|
||||||
"""Checks if [cache_location]/[hash_of_url].headers and .body exist
|
|
||||||
"""
|
|
||||||
thumb = md5(url).hexdigest()
|
|
||||||
header = os.path.join(cache_location, thumb + ".headers")
|
|
||||||
body = os.path.join(cache_location, thumb + ".body")
|
|
||||||
return header, body
|
|
||||||
|
|
||||||
def check_cache_time(path, max_age):
|
|
||||||
"""Checks if a file has been created/modified in the [last max_age] seconds.
|
|
||||||
False means the file is too old (or doesn't exist), True means it is
|
|
||||||
up-to-date and valid"""
|
|
||||||
if not os.path.isfile(path):
|
|
||||||
return False
|
|
||||||
cache_modified_time = os.stat(path).st_mtime
|
|
||||||
time_now = time.time()
|
|
||||||
if cache_modified_time < time_now - max_age:
|
|
||||||
# Cache is old
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def exists_in_cache(cache_location, url, max_age):
|
|
||||||
"""Returns if header AND body cache file exist (and are up-to-date)"""
|
|
||||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
|
||||||
if os.path.exists(hpath) and os.path.exists(bpath):
|
|
||||||
return(
|
|
||||||
check_cache_time(hpath, max_age)
|
|
||||||
and check_cache_time(bpath, max_age)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# File does not exist
|
|
||||||
return False
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def store_in_cache(cache_location, url, response):
|
|
||||||
"""Tries to store response in cache."""
|
|
||||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
|
||||||
try:
|
|
||||||
outf = open(hpath, "wb")
|
|
||||||
headers = str(response.info())
|
|
||||||
outf.write(headers)
|
|
||||||
outf.close()
|
|
||||||
|
|
||||||
outf = open(bpath, "wb")
|
|
||||||
outf.write(response.read())
|
|
||||||
outf.close()
|
|
||||||
except IOError:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def delete_from_cache(cache_location, url):
|
|
||||||
"""Deletes a response in cache."""
|
|
||||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
|
||||||
try:
|
|
||||||
if os.path.exists(hpath):
|
|
||||||
os.remove(hpath)
|
|
||||||
if os.path.exists(bpath):
|
|
||||||
os.remove(bpath)
|
|
||||||
except IOError:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
class CacheHandler(urllib2.BaseHandler):
|
|
||||||
"""Stores responses in a persistant on-disk cache.
|
|
||||||
|
|
||||||
If a subsequent GET request is made for the same URL, the stored
|
|
||||||
response is returned, saving time, resources and bandwidth
|
|
||||||
"""
|
|
||||||
@locked_function
|
|
||||||
def __init__(self, cache_location, max_age = 21600):
|
|
||||||
"""The location of the cache directory"""
|
|
||||||
self.max_age = max_age
|
|
||||||
self.cache_location = cache_location
|
|
||||||
if not os.path.exists(self.cache_location):
|
|
||||||
try:
|
|
||||||
os.mkdir(self.cache_location)
|
|
||||||
except OSError, e:
|
|
||||||
if e.errno == errno.EEXIST and os.path.isdir(self.cache_location):
|
|
||||||
# File exists, and it's a directory,
|
|
||||||
# another process beat us to creating this dir, that's OK.
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Our target dir is already a file, or different error,
|
|
||||||
# relay the error!
|
|
||||||
raise
|
|
||||||
|
|
||||||
def default_open(self, request):
|
|
||||||
"""Handles GET requests, if the response is cached it returns it
|
|
||||||
"""
|
|
||||||
if request.get_method() != "GET":
|
|
||||||
return None # let the next handler try to handle the request
|
|
||||||
|
|
||||||
if exists_in_cache(
|
|
||||||
self.cache_location, request.get_full_url(), self.max_age
|
|
||||||
):
|
|
||||||
return CachedResponse(
|
|
||||||
self.cache_location,
|
|
||||||
request.get_full_url(),
|
|
||||||
set_cache_header = True
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def http_response(self, request, response):
|
|
||||||
"""Gets a HTTP response, if it was a GET request and the status code
|
|
||||||
starts with 2 (200 OK etc) it caches it and returns a CachedResponse
|
|
||||||
"""
|
|
||||||
if (request.get_method() == "GET"
|
|
||||||
and str(response.code).startswith("2")
|
|
||||||
):
|
|
||||||
if 'x-local-cache' not in response.info():
|
|
||||||
# Response is not cached
|
|
||||||
set_cache_header = store_in_cache(
|
|
||||||
self.cache_location,
|
|
||||||
request.get_full_url(),
|
|
||||||
response
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
set_cache_header = True
|
|
||||||
|
|
||||||
return CachedResponse(
|
|
||||||
self.cache_location,
|
|
||||||
request.get_full_url(),
|
|
||||||
set_cache_header = set_cache_header
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return response
|
|
||||||
|
|
||||||
class CachedResponse(StringIO.StringIO):
|
|
||||||
"""An urllib2.response-like object for cached responses.
|
|
||||||
|
|
||||||
To determine if a response is cached or coming directly from
|
|
||||||
the network, check the x-local-cache header rather than the object type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def __init__(self, cache_location, url, set_cache_header=True):
|
|
||||||
self.cache_location = cache_location
|
|
||||||
hpath, bpath = calculate_cache_path(cache_location, url)
|
|
||||||
|
|
||||||
StringIO.StringIO.__init__(self, file(bpath, "rb").read())
|
|
||||||
|
|
||||||
self.url = url
|
|
||||||
self.code = 200
|
|
||||||
self.msg = "OK"
|
|
||||||
headerbuf = file(hpath, "rb").read()
|
|
||||||
if set_cache_header:
|
|
||||||
headerbuf += "x-local-cache: %s\r\n" % (bpath)
|
|
||||||
self.headers = httplib.HTTPMessage(StringIO.StringIO(headerbuf))
|
|
||||||
|
|
||||||
def info(self):
|
|
||||||
"""Returns headers
|
|
||||||
"""
|
|
||||||
return self.headers
|
|
||||||
|
|
||||||
def geturl(self):
|
|
||||||
"""Returns original URL
|
|
||||||
"""
|
|
||||||
return self.url
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def recache(self):
|
|
||||||
new_request = urllib2.urlopen(self.url)
|
|
||||||
set_cache_header = store_in_cache(
|
|
||||||
self.cache_location,
|
|
||||||
new_request.url,
|
|
||||||
new_request
|
|
||||||
)
|
|
||||||
CachedResponse.__init__(self, self.cache_location, self.url, True)
|
|
||||||
|
|
||||||
@locked_function
|
|
||||||
def delete_cache(self):
|
|
||||||
delete_from_cache(
|
|
||||||
self.cache_location,
|
|
||||||
self.url
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
def main():
|
|
||||||
"""Quick test/example of CacheHandler"""
|
|
||||||
opener = urllib2.build_opener(CacheHandler("/tmp/"))
|
|
||||||
response = opener.open("http://google.com")
|
|
||||||
print response.headers
|
|
||||||
print "Response:", response.read()
|
|
||||||
|
|
||||||
response.recache()
|
|
||||||
print response.headers
|
|
||||||
print "After recache:", response.read()
|
|
||||||
|
|
||||||
# Test usage in threads
|
|
||||||
from threading import Thread
|
|
||||||
class CacheThreadTest(Thread):
|
|
||||||
lastdata = None
|
|
||||||
def run(self):
|
|
||||||
req = opener.open("http://google.com")
|
|
||||||
newdata = req.read()
|
|
||||||
if self.lastdata is None:
|
|
||||||
self.lastdata = newdata
|
|
||||||
assert self.lastdata == newdata, "Data was not consistent, uhoh"
|
|
||||||
req.recache()
|
|
||||||
threads = [CacheThreadTest() for x in range(50)]
|
|
||||||
print "Starting threads"
|
|
||||||
[t.start() for t in threads]
|
|
||||||
print "..done"
|
|
||||||
print "Joining threads"
|
|
||||||
[t.join() for t in threads]
|
|
||||||
print "..done"
|
|
||||||
main()
|
|
|
@ -1,48 +0,0 @@
|
||||||
#!/usr/bin/env python2
|
|
||||||
#encoding:utf-8
|
|
||||||
#author:dbr/Ben (ripped from tvdb:echel0n)
|
|
||||||
#project:tvrage_api
|
|
||||||
|
|
||||||
#license:unlicense (http://unlicense.org/)
|
|
||||||
|
|
||||||
"""Custom exceptions used or raised by tvrage_api"""
|
|
||||||
|
|
||||||
__all__ = ["tvrage_error", "tvrage_userabort", "tvrage_shownotfound",
|
|
||||||
"tvrage_seasonnotfound", "tvrage_episodenotfound", "tvrage_attributenotfound"]
|
|
||||||
|
|
||||||
class tvrage_exception(Exception):
|
|
||||||
"""Any exception generated by tvrage_api
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_error(tvrage_exception):
|
|
||||||
"""An error with tvrage.com (Cannot connect, for example)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_userabort(tvrage_exception):
|
|
||||||
"""User aborted the interactive selection (via
|
|
||||||
the q command, ^c etc)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_shownotfound(tvrage_exception):
|
|
||||||
"""Show cannot be found on tvrage.com (non-existant show)
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_seasonnotfound(tvrage_exception):
|
|
||||||
"""Season cannot be found on tvrage.com
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_episodenotfound(tvrage_exception):
|
|
||||||
"""Episode cannot be found on tvrage.com
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class tvrage_attributenotfound(tvrage_exception):
|
|
||||||
"""Raised if an episode does not have the requested
|
|
||||||
attribute (such as a episode name)
|
|
||||||
"""
|
|
||||||
pass
|
|
|
@ -1,28 +0,0 @@
|
||||||
#!/usr/bin/env python2
|
|
||||||
#encoding:utf-8
|
|
||||||
#author:dbr/Ben (ripped from tvdb:echel0n)
|
|
||||||
#project:tvrage_api
|
|
||||||
|
|
||||||
#license:unlicense (http://unlicense.org/)
|
|
||||||
|
|
||||||
"""Contains included user interface for TVRage show selection"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
def log():
|
|
||||||
return logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class BaseUI:
|
|
||||||
"""Default non-interactive UI, which auto-selects first results
|
|
||||||
"""
|
|
||||||
def __init__(self, config, log = None):
|
|
||||||
self.config = config
|
|
||||||
if log is not None:
|
|
||||||
warnings.warn("the UI's log parameter is deprecated, instead use\n"
|
|
||||||
"use import logging; logging.getLogger('ui').info('blah')\n"
|
|
||||||
"The self.log attribute will be removed in the next version")
|
|
||||||
self.log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def selectSeries(self, allSeries):
|
|
||||||
return allSeries[0]
|
|
|
@ -1,5 +1,4 @@
|
||||||
from lib.tvdb_api.tvdb_api import Tvdb
|
from lib.tvdb_api.tvdb_api import Tvdb
|
||||||
from lib.tvrage_api.tvrage_api import TVRage
|
|
||||||
|
|
||||||
INDEXER_TVDB = 1
|
INDEXER_TVDB = 1
|
||||||
INDEXER_TVRAGE = 2
|
INDEXER_TVRAGE = 2
|
||||||
|
@ -33,7 +32,7 @@ indexerConfig = {
|
||||||
main_url='http://tvrage.com/',
|
main_url='http://tvrage.com/',
|
||||||
id=INDEXER_TVRAGE,
|
id=INDEXER_TVRAGE,
|
||||||
name='TVRage',
|
name='TVRage',
|
||||||
module=TVRage,
|
module=None,
|
||||||
api_params=dict(apikey='Uhewg1Rr0o62fvZvUIZt', language='en'),
|
api_params=dict(apikey='Uhewg1Rr0o62fvZvUIZt', language='en'),
|
||||||
active=False,
|
active=False,
|
||||||
dupekey='tvr',
|
dupekey='tvr',
|
||||||
|
|
|
@ -5,10 +5,6 @@
|
||||||
|
|
||||||
"""Custom exceptions used or raised by indexer_api"""
|
"""Custom exceptions used or raised by indexer_api"""
|
||||||
|
|
||||||
from lib.tvrage_api.tvrage_exceptions import \
|
|
||||||
tvrage_exception, tvrage_attributenotfound, tvrage_episodenotfound, tvrage_error, \
|
|
||||||
tvrage_seasonnotfound, tvrage_shownotfound, tvrage_userabort
|
|
||||||
|
|
||||||
from lib.tvdb_api.tvdb_exceptions import \
|
from lib.tvdb_api.tvdb_exceptions import \
|
||||||
tvdb_exception, tvdb_attributenotfound, tvdb_episodenotfound, tvdb_error, \
|
tvdb_exception, tvdb_attributenotfound, tvdb_episodenotfound, tvdb_error, \
|
||||||
tvdb_seasonnotfound, tvdb_shownotfound, tvdb_userabort
|
tvdb_seasonnotfound, tvdb_shownotfound, tvdb_userabort
|
||||||
|
@ -19,14 +15,11 @@ indexerExcepts = ["indexer_exception", "indexer_error", "indexer_userabort", "in
|
||||||
tvdbExcepts = ["tvdb_exception", "tvdb_error", "tvdb_userabort", "tvdb_shownotfound",
|
tvdbExcepts = ["tvdb_exception", "tvdb_error", "tvdb_userabort", "tvdb_shownotfound",
|
||||||
"tvdb_seasonnotfound", "tvdb_episodenotfound", "tvdb_attributenotfound"]
|
"tvdb_seasonnotfound", "tvdb_episodenotfound", "tvdb_attributenotfound"]
|
||||||
|
|
||||||
tvrageExcepts = ["tvdb_exception", "tvrage_error", "tvrage_userabort", "tvrage_shownotfound",
|
|
||||||
"tvrage_seasonnotfound", "tvrage_episodenotfound", "tvrage_attributenotfound"]
|
|
||||||
|
|
||||||
# link API exceptions to our exception handler
|
# link API exceptions to our exception handler
|
||||||
indexer_exception = tvdb_exception, tvrage_exception
|
indexer_exception = tvdb_exception
|
||||||
indexer_error = tvdb_error, tvrage_error
|
indexer_error = tvdb_error
|
||||||
indexer_userabort = tvdb_userabort, tvrage_userabort
|
indexer_userabort = tvdb_userabort
|
||||||
indexer_attributenotfound = tvdb_attributenotfound, tvrage_attributenotfound
|
indexer_attributenotfound = tvdb_attributenotfound
|
||||||
indexer_episodenotfound = tvdb_episodenotfound, tvrage_episodenotfound
|
indexer_episodenotfound = tvdb_episodenotfound
|
||||||
indexer_seasonnotfound = tvdb_seasonnotfound, tvrage_seasonnotfound
|
indexer_seasonnotfound = tvdb_seasonnotfound
|
||||||
indexer_shownotfound = tvdb_shownotfound, tvrage_shownotfound
|
indexer_shownotfound = tvdb_shownotfound
|
Loading…
Reference in a new issue