@@ -1350,7 +1368,7 @@
method in which to download episodes for new show's.
-
+
Remove episode:
@@ -1359,8 +1377,8 @@
remove an episode from your watchlist after it is downloaded.
-
-
+
+
Remove series:
diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js
index 1e851c4d..63830ac9 100644
--- a/gui/slick/js/configNotifications.js
+++ b/gui/slick/js/configNotifications.js
@@ -56,22 +56,41 @@ $(document).ready(function(){
});
});
- $('#testPLEX').click(function () {
+ $('#testPMC').click(function () {
var plex_host = $.trim($('#plex_host').val());
var plex_username = $.trim($('#plex_username').val());
var plex_password = $.trim($('#plex_password').val());
if (!plex_host) {
- $('#testPLEX-result').html('Please fill out the necessary fields above.');
+ $('#testPMC-result').html('Please fill out the necessary fields above.');
$('#plex_host').addClass('warning');
return;
}
$('#plex_host').removeClass('warning');
$(this).prop('disabled', true);
- $('#testPLEX-result').html(loading);
- $.get(sbRoot + '/home/testPLEX', {'host': plex_host, 'username': plex_username, 'password': plex_password})
+ $('#testPMC-result').html(loading);
+ $.get(sbRoot + '/home/testPMC', {'host': plex_host, 'username': plex_username, 'password': plex_password})
.done(function (data) {
- $('#testPLEX-result').html(data);
- $('#testPLEX').prop('disabled', false);
+ $('#testPMC-result').html(data);
+ $('#testPMC').prop('disabled', false);
+ });
+ });
+
+ $('#testPMS').click(function () {
+ var plex_server_host = $.trim($('#plex_server_host').val());
+ var plex_username = $.trim($('#plex_username').val());
+ var plex_password = $.trim($('#plex_password').val());
+ if (!plex_server_host) {
+ $('#testPMS-result').html('Please fill out the necessary fields above.');
+ $('#plex_server_host').addClass('warning');
+ return;
+ }
+ $('#plex_server_host').removeClass('warning');
+ $(this).prop('disabled', true);
+ $('#testPMS-result').html(loading);
+ $.get(sbRoot + '/home/testPMS', {'host': plex_server_host, 'username': plex_username, 'password': plex_password})
+ .done(function (data) {
+ $('#testPMS-result').html(data);
+ $('#testPMS').prop('disabled', false);
});
});
@@ -158,7 +177,7 @@ $(document).ready(function(){
}
$('#testNMJ-result').html(loading);
var nmj_host = $('#nmj_host').val();
-
+
$.get(sbRoot + '/home/settingsNMJ', {'host': nmj_host},
function (data) {
if (data === null) {
@@ -220,7 +239,7 @@ $(document).ready(function(){
}
var nmjv2_dbinstance=$('#NMJv2db_instance').val();
- $.get(sbRoot + '/home/settingsNMJv2', {'host': nmjv2_host,'dbloc': nmjv2_dbloc,'instance': nmjv2_dbinstance},
+ $.get(sbRoot + '/home/settingsNMJv2', {'host': nmjv2_host,'dbloc': nmjv2_dbloc,'instance': nmjv2_dbinstance},
function (data){
if (data == null) {
$('#nmjv2_database').removeAttr('readonly');
@@ -228,7 +247,7 @@ $(document).ready(function(){
var JSONData = $.parseJSON(data);
$('#testNMJv2-result').html(JSONData.message);
$('#nmjv2_database').val(JSONData.database);
-
+
if (JSONData.database)
$('#nmjv2_database').attr('readonly', true);
else
@@ -379,7 +398,7 @@ $(document).ready(function(){
if(msg){
$('#testPushbullet-result').html(loading);
}
-
+
var pushbullet_api = $("#pushbullet_api").val();
if(!pushbullet_api) {
@@ -417,7 +436,7 @@ $(document).ready(function(){
$('#getPushbulletDevices').click(function(){
get_pushbullet_devices("Device list updated. Please choose a device to push to.");
});
-
+
// we have to call this function on dom ready to create the devices select
get_pushbullet_devices();
@@ -458,5 +477,5 @@ $(document).ready(function(){
$('.plexinfo').addClass('hide');
}
});
-
+ if ($('input[id="use_plex"]').is(':checked')) {$('.plexinfo').removeClass('hide')}
});
diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py
index 4eeafe41..1262f798 100644
--- a/sickbeard/notifiers/plex.py
+++ b/sickbeard/notifiers/plex.py
@@ -19,6 +19,7 @@
import urllib
import urllib2
import base64
+import re
import sickbeard
@@ -56,7 +57,7 @@ class PLEXNotifier:
password = sickbeard.PLEX_PASSWORD
if not host:
- logger.log(u"PLEX: No host specified, check your settings", logger.ERROR)
+ logger.log(u'PLEX: No host specified, check your settings', logger.ERROR)
return False
for key in command:
@@ -64,7 +65,7 @@ class PLEXNotifier:
command[key] = command[key].encode('utf-8')
enc_command = urllib.urlencode(command)
- logger.log(u"PLEX: Encoded API command: " + enc_command, logger.DEBUG)
+ logger.log(u'PLEX: Encoded API command: ' + enc_command, logger.DEBUG)
url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)
try:
@@ -72,26 +73,26 @@ class PLEXNotifier:
# if we have a password, use authentication
if password:
base64string = base64.encodestring('%s:%s' % (username, password))[:-1]
- authheader = "Basic %s" % base64string
- req.add_header("Authorization", authheader)
- logger.log(u"PLEX: Contacting (with auth header) via url: " + url, logger.DEBUG)
+ authheader = 'Basic %s' % base64string
+ req.add_header('Authorization', authheader)
+ logger.log(u'PLEX: Contacting (with auth header) via url: ' + url, logger.DEBUG)
else:
- logger.log(u"PLEX: Contacting via url: " + url, logger.DEBUG)
+ logger.log(u'PLEX: Contacting via url: ' + url, logger.DEBUG)
response = urllib2.urlopen(req)
result = response.read().decode(sickbeard.SYS_ENCODING)
response.close()
- logger.log(u"PLEX: HTTP response: " + result.replace('\n', ''), logger.DEBUG)
+ logger.log(u'PLEX: HTTP response: ' + result.replace('\n', ''), logger.DEBUG)
# could return result response = re.compile('(.+\w)').findall(result)
return 'OK'
except (urllib2.URLError, IOError), e:
- logger.log(u"PLEX: Warning: Couldn't contact Plex at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING)
+ logger.log(u'PLEX: Warning: Couldn\'t contact Plex at ' + fixStupidEncodings(url) + ' ' + ex(e), logger.WARNING)
return False
- def _notify_pmc(self, message, title="SickGear", host=None, username=None, password=None, force=False):
+ def _notify_pmc(self, message, title='SickGear', host=None, username=None, password=None, force=False):
"""Internal wrapper for the notify_snatch and notify_download functions
Args:
@@ -121,13 +122,13 @@ class PLEXNotifier:
password = sickbeard.PLEX_PASSWORD
result = ''
- for curHost in [x.strip() for x in host.split(",")]:
- logger.log(u"PLEX: Sending notification to '" + curHost + "' - " + message, logger.MESSAGE)
+ for curHost in [x.strip() for x in host.split(',')]:
+ logger.log(u'PLEX: Sending notification to \'%s\' - %s' % (curHost, message), logger.MESSAGE)
- command = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + title.encode("utf-8") + ',' + message.encode("utf-8") + ')'}
- notifyResult = self._send_to_plex(command, curHost, username, password)
- if notifyResult:
- result += curHost + ':' + str(notifyResult)
+ command = {'command': 'ExecBuiltIn', 'parameter': 'Notification(%s,%s)' % (title.encode('utf-8'), message.encode('utf-8'))}
+ notify_result = self._send_to_plex(command, curHost, username, password)
+ if notify_result:
+ result += '%s:%s' % (curHost, str(notify_result))
return result
@@ -145,89 +146,124 @@ class PLEXNotifier:
def notify_subtitle_download(self, ep_name, lang):
if sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD:
- self._notify_pmc(ep_name + ": " + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
+ self._notify_pmc(ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
- def notify_git_update(self, new_version="??"):
+ def notify_git_update(self, new_version='??'):
if sickbeard.USE_PLEX:
update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title = common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_pmc(update_text + new_version, title)
- def test_notify(self, host, username, password):
- return self._notify_pmc("This is a test notification from SickGear", "Test", host, username, password, force=True)
+ def test_notify_pmc(self, host, username, password):
+ return self._notify_pmc('This is a test notification from SickGear', 'Test', host, username, password, force=True)
- def update_library(self, ep_obj=None, host=None, username=None, password=None):
+ def test_notify_pms(self, host, username, password):
+ return self.update_library(host=host, username=username, password=password, force=False)
+
+ def update_library(self, ep_obj=None, host=None, username=None, password=None, force=True):
"""Handles updating the Plex Media Server host via HTTP API
Plex Media Server currently only supports updating the whole video library and not a specific path.
Returns:
- Returns True or False
+ Returns None for no issue, else a string of host with connection issues
"""
- # fill in omitted parameters
- if not host:
- host = sickbeard.PLEX_SERVER_HOST
- if not username:
- username = sickbeard.PLEX_USERNAME
- if not password:
- password = sickbeard.PLEX_PASSWORD
-
if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY:
+
if not sickbeard.PLEX_SERVER_HOST:
- logger.log(u"PLEX: No Plex Media Server host specified, check your settings", logger.DEBUG)
+ logger.log(u'PLEX: No Plex Media Server host specified, check your settings', logger.DEBUG)
return False
- logger.log(u"PLEX: Updating library for the Plex Media Server host: " + host, logger.MESSAGE)
+ if not host:
+ host = sickbeard.PLEX_SERVER_HOST
+ if not username:
+ username = sickbeard.PLEX_USERNAME
+ if not password:
+ password = sickbeard.PLEX_PASSWORD
# if username and password were provided, fetch the auth token from plex.tv
- token_arg = ""
+ token_arg = ''
if username and password:
- logger.log(u"PLEX: fetching credentials for Plex user: " + username, logger.DEBUG)
- req = urllib2.Request("https://plex.tv/users/sign_in.xml", data="")
- authheader = "Basic %s" % base64.encodestring('%s:%s' % (username, password))[:-1]
- req.add_header("Authorization", authheader)
- req.add_header("X-Plex-Device-Name", "SickGear")
- req.add_header("X-Plex-Product", "SickGear Notifier")
- req.add_header("X-Plex-Client-Identifier", "5f48c063eaf379a565ff56c9bb2b401e")
- req.add_header("X-Plex-Version", "1.0")
-
+ logger.log(u'PLEX: fetching plex.tv credentials for user: ' + username, logger.DEBUG)
+ req = urllib2.Request('https://plex.tv/users/sign_in.xml', data='')
+ authheader = 'Basic %s' % base64.encodestring('%s:%s' % (username, password))[:-1]
+ req.add_header('Authorization', authheader)
+ req.add_header('X-Plex-Device-Name', 'SickGear')
+ req.add_header('X-Plex-Product', 'SickGear Notifier')
+ req.add_header('X-Plex-Client-Identifier', '5f48c063eaf379a565ff56c9bb2b401e')
+ req.add_header('X-Plex-Version', '1.0')
+
try:
response = urllib2.urlopen(req)
auth_tree = etree.parse(response)
- token = auth_tree.findall(".//authentication-token")[0].text
- token_arg = "?X-Plex-Token=" + token
-
+ token = auth_tree.findall('.//authentication-token')[0].text
+ token_arg = '?X-Plex-Token=' + token
+
except urllib2.URLError as e:
- logger.log(u"PLEX: Error fetching credentials from from plex.tv for user %s: %s" % (username, ex(e)), logger.MESSAGE)
-
+ logger.log(u'PLEX: Error fetching credentials from from plex.tv for user %s: %s' % (username, ex(e)), logger.MESSAGE)
+
except (ValueError, IndexError) as e:
- logger.log(u"PLEX: Error parsing plex.tv response: " + ex(e), logger.MESSAGE)
+ logger.log(u'PLEX: Error parsing plex.tv response: ' + ex(e), logger.MESSAGE)
- url = "http://%s/library/sections%s" % (sickbeard.PLEX_SERVER_HOST, token_arg)
- try:
- xml_tree = etree.parse(urllib.urlopen(url))
- media_container = xml_tree.getroot()
- except IOError, e:
- logger.log(u"PLEX: Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR)
- return False
+ 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 = []
+ for cur_host in host_list:
- sections = media_container.findall('.//Directory')
- if not sections:
- logger.log(u"PLEX: Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE)
- return False
+ url = 'http://%s/library/sections%s' % (cur_host, token_arg)
+ try:
+ xml_tree = etree.parse(urllib.urlopen(url))
+ media_container = xml_tree.getroot()
+ except IOError, e:
+ logger.log(u'PLEX: Error while trying to contact Plex Media Server: ' + ex(e), logger.ERROR)
+ hosts_failed.append(cur_host)
+ continue
- for section in sections:
- if section.attrib['type'] == "show":
- url = "http://%s/library/sections/%s/refresh%s" % (sickbeard.PLEX_SERVER_HOST, section.attrib['key'], token_arg)
- try:
- urllib.urlopen(url)
- except Exception, e:
- logger.log(u"PLEX: Error updating library section for Plex Media Server: " + ex(e), logger.ERROR)
- return False
+ sections = media_container.findall('.//Directory')
+ if not sections:
+ logger.log(u'PLEX: Plex Media Server not running on: ' + cur_host, logger.MESSAGE)
+ hosts_failed.append(cur_host)
+ continue
- return True
+ for section in sections:
+ if 'show' == section.attrib['type']:
+
+ keyed_host = [(str(section.attrib['key']), cur_host)]
+ hosts_all.update(keyed_host)
+ if not file_location:
+ continue
+
+ for section_location in section.findall('.//Location'):
+ section_path = re.sub(r'[/\\]+', '/', section_location.attrib['path'].lower())
+ section_path = re.sub(r'^(.{,2})[/\\]', '', section_path)
+ location_path = re.sub(r'[/\\]+', '/', file_location.lower())
+ location_path = re.sub(r'^(.{,2})[/\\]', '', location_path)
+
+ if section_path in location_path:
+ hosts_match.update(keyed_host)
+
+ hosts_try = (hosts_all.copy(), hosts_match.copy())[len(hosts_match)]
+ host_list = []
+ for section_key, cur_host in hosts_try.items():
+
+ url = 'http://%s/library/sections/%s/refresh%s' % (cur_host, section_key, token_arg)
+ try:
+ force and urllib.urlopen(url)
+ host_list.append(cur_host)
+ except Exception, e:
+ logger.log(u'PLEX: Error updating library section for Plex Media Server: ' + ex(e), logger.ERROR)
+ hosts_failed.append(cur_host)
+
+ if len(hosts_match):
+ logger.log(u'PLEX: Updating hosts where TV section paths match the downloaded show: ' + ', '.join(set(host_list)), logger.MESSAGE)
+ else:
+ logger.log(u'PLEX: Updating all hosts with TV sections: ' + ', '.join(set(host_list)), logger.MESSAGE)
+
+ return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)]
notifier = PLEXNotifier
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index ab2d170d..731e5e2c 100644
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -1013,7 +1013,7 @@ class PostProcessor(object):
notifiers.xbmc_notifier.update_library(ep_obj.show.name)
# do the library update for Plex
- notifiers.plex_notifier.update_library()
+ notifiers.plex_notifier.update_library(ep_obj)
# do the library update for NMJ
# nmj_notifier kicks off its library update when the notify_download is issued (inside notifiers)
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index cd71fb03..ed6b8869 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -2393,7 +2393,7 @@ class ConfigNotifications(MainHandler):
sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(plex_notify_onsubtitledownload)
sickbeard.PLEX_UPDATE_LIBRARY = config.checkbox_to_value(plex_update_library)
sickbeard.PLEX_HOST = config.clean_hosts(plex_host)
- sickbeard.PLEX_SERVER_HOST = config.clean_host(plex_server_host)
+ sickbeard.PLEX_SERVER_HOST = config.clean_hosts(plex_server_host)
sickbeard.PLEX_USERNAME = plex_username
sickbeard.PLEX_PASSWORD = plex_password
@@ -3412,21 +3412,37 @@ class Home(MainHandler):
return finalResult
-
- def testPLEX(self, host=None, username=None, password=None):
+ def testPMC(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
finalResult = ''
- for curHost in [x.strip() for x in host.split(",")]:
- curResult = notifiers.plex_notifier.test_notify(urllib.unquote_plus(curHost), username, password)
- if len(curResult.split(":")) > 2 and 'OK' in curResult.split(":")[2]:
- finalResult += "Test Plex notice sent successfully to " + urllib.unquote_plus(curHost)
+ for curHost in [x.strip() for x in host.split(',')]:
+ curResult = notifiers.plex_notifier.test_notify_pmc(urllib.unquote_plus(curHost), username, password)
+ if len(curResult.split(':')) > 2 and 'OK' in curResult.split(':')[2]:
+ finalResult += 'Successful test notice sent to Plex client ... ' + urllib.unquote_plus(curHost)
else:
- finalResult += "Test Plex notice failed to " + urllib.unquote_plus(curHost)
- finalResult += " \n"
+ finalResult += 'Test failed for Plex client ... ' + urllib.unquote_plus(curHost)
+ finalResult += ' ' + "\n"
+
+ ui.notifications.message('Tested Plex client(s): ', urllib.unquote_plus(host.replace(',', ', ')))
return finalResult
+ def testPMS(self, host=None, username=None, password=None):
+ self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+ finalResult = ''
+
+ curResult = notifiers.plex_notifier.test_notify_pms(urllib.unquote_plus(host), username, password)
+ if None is curResult:
+ finalResult += 'Successful test of Plex server(s) ... ' + urllib.unquote_plus(host.replace(',', ', '))
+ else:
+ finalResult += 'Test failed for Plex server(s) ... ' + urllib.unquote_plus(curResult.replace(',', ', '))
+ finalResult += ' ' + "\n"
+
+ ui.notifications.message('Tested Plex Media Server host(s): ', urllib.unquote_plus(host.replace(',', ', ')))
+
+ return finalResult
def testLibnotify(self, *args, **kwargs):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@@ -4085,16 +4101,15 @@ class Home(MainHandler):
ui.notifications.error("Unable to contact one or more XBMC host(s): " + host)
redirect('/home/')
-
def updatePLEX(self, *args, **kwargs):
- if notifiers.plex_notifier.update_library():
+ result = notifiers.plex_notifier.update_library()
+ if None is result:
ui.notifications.message(
- "Library update command sent to Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
+ 'Library update command sent to', 'Plex Media Server host(s): ' + sickbeard.PLEX_SERVER_HOST.replace(',', ', '))
else:
- ui.notifications.error("Unable to contact Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST)
+ ui.notifications.error('Unable to contact', 'Plex Media Server host(s): ' + result.replace(',', ', '))
redirect('/home/')
-
def setStatus(self, show=None, eps=None, status=None, direct=False):
if show is None or eps is None or status is None: