Commit 14196682 authored by echel0n's avatar echel0n
Browse files

Fixed #SICKRAGE-APP-5SW - StatementError sqlalchemy.dialects.sqlite.base in process

Fixed #SICKRAGE-APP-5SY - IntegrityError sqlalchemy.engine.default in do_execute
Fixed #SICKRAGE-APP-5SH - AttributeError sickrage.notifiers.plex in update_library
Fixed #SICKRAGE-APP-5T6 - KeyError sickrage.core.tv.show in delete_show
Misc fixes for app API
Refactored Plex notifier client
Added default web session timeout of 15s
Lowered core external API timeout to 15s
Wording correcting in notifications.mako
parent 28e7ba82
......@@ -170,7 +170,7 @@ class API(object):
def upload_privatekey(self, privatekey):
return self.request('POST', 'account/private-key', data=dict({'privatekey': privatekey}))
def request(self, method, url, timeout=30, **kwargs):
def request(self, method, url, timeout=15, **kwargs):
if not self.is_enabled or not self.session:
return
......
......@@ -21,7 +21,7 @@
import datetime
import functools
import threading
import time
import feedparser
......@@ -40,6 +40,7 @@ from sickrage.core.websession import WebSession
class TVCache(object):
def __init__(self, provider, **kwargs):
self.lock = threading.Lock()
self.provider = provider
self.providerID = self.provider.id
self.min_time = kwargs.pop('min_time', 10)
......@@ -144,16 +145,17 @@ class TVCache(object):
def last_update(self, toDate):
session = sickrage.app.cache_db.session()
try:
dbData = session.query(CacheDB.LastUpdate).filter_by(provider=self.providerID).one()
dbData.time = int(time.mktime(toDate.timetuple()))
except orm.exc.NoResultFound:
session.add(CacheDB.LastUpdate(**{
'provider': self.providerID,
'time': int(time.mktime(toDate.timetuple()))
}))
finally:
session.commit()
with self.lock:
try:
dbData = session.query(CacheDB.LastUpdate).filter_by(provider=self.providerID).one()
dbData.time = int(time.mktime(toDate.timetuple()))
except orm.exc.NoResultFound:
session.add(CacheDB.LastUpdate(**{
'provider': self.providerID,
'time': int(time.mktime(toDate.timetuple()))
}))
finally:
session.commit()
@property
def last_search(self):
......@@ -173,16 +175,17 @@ class TVCache(object):
def last_search(self, toDate):
session = sickrage.app.cache_db.session()
try:
dbData = session.query(CacheDB.LastSearch).filter_by(provider=self.providerID).one()
dbData.time = int(time.mktime(toDate.timetuple()))
except orm.exc.NoResultFound:
session.add(CacheDB.LastSearch(**{
'provider': self.providerID,
'time': int(time.mktime(toDate.timetuple()))
}))
finally:
session.commit()
with self.lock:
try:
dbData = session.query(CacheDB.LastSearch).filter_by(provider=self.providerID).one()
dbData.time = int(time.mktime(toDate.timetuple()))
except orm.exc.NoResultFound:
session.add(CacheDB.LastSearch(**{
'provider': self.providerID,
'time': int(time.mktime(toDate.timetuple()))
}))
finally:
session.commit()
def should_update(self):
# if we've updated recently then skip the update
......
......@@ -1049,8 +1049,11 @@ class TVShow(object):
# remove episodes from show episode cache
self.flush_episodes()
# remove from show cache
del sickrage.app.shows[(self.indexer_id, self.indexer)]
# remove from show cache if found
try:
del sickrage.app.shows[(self.indexer_id, self.indexer)]
except KeyError:
pass
# clear the cache
image_cache_dir = os.path.join(sickrage.app.cache_dir, 'images')
......
......@@ -735,7 +735,8 @@ class CMD_Episode(ApiCall):
return _responds(RESULT_FAILURE, msg="Show not found")
try:
episode = session.query(MainDB.TVEpisode).filter_by(showid=self.indexerid, season=self.s, episode=self.e).one()
db_data = session.query(MainDB.TVEpisode).filter_by(showid=self.indexerid, season=self.s, episode=self.e).one()
episode_result = db_data.as_dict()
show_path = show_obj.location
......@@ -746,24 +747,24 @@ class CMD_Episode(ApiCall):
elif bool(self.fullPath) is False and os.path.isdir(show_path):
# using the length because lstrip removes to much
show_path_length = len(show_path) + 1 # the / or \ yeah not that nice i know
episode.location = episode.location[show_path_length:]
episode_result['location'] = episode_result['location'][show_path_length:]
elif not os.path.isdir(show_path): # show dir is broken ... episode path will be empty
episode.location = ""
episode_result['location'] = ""
# convert stuff to human form
if episode.airdate > datetime.date.min: # 1900
episode.airdate = srdatetime.SRDateTime(srdatetime.SRDateTime(
sickrage.app.tz_updater.parse_date_time(episode.airdate, show_obj.airs, show_obj.network),
if episode_result['airdate'] > datetime.date.min: # 1900
episode_result['airdate'] = srdatetime.SRDateTime(srdatetime.SRDateTime(
sickrage.app.tz_updater.parse_date_time(episode_result['airdate'], show_obj.airs, show_obj.network),
convert=True).dt).srfdate(d_preset=dateFormat)
else:
episode.airdate = 'Never'
episode_result['airdate'] = 'Never'
status, quality = Quality.split_composite_status(int(episode.status))
episode.status = _get_status_strings(status)
episode.quality = get_quality_string(quality)
episode.file_size_human = pretty_file_size(episode.file_size)
status, quality = Quality.split_composite_status(int(episode_result['status']))
episode_result['status'] = _get_status_strings(status)
episode_result['quality'] = get_quality_string(quality)
episode_result['file_size_human'] = pretty_file_size(episode_result['file_size'])
return _responds(RESULT_SUCCESS, episode.as_dict())
return _responds(RESULT_SUCCESS, episode_result)
except orm.exc.NoResultFound:
raise InternalApiError("Episode not found")
......@@ -812,8 +813,7 @@ class CMD_EpisodeSearch(ApiCall):
if ep_queue_item.success:
status, quality = Quality.split_composite_status(epObj.status)
# TODO: split quality and status?
return _responds(RESULT_SUCCESS, {"quality": get_quality_string(quality)},
"Snatched (" + get_quality_string(quality) + ")")
return _responds(RESULT_SUCCESS, {"quality": get_quality_string(quality)}, "Snatched (" + get_quality_string(quality) + ")")
return _responds(RESULT_FAILURE, msg='Unable to find episode')
......@@ -895,6 +895,7 @@ class CMD_EpisodeSetStatus(ApiCall):
continue
epObj.status = self.status
epObj.save()
if self.status == WANTED:
start_backlog = True
......@@ -1308,6 +1309,9 @@ class CMD_SiCKRAGEAddRootDir(ApiCall):
root_dirs_new = '|'.join(x for x in root_dirs_new)
sickrage.app.config.root_dirs = root_dirs_new
sickrage.app.config.save()
return _responds(RESULT_SUCCESS, _get_root_dirs(), msg="Root directories updated")
......@@ -1686,6 +1690,8 @@ class CMD_SiCKRAGESetDefaults(ApiCall):
if self.future_show_paused is not None:
sickrage.app.config.coming_eps_display_paused = int(self.future_show_paused)
sickrage.app.config.save()
return _responds(RESULT_SUCCESS, msg="Saved defaults")
......@@ -2034,10 +2040,8 @@ class CMD_ShowAddNew(ApiCall):
else:
dir_exists = make_dir(show_path)
if not dir_exists:
sickrage.app.log.warning(
"Unable to create the folder " + show_path + ", can't add the show")
return _responds(RESULT_FAILURE, {"path": show_path},
"Unable to create the folder " + show_path + ", can't add the show")
sickrage.app.log.warning("Unable to create the folder " + show_path + ", can't add the show")
return _responds(RESULT_FAILURE, {"path": show_path}, "Unable to create the folder " + show_path + ", can't add the show")
else:
chmod_as_parent(show_path)
......@@ -2276,6 +2280,8 @@ class CMD_ShowPause(ApiCall):
else:
show_object.paused = self.pause
show_object.save()
return _responds(RESULT_SUCCESS, msg='%s has been %s' % (show_object.name, ('resumed', 'paused')[show_object.paused]))
......@@ -2442,9 +2448,9 @@ class CMD_ShowSetQuality(ApiCall):
newQuality = Quality.combine_qualities(iqualityID, aqualityID)
show_object.quality = newQuality
show_object.save()
return _responds(RESULT_SUCCESS,
msg=show_object.name + " quality has been changed to " + get_quality_string(show_object.quality))
return _responds(RESULT_SUCCESS, msg=show_object.name + " quality has been changed to " + get_quality_string(show_object.quality))
class CMD_ShowStats(ApiCall):
......
......@@ -246,6 +246,25 @@
</div>
<div id="content_use_plex">
<div class="form-row form-group">
<div class="col-lg-3 col-md-4 col-sm-5">
<label class="component-title">${_('Plex Media Server IP:Port')}</label>
</div>
<div class="col-lg-9 col-md-8 col-sm-7 component-desc">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><span class="fas fa-globe"></span></span>
</div>
<input name="plex_server_host"
id="plex_server_host"
placeholder="${_('ex. 192.168.1.1:32400, 192.168.1.2:32400')}"
value="${re.sub(r'\b,\b', ', ', sickrage.app.config.plex_server_host)}"
class="form-control"
autocapitalize="off"/>
</div>
</div>
</div>
<div class="form-row form-group">
<div class="col-lg-3 col-md-4 col-sm-5">
<label class="component-title">${_('Plex Media Server Auth Token')}</label>
......@@ -325,42 +344,22 @@
</label>
</div>
</div>
<div id="content_plex_update_library">
<div class="form-row form-group">
<div class="col-lg-3 col-md-4 col-sm-5">
<label class="component-title">${_('Plex Media Server IP:Port')}</label>
</div>
<div class="col-lg-9 col-md-8 col-sm-7 component-desc">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><span class="fas fa-globe"></span></span>
</div>
<input name="plex_server_host"
id="plex_server_host"
placeholder="${_('ex. 192.168.1.1:32400, 192.168.1.2:32400')}"
value="${re.sub(r'\b,\b', ', ', sickrage.app.config.plex_server_host)}"
class="form-control"
autocapitalize="off"/>
</div>
</div>
</div>
<div class="form-row">
<div class="col-md-12">
<div class="card mb-3">
<div class="card-text m-1">
<div id="testPMS-result">${_('Click below to test')}</div>
</div>
<div class="form-row">
<div class="col-md-12">
<div class="card mb-3">
<div class="card-text m-1">
<div id="testPMS-result">${_('Click below to test')}</div>
</div>
</div>
</div>
</div>
<div class="form-row">
<div class="col-md-12">
<input class="btn" type="button" value="${_('Test Plex Server')}"
id="testPMS"/>
<input type="submit" class="config_submitter btn" value="${_('Save Changes')}"/>
</div>
<div class="form-row">
<div class="col-md-12">
<input class="btn" type="button" value="${_('Test Plex Server')}"
id="testPMS"/>
<input type="submit" class="config_submitter btn" value="${_('Save Changes')}"/>
</div>
</div>
</div><!-- /content_use_plex -->
......@@ -452,7 +451,7 @@
<div class="form-row form-group">
<div class="col-lg-3 col-md-4 col-sm-5">
<label class="component-title">${_('Server Username')}</label>
<label class="component-title">${_('Client Username')}</label>
</div>
<div class="col-lg-9 col-md-8 col-sm-7 component-desc">
<div class="input-group">
......
......@@ -75,23 +75,18 @@ class WebSession(Session):
"""
return certifi.where() if all([sickrage.app.config.ssl_verify, verify]) else False
def request(self, method, url, verify=False, random_ua=False, allow_post_redirects=False, *args, **kwargs):
def request(self, method, url, verify=False, random_ua=False, timeout=15, *args, **kwargs):
self.headers.update({'Accept-Encoding': 'gzip, deflate',
'User-Agent': (sickrage.app.user_agent, UserAgent().random)[random_ua]})
if not verify:
disable_warnings()
if allow_post_redirects and method == 'POST':
sickrage.app.log.debug('Retrieving redirect URL for {url}'.format(**{'url': url}))
response = super(WebSession, self).request(method, url, allow_redirects=False)
url = self.get_redirect_target(response) or url
for i in range(5):
resp = None
try:
resp = super(WebSession, self).request(method, url, verify=self._get_ssl_cert(verify), *args, **kwargs)
resp = super(WebSession, self).request(method, url, verify=self._get_ssl_cert(verify), timeout=timeout, *args, **kwargs)
# check of cloudflare handling is required
if self.cloudflare:
......
......@@ -35,6 +35,13 @@ class PLEXNotifier(Notifiers):
super(PLEXNotifier, self).__init__()
self.name = 'plex'
self.headers = {
'X-Plex-Device-Name': 'SiCKRAGE',
'X-Plex-Product': 'SiCKRAGE Notifier',
'X-Plex-Client-Identifier': sickrage.app.user_agent,
'X-Plex-Version': sickrage.version()
}
def _send_to_plex(self, command, host, username=None, password=None):
"""Handles communication to Plex hosts via HTTP API
......@@ -149,8 +156,7 @@ class PLEXNotifier(Notifiers):
self._notify_pmc(update_text + new_version, title)
def test_notify_pmc(self, host, username, password):
return self._notify_pmc('This is a test notification from SiCKRAGE', 'Test Notification', host, username,
password, force=True)
return self._notify_pmc('This is a test notification from SiCKRAGE', 'Test Notification', host, username, password, force=True)
def test_notify_pms(self, host, username, password, plex_server_token):
return self.update_library(host=host, username=username, password=password, plex_server_token=plex_server_token, force=False)
......@@ -166,64 +172,38 @@ class PLEXNotifier(Notifiers):
"""
if sickrage.app.config.use_plex and sickrage.app.config.plex_update_library:
if not sickrage.app.config.plex_server_host:
sickrage.app.log.debug('PLEX: No Plex Media Server host specified, check your settings')
return False
if not host:
host = sickrage.app.config.plex_server_host
if not username:
username = sickrage.app.config.plex_username
if not password:
password = sickrage.app.config.plex_password
if not plex_server_token:
plex_server_token = sickrage.app.config.plex_server_token
# if username and password were provided, fetch the auth token from plex.tv
token_arg = ''
if plex_server_token:
token_arg = '?X-Plex-Token=' + plex_server_token
elif username and password:
sickrage.app.log.debug('PLEX: fetching plex.tv credentials for user: ' + username)
headers = {
'Authorization': 'Basic %s' % base64.b64encode(bytes('{}:{}'.format(username, password).replace('\n', ''), 'utf-8')).decode('ascii'),
'X-Plex-Device-Name': 'SiCKRAGE',
'X-Plex-Product': 'SiCKRAGE Notifier',
'X-Plex-Client-Identifier': sickrage.app.user_agent,
'X-Plex-Version': '1.0'
}
try:
resp = WebSession().get('https://plex.tv/users/sign_in.xml', headers=headers)
auth_tree = ElementTree.fromstring(resp.text)
token = auth_tree.findall('.//authentication-token')[0].text
token_arg = '?X-Plex-Token=' + token
except Exception as e:
sickrage.app.log.debug('PLEX: Error fetching credentials from from plex.tv for user %s: %s' % (username, e))
except (ValueError, IndexError) as e:
sickrage.app.log.debug('PLEX: Error parsing plex.tv response: ' + e)
if not self.get_token(username, password, plex_server_token):
sickrage.app.log.warning('PLEX: Error getting auth token for Plex Media Server, check your settings')
return 'Error getting auth token for Plex Media Server, check your settings'
file_location = '' if None is ep_obj else ep_obj.location
host_list = [x.strip() for x in host.split(',')]
hosts_all = {}
hosts_match = {}
hosts_failed = []
hosts_failed = set()
for cur_host in host_list:
try:
url = 'http://%s/library/sections%s' % (cur_host, token_arg)
resp = WebSession().get(url)
url = 'http://%s/library/sections' % cur_host
resp = WebSession().get(url, headers=self.headers)
if not resp or not resp.text:
sickrage.app.log.warning('PLEX: Unable to get library data from Plex Media Server')
continue
media_container = ElementTree.fromstring(resp.text)
except IOError as e:
sickrage.app.log.warning('PLEX: Error while trying to contact Plex Media Server: {}'.format(e))
hosts_failed.append(cur_host)
hosts_failed.add(cur_host)
continue
except Exception as e:
if 'invalid token' in str(e):
sickrage.app.log.error('PLEX: Please set TOKEN in Plex settings: ')
sickrage.app.log.error('PLEX: Please set TOKEN in Plex settings')
else:
sickrage.app.log.error('PLEX: Error while trying to contact Plex Media Server: {}'.format(e))
continue
......@@ -231,7 +211,7 @@ class PLEXNotifier(Notifiers):
sections = media_container.findall('.//Directory')
if not sections:
sickrage.app.log.debug('PLEX: Plex Media Server not running on: ' + cur_host)
hosts_failed.append(cur_host)
hosts_failed.add(cur_host)
continue
for section in sections:
......@@ -255,12 +235,12 @@ class PLEXNotifier(Notifiers):
host_list = []
for section_key, cur_host in hosts_try.items():
try:
url = 'http://%s/library/sections/%s/refresh%s' % (cur_host, section_key, token_arg)
force and WebSession().get(url)
url = 'http://%s/library/sections/%s/refresh' % (cur_host, section_key)
WebSession().get(url, headers=self.headers)
host_list.append(cur_host)
except Exception as e:
sickrage.app.log.warning('PLEX: Error updating library section for Plex Media Server: {}'.format(e))
hosts_failed.append(cur_host)
hosts_failed.add(cur_host)
if hosts_match:
sickrage.app.log.debug('PLEX: Updating hosts where TV section paths match the downloaded show: ' + ', '.join(set(host_list)))
......@@ -268,3 +248,38 @@ class PLEXNotifier(Notifiers):
sickrage.app.log.debug('PLEX: Updating TV sections on these hosts: {}'.format(', '.join(set(host_list))))
return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)]
def get_token(self, username=None, password=None, plex_server_token=None):
if plex_server_token:
self.headers['X-Plex-Token'] = plex_server_token
if 'X-Plex-Token' in self.headers:
return True
if not (username and password):
return True
sickrage.app.log.debug('PLEX: fetching plex.tv credentials for user: ' + username)
params = {
'user[login]': username,
'user[password]': password
}
resp = WebSession().post('https://plex.tv/users/sign_in.json', data=params, headers=self.headers)
try:
data = resp.json()
except ValueError:
sickrage.app.log.debug("PLEX: No data returned from plex.tv when attempting to fetch credentials")
self.headers.pop('X-Plex-Token', '')
return False
if data and 'error' in data:
sickrage.app.log.debug('PLEX: Error fetching credentials from from plex.tv for user %s: %s' % (username, data['error']))
self.headers.pop('X-Plex-Token', '')
return False
elif data and 'user' in data:
self.headers['X-Plex-Token'] = data['user']['authentication_token']
return 'X-Plex-Token' in self.headers
......@@ -156,7 +156,7 @@ class YggtorrentProvider(TorrentProvider):
if not self._is_authenticated():
try:
self.session.post(self.urls['login'], data=login_params, allow_post_redirects=True)
self.session.post(self.urls['login'], data=login_params)
except Exception:
sickrage.app.log.warning('Unable to connect or login to provider')
return False
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment