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
* 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 response handling in downloaders to simplify logic.
[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

View file

@ -22,6 +22,7 @@ from base64 import b64encode
import sickbeard
from sickbeard import logger
from sickbeard.clients.generic import GenericClient
from lib.requests.exceptions import RequestException
class DelugeAPI(GenericClient):
@ -33,134 +34,148 @@ class DelugeAPI(GenericClient):
def _get_auth(self):
post_data = json.dumps({"method": "auth.login",
"params": [self.password],
"id": 1
})
post_data = json.dumps({'method': 'auth.login',
'params': [self.password],
'id': 1})
try:
self.response = self.session.post(self.url, data=post_data.encode('utf-8'), verify=sickbeard.TORRENT_VERIFY_CERT)
except:
return None
self.auth = self.session.post(
self.url,
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",
"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.session.post(
self.url,
data=post_data.encode('utf-8'),
verify=sickbeard.TORRENT_VERIFY_CERT
).json()['result']
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:
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
except RequestException:
return None
return self.auth
def _add_torrent_uri(self, result):
post_data = json.dumps({"method": "core.add_torrent_magnet",
"params": [result.url, {"move_completed": "true",
"move_completed_path": sickbeard.TV_DOWNLOAD_DIR}],
"id": 2
post_data = json.dumps({
'method': 'core.add_torrent_magnet',
'params': [result.url, {
'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 self.response.json()['result']
return result.hash
def _add_torrent_file(self, result):
post_data = json.dumps({"method": "core.add_torrent_file",
"params": [result.name + '.torrent', b64encode(result.content),
{"move_completed": "true",
"move_completed_path": sickbeard.TV_DOWNLOAD_DIR}],
"id": 2
})
self._request(method='post', data=post_data)
post_data = json.dumps({'method':
'core.add_torrent_file',
'params': [result.name + '.torrent',
b64encode(result.content),
{'move_completed': 'true',
'move_completed_path':
sickbeard.TV_DOWNLOAD_DIR}],
'id': 2})
result.hash = self._request(method='post',
data=post_data).json()['result']
result.hash = self.response.json()['result']
return self.response.json()['result']
return result.hash
def _set_torrent_label(self, result):
label = sickbeard.TORRENT_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
if label:
# check if label already exists and create it if not
post_data = json.dumps({"method": 'label.get_labels',
"params": [],
"id": 3
post_data = json.dumps({
'method': 'label.get_labels',
'params': [],
'id': 3
})
self._request(method='post', data=post_data)
labels = self.response.json()['result']
labels = self._request(method='post',
data=post_data).json()['result']
if labels != None:
if labels is not None:
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)
post_data = json.dumps({"method": 'label.add',
"params": [label],
"id": 4
post_data = json.dumps({
'method': 'label.add',
'params': [label],
'id': 4
})
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
post_data = json.dumps({"method": 'label.set_torrent',
"params": [result.hash, label],
"id": 5
# add label to torrent
post_data = json.dumps({
'method': 'label.set_torrent',
'params': [result.hash, label],
'id': 5
})
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:
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 not self.response.json()['error']
return True
def _set_torrent_ratio(self, result):
@ -169,53 +184,43 @@ class DelugeAPI(GenericClient):
ratio = result.ratio
if ratio:
post_data = json.dumps({"method": "core.set_torrent_stop_at_ratio",
"params": [result.hash, True],
"id": 5
})
post_data = json.dumps({'method': 'core.set_torrent_stop_at_ratio',
'params': [result.hash, True],
'id': 5})
self._request(method='post', data=post_data)
post_data = json.dumps({"method": "core.set_torrent_stop_ratio",
"params": [result.hash, float(ratio)],
"id": 6
})
post_data = json.dumps({'method': 'core.set_torrent_stop_ratio',
'params': [result.hash, float(ratio)],
'id': 6})
self._request(method='post', data=post_data)
return not self.response.json()['error']
return True
def _set_torrent_path(self, result):
if sickbeard.TORRENT_PATH:
post_data = json.dumps({"method": "core.set_torrent_move_completed",
"params": [result.hash, True],
"id": 7
post_data = json.dumps({
'method': 'core.set_torrent_move_completed',
'params': [result.hash, True],
'id': 7
})
self._request(method='post', data=post_data)
post_data = json.dumps({"method": "core.set_torrent_move_completed_path",
"params": [result.hash, sickbeard.TORRENT_PATH],
"id": 8
post_data = json.dumps({
'method': 'core.set_torrent_move_completed_path',
'params': [result.hash, sickbeard.TORRENT_PATH],
'id': 8
})
self._request(method='post', data=post_data)
return not self.response.json()['error']
return True
def _set_torrent_pause(self, result):
if sickbeard.TORRENT_PAUSED:
post_data = json.dumps({"method": "core.pause_torrent",
"params": [[result.hash]],
"id": 9
})
post_data = json.dumps({'method': 'core.pause_torrent',
'params': [[result.hash]],
'id': 9})
self._request(method='post', data=post_data)
return not self.response.json()['error']
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'
try:
self.response = self.session.get(auth_url, verify=False)
self.auth = self.response.json()['data']['sid']
response = self.session.get(auth_url, verify=False)
self.auth = response.json()['data']['sid']
except:
return None
@ -53,9 +53,9 @@ class DownloadStationAPI(GenericClient):
}
if 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):
@ -68,8 +68,8 @@ class DownloadStationAPI(GenericClient):
if sickbeard.TORRENT_PATH:
data['destination'] = sickbeard.TORRENT_PATH
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 lib.bencode import bencode, bdecode
from lib import requests
from lib.requests import exceptions
class GenericClient(object):
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.url = None
self.response = None
self.auth = None
self.last_time = time.time()
self.session = requests.session()
self.session.auth = (self.username, self.password)
def _request(self, method='get', params={}, data=None, files=None):
response = None
if time.time() > self.last_time + 1800 or not self.auth:
self.last_time = time.time()
self._get_auth()
logger.log(
self.name + u': Requested a ' + method.upper() + ' connection to url ' + self.url + ' with Params= ' + str(
params) + ' Data=' + str(data if data else 'None')[0:99] + (
'...' if len(data if data else 'None') > 200 else ''), logger.DEBUG)
self.name + u': Requested a ' + method.upper() +
' connection to url ' + self.url + ' with Params= ' + str(params) +
' Data=' + str(data if data else 'None')[0:99] +
('...' if len(data if data else 'None') > 200 else ''),
logger.DEBUG
)
logger.log(
self.name + u': Requested a ' + method.upper() + ' 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 ""),
logger.DEBUG)
self.name + u': Requested a ' + method.upper() +
' 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 ''),
logger.DEBUG
)
if not self.auth:
logger.log(self.name + u': Authentication Failed', logger.ERROR)
return False
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)
except requests.exceptions.ConnectionError, e:
logger.log(self.name + u': Unable to connect ' + ex(e), logger.ERROR)
@ -66,78 +70,78 @@ class GenericClient(object):
logger.ERROR)
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)
return False
if self.response.status_code in http_error_code.keys():
logger.log(self.name + u': ' + http_error_code[self.response.status_code], logger.DEBUG)
if response.status_code in http_error_code.keys():
logger.log(self.name + u': ' + http_error_code[response.status_code], logger.DEBUG)
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):
"""
'''
This should be overridden and should return the auth_id needed for the client
"""
'''
return None
def _add_torrent_uri(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is added via url (magnet or .torrent link)
"""
'''
return False
def _add_torrent_file(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is added via result.content (only .torrent file)
"""
'''
return False
def _set_torrent_label(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is set with label
"""
'''
return True
def _set_torrent_ratio(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is set with ratio
"""
'''
return True
def _set_torrent_seed_time(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is set with a seed time
"""
'''
return True
def _set_torrent_priority(self, result):
"""
'''
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)
"""
'''
return True
def _set_torrent_path(self, torrent_path):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is set with path
"""
'''
return True
def _set_torrent_pause(self, result):
"""
'''
This should be overridden should return the True/False from the client
when a torrent is set with pause
"""
'''
return True
def _get_torrent_hash(self, result):
@ -148,7 +152,7 @@ class GenericClient(object):
result.hash = b16encode(b32decode(result.hash)).lower()
else:
result.content = result.provider.getURL(result.url)
info = bdecode(result.content)["info"]
info = bdecode(result.content)['info']
result.hash = sha1(bencode(info)).hexdigest()
return result
@ -205,20 +209,21 @@ class GenericClient(object):
return r_code
def testAuthentication(self):
response = None
try:
self.response = self.session.get(self.url, timeout=120, verify=False)
except requests.exceptions.ConnectionError, e:
response = self.session.get(self.url, timeout=120, verify=False)
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'
except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL):
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:
self._get_auth()
if self.response.status_code == 200 and self.auth:
authenticated = self._get_auth()
# FIXME: This test is redundant
if authenticated and self.auth:
return True, 'Success: Connected and Authenticated'
else:
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', })
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)
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:
return None
@ -62,9 +62,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments,
'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):
@ -75,9 +75,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments,
'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):
@ -101,9 +101,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments,
'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):
@ -117,9 +117,9 @@ class TransmissionAPI(GenericClient):
post_data = json.dumps({'arguments': arguments,
'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:
return True
@ -139,12 +139,14 @@ class TransmissionAPI(GenericClient):
else:
arguments['priority-normal'] = []
post_data = json.dumps({'arguments': arguments,
'method': 'torrent-set',
})
self._request(method='post', data=post_data)
post_data = json.dumps({
'arguments': arguments,
'method': 'torrent-set',
return self.response.json()['result'] == "success"
})
response = self._request(method='post', data=post_data)
return response.json()['result'] == 'success'
api = TransmissionAPI()

View file

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