Merge pull request #349 from ressu/feature/CleanDownloaders

Change request handling in torrent clients
This commit is contained in:
JackDandy 2015-05-06 19:46:30 +01:00
commit ac07dfc1c6
6 changed files with 203 additions and 191 deletions

View file

@ -31,6 +31,7 @@
* Add General Config/Interface/"Group show list shows into:"... to divide shows into groups on the Show List page * Add General Config/Interface/"Group show list shows into:"... to divide shows into groups on the Show List page
* Change Show List progress bar code, smaller page load, efficient use of js render engine * Change Show List progress bar code, smaller page load, efficient use of js render engine
* Change values used for date sorting on home page and episode view for improved compatibility with posix systems * Change values used for date sorting on home page and episode view for improved compatibility with posix systems
* Change response handling in downloaders to simplify logic.
[develop changelog] [develop changelog]
Fix issue changing a custom show list group name that is in use. The bug resulted in the db containing stale group names Fix issue changing a custom show list group name that is in use. The bug resulted in the db containing stale group names

View file

@ -22,6 +22,7 @@ from base64 import b64encode
import sickbeard import sickbeard
from sickbeard import logger from sickbeard import logger
from sickbeard.clients.generic import GenericClient from sickbeard.clients.generic import GenericClient
from lib.requests.exceptions import RequestException
class DelugeAPI(GenericClient): class DelugeAPI(GenericClient):
@ -33,134 +34,148 @@ class DelugeAPI(GenericClient):
def _get_auth(self): def _get_auth(self):
post_data = json.dumps({"method": "auth.login", post_data = json.dumps({'method': 'auth.login',
"params": [self.password], 'params': [self.password],
"id": 1 'id': 1})
})
try: try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT) self.auth = self.session.post(
except: self.url,
return None data=post_data.encode('utf-8'),
verify=sickbeard.TORRENT_VERIFY_CERT
).json()['result']
self.auth = self.response.json()["result"] post_data = json.dumps({'method': 'web.connected',
'params': [],
'id': 10})
post_data = json.dumps({"method": "web.connected", connected = self.session.post(
"params": [], self.url,
"id": 10 data=post_data.encode('utf-8'),
}) verify=sickbeard.TORRENT_VERIFY_CERT
try: ).json()['result']
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT)
except:
return None
connected = self.response.json()['result']
if not connected:
post_data = json.dumps({"method": "web.get_hosts",
"params": [],
"id": 11
})
try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT)
except:
return None
hosts = self.response.json()['result']
if len(hosts) == 0:
logger.log(self.name + u': WebUI does not contain daemons', logger.ERROR)
return None
post_data = json.dumps({"method": "web.connect",
"params": [hosts[0][0]],
"id": 11
})
try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT)
except:
return None
post_data = json.dumps({"method": "web.connected",
"params": [],
"id": 10
})
try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT)
except:
return None
connected = self.response.json()['result']
if not connected: if not connected:
logger.log(self.name + u': WebUI could not connect to daemon', logger.ERROR) post_data = json.dumps({'method': 'web.get_hosts',
'params': [],
'id': 11})
hosts = self.session.post(
self.url,
data=post_data.encode('utf-8'),
verify=sickbeard.TORRENT_VERIFY_CERT
).json()['result']
if len(hosts) == 0:
logger.log(self.name + u': WebUI does not contain daemons',
logger.ERROR)
return None
post_data = json.dumps({'method': 'web.connect',
'params': [hosts[0][0]],
'id': 11})
self.session.post(self.url, data=post_data.encode('utf-8'),
verify=sickbeard.TORRENT_VERIFY_CERT)
post_data = json.dumps({'method': 'web.connected',
'params': [],
'id': 10})
connected = self.session.post(
self.url,
data=post_data.encode('utf-8'),
verify=sickbeard.TORRENT_VERIFY_CERT
).json()['result']
if not connected:
logger.log(self.name + u': WebUI could not connect to daemon',
logger.ERROR)
return None return None
except RequestException:
return None
return self.auth return self.auth
def _add_torrent_uri(self, result): def _add_torrent_uri(self, result):
post_data = json.dumps({"method": "core.add_torrent_magnet", post_data = json.dumps({
"params": [result.url, {"move_completed": "true", 'method': 'core.add_torrent_magnet',
"move_completed_path": sickbeard.TV_DOWNLOAD_DIR}], 'params': [result.url, {
"id": 2 'move_completed': 'true',
'move_completed_path': sickbeard.TV_DOWNLOAD_DIR
}],
'id': 2
}) })
self._request(method='post', data=post_data) result.hash = self._request(method='post',
data=post_data).json()['result']
result.hash = self.response.json()['result'] return result.hash
return self.response.json()['result']
def _add_torrent_file(self, result): def _add_torrent_file(self, result):
post_data = json.dumps({"method": "core.add_torrent_file", post_data = json.dumps({'method':
"params": [result.name + '.torrent', b64encode(result.content), 'core.add_torrent_file',
{"move_completed": "true", 'params': [result.name + '.torrent',
"move_completed_path": sickbeard.TV_DOWNLOAD_DIR}], b64encode(result.content),
"id": 2 {'move_completed': 'true',
}) 'move_completed_path':
self._request(method='post', data=post_data) sickbeard.TV_DOWNLOAD_DIR}],
'id': 2})
result.hash = self._request(method='post',
data=post_data).json()['result']
result.hash = self.response.json()['result'] return result.hash
return self.response.json()['result']
def _set_torrent_label(self, result): def _set_torrent_label(self, result):
label = sickbeard.TORRENT_LABEL label = sickbeard.TORRENT_LABEL
if ' ' in label: if ' ' in label:
logger.log(self.name + u': Invalid label. Label must not contain a space', logger.ERROR) logger.log(self.name +
u': Invalid label. Label must not contain a space',
logger.ERROR)
return False return False
if label: if label:
# check if label already exists and create it if not # check if label already exists and create it if not
post_data = json.dumps({"method": 'label.get_labels', post_data = json.dumps({
"params": [], 'method': 'label.get_labels',
"id": 3 'params': [],
'id': 3
}) })
self._request(method='post', data=post_data) labels = self._request(method='post',
labels = self.response.json()['result'] data=post_data).json()['result']
if labels != None: if labels is not None:
if label not in labels: if label not in labels:
logger.log(self.name + ': ' + label + u" label does not exist in Deluge we must add it", logger.log(self.name + ': ' + label +
u' label does not exist in ' +
u'Deluge we must add it',
logger.DEBUG) logger.DEBUG)
post_data = json.dumps({"method": 'label.add', post_data = json.dumps({
"params": [label], 'method': 'label.add',
"id": 4 'params': [label],
'id': 4
}) })
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
logger.log(self.name + ': ' + label + u" label added to Deluge", logger.DEBUG) logger.log(self.name + ': ' + label +
u' label added to Deluge', logger.DEBUG)
# add label to torrent # add label to torrent
post_data = json.dumps({"method": 'label.set_torrent', post_data = json.dumps({
"params": [result.hash, label], 'method': 'label.set_torrent',
"id": 5 'params': [result.hash, label],
'id': 5
}) })
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
logger.log(self.name + ': ' + label + u" label added to torrent", logger.DEBUG) logger.log(self.name + ': ' + label +
u' label added to torrent',
logger.DEBUG)
else: else:
logger.log(self.name + ': ' + u"label plugin not detected", logger.DEBUG) logger.log(self.name + ': ' +
u'label plugin not detected',
logger.DEBUG)
return False return False
return not self.response.json()['error'] return True
def _set_torrent_ratio(self, result): def _set_torrent_ratio(self, result):
@ -169,53 +184,43 @@ class DelugeAPI(GenericClient):
ratio = result.ratio ratio = result.ratio
if ratio: if ratio:
post_data = json.dumps({"method": "core.set_torrent_stop_at_ratio", post_data = json.dumps({'method': 'core.set_torrent_stop_at_ratio',
"params": [result.hash, True], 'params': [result.hash, True],
"id": 5 'id': 5})
})
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
post_data = json.dumps({"method": "core.set_torrent_stop_ratio", post_data = json.dumps({'method': 'core.set_torrent_stop_ratio',
"params": [result.hash, float(ratio)], 'params': [result.hash, float(ratio)],
"id": 6 'id': 6})
})
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
return not self.response.json()['error']
return True return True
def _set_torrent_path(self, result): def _set_torrent_path(self, result):
if sickbeard.TORRENT_PATH: if sickbeard.TORRENT_PATH:
post_data = json.dumps({"method": "core.set_torrent_move_completed", post_data = json.dumps({
"params": [result.hash, True], 'method': 'core.set_torrent_move_completed',
"id": 7 'params': [result.hash, True],
'id': 7
}) })
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
post_data = json.dumps({"method": "core.set_torrent_move_completed_path", post_data = json.dumps({
"params": [result.hash, sickbeard.TORRENT_PATH], 'method': 'core.set_torrent_move_completed_path',
"id": 8 'params': [result.hash, sickbeard.TORRENT_PATH],
'id': 8
}) })
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
return not self.response.json()['error']
return True return True
def _set_torrent_pause(self, result): def _set_torrent_pause(self, result):
if sickbeard.TORRENT_PAUSED: if sickbeard.TORRENT_PAUSED:
post_data = json.dumps({"method": "core.pause_torrent", post_data = json.dumps({'method': 'core.pause_torrent',
"params": [[result.hash]], 'params': [[result.hash]],
"id": 9 'id': 9})
})
self._request(method='post', data=post_data) self._request(method='post', data=post_data)
return not self.response.json()['error']
return True return True
api = DelugeAPI() api = DelugeAPI()

View file

@ -36,8 +36,8 @@ class DownloadStationAPI(GenericClient):
auth_url = self.host + 'webapi/auth.cgi?api=SYNO.API.Auth&version=2&method=login&account=' + self.username + '&passwd=' + self.password + '&session=DownloadStation&format=sid' auth_url = self.host + 'webapi/auth.cgi?api=SYNO.API.Auth&version=2&method=login&account=' + self.username + '&passwd=' + self.password + '&session=DownloadStation&format=sid'
try: try:
self.response = self.session.get(auth_url, verify=False) response = self.session.get(auth_url, verify=False)
self.auth = self.response.json()['data']['sid'] self.auth = response.json()['data']['sid']
except: except:
return None return None
@ -53,9 +53,9 @@ class DownloadStationAPI(GenericClient):
} }
if sickbeard.TORRENT_PATH: if sickbeard.TORRENT_PATH:
data['destination'] = sickbeard.TORRENT_PATH data['destination'] = sickbeard.TORRENT_PATH
self._request(method='post', data=data) response = self._request(method='post', data=data)
return self.response.json()['success'] return response.json()['success']
def _add_torrent_file(self, result): def _add_torrent_file(self, result):
@ -68,8 +68,8 @@ class DownloadStationAPI(GenericClient):
if sickbeard.TORRENT_PATH: if sickbeard.TORRENT_PATH:
data['destination'] = sickbeard.TORRENT_PATH data['destination'] = sickbeard.TORRENT_PATH
files = {'file':(result.name + '.torrent', result.content)} files = {'file':(result.name + '.torrent', result.content)}
self._request(method='post', data=data, files=files) response = self._request(method='post', data=data, files=files)
return self.response.json()['success'] return response.json()['success']
api = DownloadStationAPI() api = DownloadStationAPI()

View file

@ -9,7 +9,7 @@ from sickbeard.exceptions import ex
from sickbeard.clients import http_error_code from sickbeard.clients import http_error_code
from lib.bencode import bencode, bdecode from lib.bencode import bencode, bdecode
from lib import requests from lib import requests
from lib.requests import exceptions
class GenericClient(object): class GenericClient(object):
def __init__(self, name, host=None, username=None, password=None): def __init__(self, name, host=None, username=None, password=None):
@ -20,34 +20,38 @@ class GenericClient(object):
self.host = sickbeard.TORRENT_HOST if host is None else host self.host = sickbeard.TORRENT_HOST if host is None else host
self.url = None self.url = None
self.response = None
self.auth = None self.auth = None
self.last_time = time.time() self.last_time = time.time()
self.session = requests.session() self.session = requests.session()
self.session.auth = (self.username, self.password) self.session.auth = (self.username, self.password)
def _request(self, method='get', params={}, data=None, files=None): def _request(self, method='get', params={}, data=None, files=None):
response = None
if time.time() > self.last_time + 1800 or not self.auth: if time.time() > self.last_time + 1800 or not self.auth:
self.last_time = time.time() self.last_time = time.time()
self._get_auth() self._get_auth()
logger.log( logger.log(
self.name + u': Requested a ' + method.upper() + ' connection to url ' + self.url + ' with Params= ' + str( self.name + u': Requested a ' + method.upper() +
params) + ' Data=' + str(data if data else 'None')[0:99] + ( ' connection to url ' + self.url + ' with Params= ' + str(params) +
'...' if len(data if data else 'None') > 200 else ''), logger.DEBUG) ' Data=' + str(data if data else 'None')[0:99] +
('...' if len(data if data else 'None') > 200 else ''),
logger.DEBUG
)
logger.log( logger.log(
self.name + u': Requested a ' + method.upper() + ' connection to url ' + self.url + ' with Params= ' + str( self.name + u': Requested a ' + method.upper() +
params) + ( ' connection to url ' + self.url + ' with Params= ' + str(params) +
(' Data=' + str(data)[0:100] + ('...' if len(data) > 100 else '')) if data is not None else ""), ((' Data=' + str(data)[0:100] + ('...' if len(data) > 100 else ''))
logger.DEBUG) if data is not None else ''),
logger.DEBUG
)
if not self.auth: if not self.auth:
logger.log(self.name + u': Authentication Failed', logger.ERROR) logger.log(self.name + u': Authentication Failed', logger.ERROR)
return False return False
try: try:
self.response = self.session.__getattribute__(method)(self.url, params=params, data=data, files=files, response = self.session.__getattribute__(method)(self.url, params=params, data=data, files=files,
timeout=120, verify=False) timeout=120, verify=False)
except requests.exceptions.ConnectionError, e: except requests.exceptions.ConnectionError, e:
logger.log(self.name + u': Unable to connect ' + ex(e), logger.ERROR) logger.log(self.name + u': Unable to connect ' + ex(e), logger.ERROR)
@ -66,78 +70,78 @@ class GenericClient(object):
logger.ERROR) logger.ERROR)
return False return False
if self.response.status_code == 401: if response.status_code == 401:
logger.log(self.name + u': Invalid Username or Password, check your config', logger.ERROR) logger.log(self.name + u': Invalid Username or Password, check your config', logger.ERROR)
return False return False
if self.response.status_code in http_error_code.keys(): if response.status_code in http_error_code.keys():
logger.log(self.name + u': ' + http_error_code[self.response.status_code], logger.DEBUG) logger.log(self.name + u': ' + http_error_code[response.status_code], logger.DEBUG)
return False return False
logger.log(self.name + u': Response to ' + method.upper() + ' request is ' + self.response.text, logger.DEBUG) logger.log(self.name + u': Response to ' + method.upper() + ' request is ' + response.text, logger.DEBUG)
return True return response
def _get_auth(self): def _get_auth(self):
""" '''
This should be overridden and should return the auth_id needed for the client This should be overridden and should return the auth_id needed for the client
""" '''
return None return None
def _add_torrent_uri(self, result): def _add_torrent_uri(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is added via url (magnet or .torrent link) when a torrent is added via url (magnet or .torrent link)
""" '''
return False return False
def _add_torrent_file(self, result): def _add_torrent_file(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is added via result.content (only .torrent file) when a torrent is added via result.content (only .torrent file)
""" '''
return False return False
def _set_torrent_label(self, result): def _set_torrent_label(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is set with label when a torrent is set with label
""" '''
return True return True
def _set_torrent_ratio(self, result): def _set_torrent_ratio(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is set with ratio when a torrent is set with ratio
""" '''
return True return True
def _set_torrent_seed_time(self, result): def _set_torrent_seed_time(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is set with a seed time when a torrent is set with a seed time
""" '''
return True return True
def _set_torrent_priority(self, result): def _set_torrent_priority(self, result):
""" '''
This should be overriden should return the True/False from the client This should be overriden should return the True/False from the client
when a torrent is set with result.priority (-1 = low, 0 = normal, 1 = high) when a torrent is set with result.priority (-1 = low, 0 = normal, 1 = high)
""" '''
return True return True
def _set_torrent_path(self, torrent_path): def _set_torrent_path(self, torrent_path):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is set with path when a torrent is set with path
""" '''
return True return True
def _set_torrent_pause(self, result): def _set_torrent_pause(self, result):
""" '''
This should be overridden should return the True/False from the client This should be overridden should return the True/False from the client
when a torrent is set with pause when a torrent is set with pause
""" '''
return True return True
def _get_torrent_hash(self, result): def _get_torrent_hash(self, result):
@ -148,7 +152,7 @@ class GenericClient(object):
result.hash = b16encode(b32decode(result.hash)).lower() result.hash = b16encode(b32decode(result.hash)).lower()
else: else:
result.content = result.provider.getURL(result.url) result.content = result.provider.getURL(result.url)
info = bdecode(result.content)["info"] info = bdecode(result.content)['info']
result.hash = sha1(bencode(info)).hexdigest() result.hash = sha1(bencode(info)).hexdigest()
return result return result
@ -205,20 +209,21 @@ class GenericClient(object):
return r_code return r_code
def testAuthentication(self): def testAuthentication(self):
response = None
try: try:
self.response = self.session.get(self.url, timeout=120, verify=False) response = self.session.get(self.url, timeout=120, verify=False)
except requests.exceptions.ConnectionError, e:
if response.status_code == 401:
return False, 'Error: Invalid ' + self.name + ' Username or Password, check your config!'
except requests.exceptions.ConnectionError:
return False, 'Error: ' + self.name + ' Connection Error' return False, 'Error: ' + self.name + ' Connection Error'
except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL): except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL):
return False, 'Error: Invalid ' + self.name + ' host' return False, 'Error: Invalid ' + self.name + ' host'
if self.response.status_code == 401:
return False, 'Error: Invalid ' + self.name + ' Username or Password, check your config!'
try: try:
self._get_auth() authenticated = self._get_auth()
if self.response.status_code == 200 and self.auth: # FIXME: This test is redundant
if authenticated and self.auth:
return True, 'Success: Connected and Authenticated' return True, 'Success: Connected and Authenticated'
else: else:
return False, 'Error: Unable to get ' + self.name + ' Authentication, check your config!' return False, 'Error: Unable to get ' + self.name + ' Authentication, check your config!'

View file

@ -37,9 +37,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'method': 'session-get', }) post_data = json.dumps({'method': 'session-get', })
try: try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), timeout=120, response = self.session.post(self.url, data=post_data.encode('utf-8'), timeout=120,
verify=sickbeard.TORRENT_VERIFY_CERT) verify=sickbeard.TORRENT_VERIFY_CERT)
self.auth = re.search('X-Transmission-Session-Id:\s*(\w+)', self.response.text).group(1) self.auth = re.search('X-Transmission-Session-Id:\s*(\w+)', response.text).group(1)
except: except:
return None return None
@ -62,9 +62,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments, post_data = json.dumps({'arguments': arguments,
'method': 'torrent-add', 'method': 'torrent-add',
}) })
self._request(method='post', data=post_data) response = self._request(method='post', data=post_data)
return self.response.json()['result'] == "success" return response.json()['result'] == 'success'
def _add_torrent_file(self, result): def _add_torrent_file(self, result):
@ -75,9 +75,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments, post_data = json.dumps({'arguments': arguments,
'method': 'torrent-add', 'method': 'torrent-add',
}) })
self._request(method='post', data=post_data) response = self._request(method='post', data=post_data)
return self.response.json()['result'] == "success" return response.json()['result'] == 'success'
def _set_torrent_ratio(self, result): def _set_torrent_ratio(self, result):
@ -101,9 +101,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments, post_data = json.dumps({'arguments': arguments,
'method': 'torrent-set', 'method': 'torrent-set',
}) })
self._request(method='post', data=post_data) response = self._request(method='post', data=post_data)
return self.response.json()['result'] == "success" return response.json()['result'] == 'success'
def _set_torrent_seed_time(self, result): def _set_torrent_seed_time(self, result):
@ -117,9 +117,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments, post_data = json.dumps({'arguments': arguments,
'method': 'torrent-set', 'method': 'torrent-set',
}) })
self._request(method='post', data=post_data) response = self._request(method='post', data=post_data)
return self.response.json()['result'] == "success" return response.json()['result'] == 'success'
else: else:
return True return True
@ -139,12 +139,14 @@ class TransmissionAPI(GenericClient):
else: else:
arguments['priority-normal'] = [] arguments['priority-normal'] = []
post_data = json.dumps({'arguments': arguments, post_data = json.dumps({
'method': 'torrent-set', 'arguments': arguments,
}) 'method': 'torrent-set',
self._request(method='post', data=post_data)
return self.response.json()['result'] == "success" })
response = self._request(method='post', data=post_data)
return response.json()['result'] == 'success'
api = TransmissionAPI() api = TransmissionAPI()

View file

@ -42,13 +42,12 @@ class uTorrentAPI(GenericClient):
def _get_auth(self): def _get_auth(self):
try: try:
self.response = self.session.get(self.url + 'token.html', verify=False) response = self.session.get(self.url + 'token.html', verify=False)
self.auth = re.findall("<div.*?>(.*?)</", self.response.text)[0] self.auth = re.findall('<div.*?>(.*?)</', response.text)[0]
return self.auth if not response.status_code == 404 else None
except: except:
return None return None
return self.auth if not self.response.status_code == 404 else None
def _add_torrent_uri(self, result): def _add_torrent_uri(self, result):
params = {'action': 'add-url', 's': result.url} params = {'action': 'add-url', 's': result.url}
@ -112,15 +111,15 @@ class uTorrentAPI(GenericClient):
else: else:
return False return False
else: else:
return True return True
def _set_torrent_priority(self, result): def _set_torrent_priority(self, result):
if result.priority == 1: if result.priority == 1:
params = {'action': 'queuetop', 'hash': result.hash} params = {'action': 'queuetop', 'hash': result.hash}
return self._request(params=params) return self._request(params=params)
else: else:
return True return True
def _set_torrent_pause(self, result): def _set_torrent_pause(self, result):
@ -132,4 +131,4 @@ class uTorrentAPI(GenericClient):
return self._request(params=params) return self._request(params=params)
api = uTorrentAPI() api = uTorrentAPI()