+
Initial
#set $anyQualityList = filter(lambda x: x > $Quality.NONE, $Quality.qualityStrings)
#for $curQuality in sorted($anyQualityList):
- $Quality.qualityStrings[$curQuality]
+ $Quality.qualityStrings[$curQuality]
#end for
@@ -40,7 +42,7 @@
#set $bestQualityList = filter(lambda x: x > $Quality.SDTV and x < $Quality.UNKNOWN, $Quality.qualityStrings)
#for $curQuality in sorted($bestQualityList):
- $Quality.qualityStrings[$curQuality]
+ $Quality.qualityStrings[$curQuality]
#end for
diff --git a/gui/slick/interfaces/default/inc_top.tmpl b/gui/slick/interfaces/default/inc_top.tmpl
index 02d2c25f..d4b3956d 100644
--- a/gui/slick/interfaces/default/inc_top.tmpl
+++ b/gui/slick/interfaces/default/inc_top.tmpl
@@ -1,5 +1,6 @@
#import sickbeard
#import urllib
+#from sickbeard.helpers import anon_url
#slurp
@@ -125,19 +126,22 @@
-
- SickGear will be dropping support for Python 2.7.8 and below. We recommend updating to latest version:
- Download here
-
+
+
As per notify 29 Nov 2015, SickGear no longer supports Python 2.7.8 and older
+ Please upgrade to Python 2.7.9 or newer (not 3.x.x): Download here
+
#end if
#if $sickbeard.NEWEST_VERSION_STRING
diff --git a/gui/slick/js/configNotifications.js b/gui/slick/js/configNotifications.js
index d616e3c3..1e55af94 100644
--- a/gui/slick/js/configNotifications.js
+++ b/gui/slick/js/configNotifications.js
@@ -37,22 +37,30 @@
});
});
- $('#testXBMC').click(function () {
- var xbmc_host = $.trim($('#xbmc_host').val());
- var xbmc_username = $.trim($('#xbmc_username').val());
- var xbmc_password = $.trim($('#xbmc_password').val());
- if (!xbmc_host) {
- $('#testXBMC-result').html('Please fill out the necessary fields above.');
- $('#xbmc_host').addClass('warning');
+ $('#testEMBY').click(function () {
+ var emby_host = $('#emby_host').val();
+ var emby_apikey = $('#emby_apikey').val();
+ if (!emby_host || !emby_apikey) {
+ $('#testEMBY-result').html('Please fill out the necessary fields above.');
+ if (!emby_host) {
+ $('#emby_host').addClass('warning');
+ } else {
+ $('#emby_host').removeClass('warning');
+ }
+ if (!emby_apikey) {
+ $('#emby_apikey').addClass('warning');
+ } else {
+ $('#emby_apikey').removeClass('warning');
+ }
return;
}
- $('#xbmc_host').removeClass('warning');
+ $('#emby_host, #emby_apikey').removeClass('warning');
$(this).prop('disabled', true);
- $('#testXBMC-result').html(loading);
- $.get(sbRoot + '/home/testXBMC', {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password})
+ $('#testEMBY-result').html(loading);
+ $.get(sbRoot + '/home/testEMBY', {'host': emby_host, 'apikey': emby_apikey})
.done(function (data) {
- $('#testXBMC-result').html(data);
- $('#testXBMC').prop('disabled', false);
+ $('#testEMBY-result').html(data);
+ $('#testEMBY').prop('disabled', false);
});
});
@@ -75,6 +83,25 @@
});
});
+ $('#testXBMC').click(function () {
+ var xbmc_host = $.trim($('#xbmc_host').val());
+ var xbmc_username = $.trim($('#xbmc_username').val());
+ var xbmc_password = $.trim($('#xbmc_password').val());
+ if (!xbmc_host) {
+ $('#testXBMC-result').html('Please fill out the necessary fields above.');
+ $('#xbmc_host').addClass('warning');
+ return;
+ }
+ $('#xbmc_host').removeClass('warning');
+ $(this).prop('disabled', true);
+ $('#testXBMC-result').html(loading);
+ $.get(sbRoot + '/home/testXBMC', {'host': xbmc_host, 'username': xbmc_username, 'password': xbmc_password})
+ .done(function (data) {
+ $('#testXBMC-result').html(data);
+ $('#testXBMC').prop('disabled', false);
+ });
+ });
+
// show instructions for plex when enabled
$('#use_plex').click(function() {
if ( $(this).is(':checked') ) {
diff --git a/gui/slick/js/editShow.js b/gui/slick/js/editShow.js
index 9c6cc74b..1717bd91 100644
--- a/gui/slick/js/editShow.js
+++ b/gui/slick/js/editShow.js
@@ -2,81 +2,72 @@
$(document).ready(function () {
- $.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) {
- var resultStr, flag, selected, current_lang_added = '';
-
- if (data.results.length === 0) {
- flag = ' class="flag" style="background-image:url(' + sbRoot + '/images/flags/' + config.show_lang + '.png)"';
- resultStr = '
' + config.show_lang + ' ';
- } else {
- current_lang_added = false;
- $.each(data.results, function (index, obj) {
-
- if (obj === config.show_lang) {
- selected = ' selected="selected"';
- current_lang_added = true;
- }
- else {
- selected = '';
- }
-
- flag = ' class="flag" style="background-image:url(' + sbRoot + '/images/flags/' + obj + '.png);"';
- resultStr += '
' + obj + ' ';
- });
-
- if (!current_lang_added) {
- resultStr += '
' + config.show_lang + ' ';
- }
-
- }
- $('#indexerLangSelectEdit').html(resultStr);
-
- });
-
-
- var all_exceptions = [];
-
$('#location').fileBrowser({title: 'Select Show Location'});
- $('#submit').click(function () {
- all_exceptions = [];
+ function htmlFlag(lang) {
+ return ' class="flag" style="background-image:url(' + sbRoot + '/images/flags/' + lang + '.png)"'
+ }
- $('#exceptions_list').find('option').each (function () {
- all_exceptions.push($(this).val());
+ $.getJSON(sbRoot + '/home/addShows/getIndexerLanguages', {}, function (data) {
+ var result = '', currentLangAdded = '', selected = ' selected="selected"';
+
+ if (0 == data.results.length) {
+ result = '
'
+ + config.showLang + ' ';
+ } else {
+ currentLangAdded = !1;
+ $.each(data.results, function (index, strLang) {
+
+ var htmlSelected = '';
+ if (strLang === config.showLang) {
+ currentLangAdded = !0;
+ htmlSelected = selected;
+ }
+
+ result += '
'
+ + strLang + ' ';
+ });
+
+ if (!currentLangAdded)
+ result += '
' + config.showLang + ' ';
+ }
+
+ $('#indexerLangSelectEdit').html(result);
+ });
+
+ function getExceptions() {
+ var allExceptions = [];
+
+ $('#exceptions_list').find('option').each(function () {
+ allExceptions.push($(this).val());
});
- $('#exceptions_list').val(all_exceptions);
- if (config.show_isanime) {
+ return allExceptions
+ }
+
+ $('#submit').click(function () {
+ $('#exceptions_list').val(getExceptions());
+ if (config.showIsAnime)
generate_bwlist();
- }
});
$('#addSceneName').click(function () {
- var scene_ex = $('#SceneName').val();
- var scene_ex_season = $('#SceneNameSeason').val();
- var option = $('
');
- all_exceptions = [];
+ var elSceneName = $('#SceneName'), elSceneNameSeason = $('#SceneNameSeason'),
+ sceneEx = elSceneName.val(), sceneExSeason = elSceneNameSeason.val();
- $('#exceptions_list').find('option').each (function () {
- all_exceptions.push($(this).val());
- });
+ elSceneName.val('');
+ elSceneNameSeason.val('');
- $('#SceneName').val('');
- $('#SceneNameSeason').val('');
-
- if ($.inArray(scene_ex_season + '|' + scene_ex, all_exceptions) > -1 || (scene_ex === '')) {
+ if (-1 < $.inArray(sceneExSeason + '|' + sceneEx, getExceptions()) || ('' === sceneEx))
return;
- }
- $('#SceneException').show();
- option.attr('value', scene_ex_season + '|' + scene_ex);
- if (scene_ex_season === "-1") {
- option.html('S*: ' + scene_ex);
- }
- else {
- option.html('S' + scene_ex_season + ': ' + scene_ex);
- }
- return option.appendTo('#exceptions_list');
+ $('#SceneException').fadeIn('fast', 'linear');
+
+ var option = $(' ');
+ option.attr('value', sceneExSeason + '|' + sceneEx);
+ option.html((config.showIsAnime ? 'S' + ('-1' === sceneExSeason ? '*' : sceneExSeason) + ': ' : '') + sceneEx);
+
+ return option.appendTo($('#exceptions_list'));
});
$('#removeSceneName').click(function () {
@@ -86,21 +77,41 @@ $(document).ready(function () {
});
$.fn.toggle_SceneException = function () {
- all_exceptions = [];
+ var elSceneException = $('#SceneException');
- $('#exceptions_list').find('option').each (function () {
- all_exceptions.push($(this).val());
- });
-
- if ('' === all_exceptions) {
- $('#SceneException').hide();
- }
- else {
- $('#SceneException').show();
- }
+ if (0 == getExceptions().length)
+ elSceneException.fadeOut('fast', 'linear');
+ else
+ elSceneException.fadeIn('fast', 'linear');
};
$(this).toggle_SceneException();
+ var elABD = $('#air_by_date'), elScene = $('#scene'), elSports = $('#sports'), elAnime = $('#anime');
-});
\ No newline at end of file
+ function uncheck(el){el.prop('checked', !1)}
+ function checked(el){return el.prop('checked')}
+
+ function isAnime(){
+ uncheck(elABD); uncheck(elSports);
+ if (config.showIsAnime){ $('#blackwhitelist').fadeIn('fast', 'linear'); } return !0; }
+ function isScene(){ uncheck(elABD); uncheck(elSports); }
+ function isABD(){ uncheck(elAnime); uncheck(elScene); $('#blackwhitelist').fadeOut('fast', 'linear'); }
+ function isSports(){ uncheck(elAnime); uncheck(elScene); $('#blackwhitelist').fadeOut('fast', 'linear'); }
+
+ if (checked(elAnime)) { isAnime(); }
+ if (checked(elScene)) { isScene(); }
+ if (checked(elABD)) { isABD(); }
+ if (checked(elSports)) { isSports() }
+
+ elAnime.on('click', function() {
+ if (checked(elAnime))
+ isAnime() && !config.showIsAnime && $('#anime-options').fadeIn('fast', 'linear');
+ else
+ $('#blackwhitelist, #anime-options').fadeOut('fast', 'linear');
+ });
+ elScene.on('click', function() { isScene(); });
+ elABD.on('click', function() { isABD(); });
+ elSports.on('click', function() { isSports() });
+
+});
diff --git a/gui/slick/js/inc_top.js b/gui/slick/js/inc_top.js
index 2af9b287..00187472 100644
--- a/gui/slick/js/inc_top.js
+++ b/gui/slick/js/inc_top.js
@@ -27,10 +27,12 @@ function initActions() {
$('#SubMenu a:contains("Show Queue Overview")').addClass('btn').html(' Show Queue Overview');
$('#SubMenu a[href$="/manage/failedDownloads/"]').addClass('btn').html(' Failed Downloads');
$('#SubMenu a:contains("Notification")').addClass('btn').html(' Notifications');
- $('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html(' Update show in XBMC');
- $('#SubMenu a[href$="/home/updateXBMC/"]').addClass('btn').html(' Update XBMC');
- $('#SubMenu a:contains("Update show in Kodi")').addClass('btn').html(' Update show in Kodi');
+ $('#SubMenu a[href$="/home/updateEMBY/"]').addClass('btn').html(' Update Emby');
$('#SubMenu a[href$="/home/updateKODI/"]').addClass('btn').html(' Update Kodi');
+ $('#SubMenu a[href$="/home/updateXBMC/"]').addClass('btn').html(' Update XBMC');
+ $('#SubMenu a:contains("Update show in Emby")').addClass('btn').html(' Update show in Emby');
+ $('#SubMenu a:contains("Update show in Kodi")').addClass('btn').html(' Update show in Kodi');
+ $('#SubMenu a:contains("Update show in XBMC")').addClass('btn').html(' Update show in XBMC');
}
$(document).ready(function(){
diff --git a/gui/slick/js/qualityChooser.js b/gui/slick/js/qualityChooser.js
index bef406b0..e025a76a 100644
--- a/gui/slick/js/qualityChooser.js
+++ b/gui/slick/js/qualityChooser.js
@@ -2,7 +2,7 @@ function setFromPresets (preset) {
var elCustomQuality = $('.show-if-quality-custom'),
selected = 'selected';
if (preset = parseInt(preset)) {
- elCustomQuality.hide();
+ elCustomQuality.fadeOut('fast', 'linear');
var upgrade = !0;
$('#anyQualities, #bestQualities').find('option').each(function() {
@@ -18,7 +18,7 @@ function setFromPresets (preset) {
$(this).attr(selected, ((preset & parseInt($(this).val())) ? selected : false));
});
} else
- elCustomQuality.show();
+ elCustomQuality.fadeIn('fast', 'linear');
}
$(document).ready(function() {
@@ -30,4 +30,4 @@ $(document).ready(function() {
});
setFromPresets(elQualityPreset.find(selected).val());
-});
\ No newline at end of file
+});
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index cb67f6ef..868e66cc 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -42,8 +42,10 @@ from indexers.indexer_config import INDEXER_TVDB
from indexers.indexer_api import indexerApi
from indexers.indexer_exceptions import indexer_shownotfound, indexer_exception, indexer_error, \
indexer_episodenotfound, indexer_attributenotfound, indexer_seasonnotfound, indexer_userabort, indexerExcepts
+from sickbeard.exceptions import ex
from sickbeard.providers.generic import GenericProvider
from sickbeard import encodingKludge as ek
+from lib.adba.aniDBerrors import (AniDBError, AniDBBannedError)
from lib.configobj import ConfigObj
from lib.libtrakt import TraktAPI
import trakt_helpers
@@ -253,17 +255,10 @@ TORRENT_HIGH_BANDWIDTH = False
TORRENT_LABEL = ''
TORRENT_VERIFY_CERT = False
-USE_XBMC = False
-XBMC_ALWAYS_ON = True
-XBMC_NOTIFY_ONSNATCH = False
-XBMC_NOTIFY_ONDOWNLOAD = False
-XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = False
-XBMC_UPDATE_LIBRARY = False
-XBMC_UPDATE_FULL = False
-XBMC_UPDATE_ONLYFIRST = False
-XBMC_HOST = ''
-XBMC_USERNAME = None
-XBMC_PASSWORD = None
+USE_EMBY = False
+EMBY_UPDATE_LIBRARY = False
+EMBY_HOST = None
+EMBY_APIKEY = None
USE_KODI = False
KODI_ALWAYS_ON = True
@@ -277,6 +272,18 @@ KODI_HOST = ''
KODI_USERNAME = None
KODI_PASSWORD = None
+USE_XBMC = False
+XBMC_ALWAYS_ON = True
+XBMC_NOTIFY_ONSNATCH = False
+XBMC_NOTIFY_ONDOWNLOAD = False
+XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = False
+XBMC_UPDATE_LIBRARY = False
+XBMC_UPDATE_FULL = False
+XBMC_UPDATE_ONLYFIRST = False
+XBMC_HOST = ''
+XBMC_USERNAME = None
+XBMC_PASSWORD = None
+
USE_PLEX = False
PLEX_NOTIFY_ONSNATCH = False
PLEX_NOTIFY_ONDOWNLOAD = False
@@ -488,12 +495,15 @@ def initialize(consoleLogging=True):
SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_HOST, \
NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_HOST, NZBGET_USE_HTTPS, backlogSearchScheduler, \
TORRENT_USERNAME, TORRENT_PASSWORD, TORRENT_HOST, TORRENT_PATH, TORRENT_SEED_TIME, TORRENT_PAUSED, TORRENT_HIGH_BANDWIDTH, TORRENT_LABEL, TORRENT_VERIFY_CERT, \
- USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, \
- XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, BACKLOG_FREQUENCY, \
- USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, \
+ USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_HOST, EMBY_APIKEY, \
+ USE_KODI, KODI_ALWAYS_ON, KODI_NOTIFY_ONSNATCH, KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD,\
+ KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, KODI_UPDATE_LIBRARY, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, \
+ USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD,\
+ XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, XBMC_UPDATE_LIBRARY, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \
+ USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, \
+ PLEX_UPDATE_LIBRARY, PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, \
USE_TRAKT, TRAKT_CONNECTED_ACCOUNT, TRAKT_ACCOUNTS, TRAKT_MRU, TRAKT_VERIFY, TRAKT_REMOVE_WATCHLIST, TRAKT_TIMEOUT, TRAKT_USE_WATCHLIST, TRAKT_METHOD_ADD, TRAKT_START_PAUSED, traktCheckerScheduler, TRAKT_SYNC, TRAKT_DEFAULT_INDEXER, TRAKT_REMOVE_SERIESLIST, TRAKT_UPDATE_COLLECTION, \
- USE_PLEX, PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_UPDATE_LIBRARY, \
- PLEX_SERVER_HOST, PLEX_HOST, PLEX_USERNAME, PLEX_PASSWORD, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, MAX_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
+ BACKLOG_FREQUENCY, DEFAULT_BACKLOG_FREQUENCY, MIN_BACKLOG_FREQUENCY, MAX_BACKLOG_FREQUENCY, BACKLOG_STARTUP, SKIP_REMOVED_FILES, \
showUpdateScheduler, __INITIALIZED__, LAUNCH_BROWSER, TRASH_REMOVE_SHOW, TRASH_ROTATE_LOGS, HOME_SEARCH_FOCUS, SORT_ARTICLE, showList, loadingShowList, UPDATE_SHOWS_ON_START, SHOW_UPDATE_HOUR, ALLOW_INCOMPLETE_SHOWDATA, \
NEWZNAB_DATA, INDEXER_DEFAULT, INDEXER_TIMEOUT, USENET_RETENTION, TORRENT_DIR, \
QUALITY_DEFAULT, FLATTEN_FOLDERS_DEFAULT, SUBTITLES_DEFAULT, STATUS_DEFAULT, WANTED_BEGIN_DEFAULT, WANTED_LATEST_DEFAULT, RECENTSEARCH_STARTUP, \
@@ -535,8 +545,9 @@ def initialize(consoleLogging=True):
CheckSection(CFG, 'Blackhole')
CheckSection(CFG, 'SABnzbd')
CheckSection(CFG, 'NZBget')
- CheckSection(CFG, 'XBMC')
+ CheckSection(CFG, 'Emby')
CheckSection(CFG, 'Kodi')
+ CheckSection(CFG, 'XBMC')
CheckSection(CFG, 'PLEX')
CheckSection(CFG, 'Growl')
CheckSection(CFG, 'Prowl')
@@ -781,17 +792,10 @@ def initialize(consoleLogging=True):
TORRENT_LABEL = check_setting_str(CFG, 'TORRENT', 'torrent_label', '')
TORRENT_VERIFY_CERT = bool(check_setting_int(CFG, 'TORRENT', 'torrent_verify_cert', 0))
- USE_XBMC = bool(check_setting_int(CFG, 'XBMC', 'use_xbmc', 0))
- XBMC_ALWAYS_ON = bool(check_setting_int(CFG, 'XBMC', 'xbmc_always_on', 1))
- XBMC_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsnatch', 0))
- XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 0))
- XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsubtitledownload', 0))
- XBMC_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_library', 0))
- XBMC_UPDATE_FULL = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_full', 0))
- XBMC_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_onlyfirst', 0))
- XBMC_HOST = check_setting_str(CFG, 'XBMC', 'xbmc_host', '')
- XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '')
- XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '')
+ USE_EMBY = bool(check_setting_int(CFG, 'Emby', 'use_emby', 0))
+ EMBY_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Emby', 'emby_update_library', 0))
+ EMBY_HOST = check_setting_str(CFG, 'Emby', 'emby_host', '')
+ EMBY_APIKEY = check_setting_str(CFG, 'Emby', 'emby_apikey', '')
USE_KODI = bool(check_setting_int(CFG, 'Kodi', 'use_kodi', 0))
KODI_ALWAYS_ON = bool(check_setting_int(CFG, 'Kodi', 'kodi_always_on', 1))
@@ -805,6 +809,18 @@ def initialize(consoleLogging=True):
KODI_USERNAME = check_setting_str(CFG, 'Kodi', 'kodi_username', '')
KODI_PASSWORD = check_setting_str(CFG, 'Kodi', 'kodi_password', '')
+ USE_XBMC = bool(check_setting_int(CFG, 'XBMC', 'use_xbmc', 0))
+ XBMC_ALWAYS_ON = bool(check_setting_int(CFG, 'XBMC', 'xbmc_always_on', 1))
+ XBMC_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsnatch', 0))
+ XBMC_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_ondownload', 0))
+ XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'XBMC', 'xbmc_notify_onsubtitledownload', 0))
+ XBMC_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_library', 0))
+ XBMC_UPDATE_FULL = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_full', 0))
+ XBMC_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'XBMC', 'xbmc_update_onlyfirst', 0))
+ XBMC_HOST = check_setting_str(CFG, 'XBMC', 'xbmc_host', '')
+ XBMC_USERNAME = check_setting_str(CFG, 'XBMC', 'xbmc_username', '')
+ XBMC_PASSWORD = check_setting_str(CFG, 'XBMC', 'xbmc_password', '')
+
USE_PLEX = bool(check_setting_int(CFG, 'Plex', 'use_plex', 0))
PLEX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsnatch', 0))
PLEX_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_ondownload', 0))
@@ -1335,7 +1351,12 @@ def halt():
pass
if ADBA_CONNECTION:
- ADBA_CONNECTION.logout()
+ try:
+ ADBA_CONNECTION.logout()
+ except AniDBBannedError as e:
+ logger.log(u'ANIDB Error %s' % ex(e), logger.DEBUG)
+ except AniDBError as e:
+ pass
logger.log(u'Waiting for the ANIDB CONNECTION thread to exit')
try:
ADBA_CONNECTION.join(10)
@@ -1591,18 +1612,11 @@ def save_config():
new_config['TORRENT']['torrent_label'] = TORRENT_LABEL
new_config['TORRENT']['torrent_verify_cert'] = int(TORRENT_VERIFY_CERT)
- new_config['XBMC'] = {}
- new_config['XBMC']['use_xbmc'] = int(USE_XBMC)
- new_config['XBMC']['xbmc_always_on'] = int(XBMC_ALWAYS_ON)
- new_config['XBMC']['xbmc_notify_onsnatch'] = int(XBMC_NOTIFY_ONSNATCH)
- new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
- new_config['XBMC']['xbmc_notify_onsubtitledownload'] = int(XBMC_NOTIFY_ONSUBTITLEDOWNLOAD)
- new_config['XBMC']['xbmc_update_library'] = int(XBMC_UPDATE_LIBRARY)
- new_config['XBMC']['xbmc_update_full'] = int(XBMC_UPDATE_FULL)
- new_config['XBMC']['xbmc_update_onlyfirst'] = int(XBMC_UPDATE_ONLYFIRST)
- new_config['XBMC']['xbmc_host'] = XBMC_HOST
- new_config['XBMC']['xbmc_username'] = XBMC_USERNAME
- new_config['XBMC']['xbmc_password'] = helpers.encrypt(XBMC_PASSWORD, ENCRYPTION_VERSION)
+ new_config['Emby'] = {}
+ new_config['Emby']['use_emby'] = int(USE_EMBY)
+ new_config['Emby']['emby_update_library'] = int(EMBY_UPDATE_LIBRARY)
+ new_config['Emby']['emby_host'] = EMBY_HOST
+ new_config['Emby']['emby_apikey'] = EMBY_APIKEY
new_config['Kodi'] = {}
new_config['Kodi']['use_kodi'] = int(USE_KODI)
@@ -1617,6 +1631,19 @@ def save_config():
new_config['Kodi']['kodi_username'] = KODI_USERNAME
new_config['Kodi']['kodi_password'] = helpers.encrypt(KODI_PASSWORD, ENCRYPTION_VERSION)
+ new_config['XBMC'] = {}
+ new_config['XBMC']['use_xbmc'] = int(USE_XBMC)
+ new_config['XBMC']['xbmc_always_on'] = int(XBMC_ALWAYS_ON)
+ new_config['XBMC']['xbmc_notify_onsnatch'] = int(XBMC_NOTIFY_ONSNATCH)
+ new_config['XBMC']['xbmc_notify_ondownload'] = int(XBMC_NOTIFY_ONDOWNLOAD)
+ new_config['XBMC']['xbmc_notify_onsubtitledownload'] = int(XBMC_NOTIFY_ONSUBTITLEDOWNLOAD)
+ new_config['XBMC']['xbmc_update_library'] = int(XBMC_UPDATE_LIBRARY)
+ new_config['XBMC']['xbmc_update_full'] = int(XBMC_UPDATE_FULL)
+ new_config['XBMC']['xbmc_update_onlyfirst'] = int(XBMC_UPDATE_ONLYFIRST)
+ new_config['XBMC']['xbmc_host'] = XBMC_HOST
+ new_config['XBMC']['xbmc_username'] = XBMC_USERNAME
+ new_config['XBMC']['xbmc_password'] = helpers.encrypt(XBMC_PASSWORD, ENCRYPTION_VERSION)
+
new_config['Plex'] = {}
new_config['Plex']['use_plex'] = int(USE_PLEX)
new_config['Plex']['plex_notify_onsnatch'] = int(PLEX_NOTIFY_ONSNATCH)
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index 8167335e..a02635e2 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -1125,6 +1125,10 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
req_headers.update(headers)
session.headers.update(req_headers)
+ mute_connect_err = kwargs.get('mute_connect_err')
+ if mute_connect_err:
+ del(kwargs['mute_connect_err'])
+
# request session ssl verify
session.verify = False
@@ -1177,7 +1181,7 @@ def getURL(url, post_data=None, params=None, headers=None, timeout=30, session=N
logger.log(u'HTTP error %s while loading URL %s' % (e.errno, e.request.url), logger.WARNING)
return
except requests.exceptions.ConnectionError as e:
- if not kwargs.get('mute_connect_err'):
+ if not mute_connect_err:
logger.log(u'Connection error msg:%s while loading URL %s' % (e.message, e.request.url), logger.WARNING)
return
except requests.exceptions.ReadTimeout as e:
diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py
index 122eb3d4..7faf5685 100644
--- a/sickbeard/notifiers/__init__.py
+++ b/sickbeard/notifiers/__init__.py
@@ -16,8 +16,9 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see .
-import xbmc
+import emby
import kodi
+import xbmc
import plex
import nmj
import nmjv2
@@ -40,8 +41,9 @@ from lib import libtrakt
import emailnotify
# home theater / nas
-xbmc_notifier = xbmc.XBMCNotifier()
+emby_notifier = emby.EmbyNotifier()
kodi_notifier = kodi.KodiNotifier()
+xbmc_notifier = xbmc.XBMCNotifier()
plex_notifier = plex.PLEXNotifier()
nmj_notifier = nmj.NMJNotifier()
nmjv2_notifier = nmjv2.NMJv2Notifier()
@@ -64,8 +66,8 @@ email_notifier = emailnotify.EmailNotifier()
notifiers = [
libnotify_notifier, # Libnotify notifier goes first because it doesn't involve blocking on network activity.
- xbmc_notifier,
kodi_notifier,
+ xbmc_notifier,
plex_notifier,
nmj_notifier,
nmjv2_notifier,
diff --git a/sickbeard/notifiers/emby.py b/sickbeard/notifiers/emby.py
new file mode 100644
index 00000000..8c9985be
--- /dev/null
+++ b/sickbeard/notifiers/emby.py
@@ -0,0 +1,170 @@
+# coding=utf-8
+#
+# This file is part of SickGear.
+#
+# SickGear is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# SickGear is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with SickGear. If not, see .
+
+import sickbeard
+from sickbeard import logger
+
+
+class EmbyNotifier:
+ def __init__(self):
+ self.sg_logo_url = 'https://raw.githubusercontent.com/SickGear/SickGear/master/gui/slick/images/ico/' + \
+ 'apple-touch-icon-precomposed.png'
+ self.response = None
+ self.test_mode = False
+
+ def _notify_emby(self, msg, hosts=None, apikeys=None):
+ """ Internal wrapper for the test_notify function
+
+ Args:
+ msg: Message body of the notice to send
+
+ Returns:
+ 2-Tuple True if msg successfully sent otherwise False, Failure message string or None
+ """
+
+ if not sickbeard.USE_EMBY and not self.test_mode:
+ self._log(u'Notification not enabled, skipping this notification', logger.DEBUG)
+ return False, None
+
+ hosts, keys, message = self._check_config(hosts, apikeys)
+ if not hosts:
+ return False, message
+
+ total_success = True
+ messages = []
+
+ args = dict(post_json={'Name': 'SickGear', 'Description': msg, 'ImageUrl': self.sg_logo_url})
+ for i, cur_host in enumerate(hosts):
+
+ self.response = None
+ response = sickbeard.helpers.getURL(
+ 'http://%s/emby/Notifications/Admin' % cur_host,
+ headers={'Content-type': 'application/json', 'X-MediaBrowser-Token': keys[i]},
+ timeout=10, hooks=dict(response=self._cb_response), **args)
+ if not response or self.response:
+ if self.response and 401 == self.response.get('status_code'):
+ total_success = False
+ messages += ['Fail: Cannot authenticate API key with %s' % cur_host]
+ self._log(u'Failed to authenticate with %s' % cur_host)
+ continue
+ elif not response and not self.response or not self.response.get('ok'):
+ total_success = False
+ messages += ['Fail: No supported Emby server found at %s' % cur_host]
+ self._log(u'Warning, could not connect with server at ' + cur_host)
+ continue
+ messages += ['OK: %s' % cur_host]
+
+ return total_success, ' \n'.join(messages)
+
+ def _update_library(self, show=None):
+
+ hosts, keys, message = self._check_config()
+ if not hosts:
+ self._log(u'Issue with hosts or api keys, check your settings')
+ return False
+
+ from sickbeard.indexers.indexer_config import INDEXER_TVDB
+ args = show and INDEXER_TVDB == show.indexer \
+ and dict(post_json={'TvdbId': '%s' % show.indexerid}) or dict(data=None)
+ mode_to_log = show and 'show "%s"' % show.name or 'all shows'
+ total_success = True
+ for i, cur_host in enumerate(hosts):
+
+ self.response = None
+ # noinspection PyArgumentList
+ response = sickbeard.helpers.getURL(
+ 'http://%s/emby/Library/Series/Updated' % cur_host,
+ headers={'Content-type': 'application/json', 'X-MediaBrowser-Token': keys[i]},
+ timeout=20, hooks=dict(response=self._cb_response), **args)
+ # Emby will initiate a LibraryMonitor path refresh one minute after this success
+ if self.response and 204 == self.response.get('status_code') and self.response.get('ok'):
+ self._log(u'Success: update %s sent to host %s in a library updated call' % (mode_to_log, cur_host),
+ logger.MESSAGE)
+ continue
+ elif self.response and 401 == self.response.get('status_code'):
+ self._log(u'Failed to authenticate with %s' % cur_host)
+ elif self.response and 404 == self.response.get('status_code'):
+ self._log(u'Warning, Library update responded 404 not found at %s' % cur_host, logger.DEBUG)
+ elif not response and not self.response or not self.response.get('ok'):
+ self._log(u'Warning, could not connect with server at %s' % cur_host)
+ else:
+ self._log(u'Warning, unknown response %sfrom %s, can most likely be ignored'
+ % (self.response and '%s ' % self.response.get('status_code') or '', cur_host), logger.DEBUG)
+ total_success = False
+
+ return total_success
+
+ # noinspection PyUnusedLocal
+ def _cb_response(self, r, *args, **kwargs):
+
+ self.response = dict(status_code=r.status_code, ok=r.ok)
+ return r
+
+ def _check_config(self, hosts=None, apikeys=None):
+
+ from sickbeard.helpers import starify
+
+ hosts, keys = hosts or sickbeard.EMBY_HOST, apikeys or sickbeard.EMBY_APIKEY
+ hosts = [x.strip() for x in hosts.split(',') if x.strip()]
+ keys = [x.strip() for x in keys.split(',') if x.strip()]
+
+ new_keys = []
+ has_old_key = False
+ for key in keys:
+ if starify(key, True):
+ has_old_key = True
+ else:
+ new_keys += [key]
+
+ apikeys = (new_keys, [x.strip() for x in sickbeard.EMBY_APIKEY.split(',') if x.strip()] + new_keys)[has_old_key]
+
+ if len(hosts) != len(apikeys):
+ message = ('Not enough Api keys for hosts', 'More Api keys than hosts')[len(apikeys) > len(hosts)]
+ self._log(u'%s, check your settings' % message)
+ return False, False, message
+
+ return hosts, apikeys, 'OK'
+
+ @staticmethod
+ def _log(msg, log_level=logger.WARNING):
+
+ logger.log(u'Emby: %s' % msg, log_level)
+
+ ##############################################################################
+ # Public functions
+ ##############################################################################
+
+ def test_notify(self, host, apikey):
+
+ self.test_mode = True
+ result = self._notify_emby('Testing SickGear Emby notifier', host, apikey)
+ self.test_mode = False
+ return result
+
+ def update_library(self, show=None, force=False):
+ """ Wrapper for the update library functions
+
+ :param show: TVShow object
+ :param force: True force update process
+
+ Returns: None if no processing done, True if processing succeeded with no issues else False if any issues found
+ """
+ if sickbeard.USE_EMBY and (sickbeard.EMBY_UPDATE_LIBRARY or force):
+ return self._update_library(show)
+
+
+notifier = EmbyNotifier
diff --git a/sickbeard/notifiers/kodi.py b/sickbeard/notifiers/kodi.py
index f24ed53f..c93a9752 100644
--- a/sickbeard/notifiers/kodi.py
+++ b/sickbeard/notifiers/kodi.py
@@ -48,6 +48,26 @@ class KodiNotifier:
self.prefix = ''
self.test_mode = False
+ @staticmethod
+ def _log(msg, log_level=logger.WARNING):
+
+ logger.log(u'Kodi: %s' % msg, log_level)
+
+ def _maybe_log(self, msg, log_level=logger.WARNING):
+
+ if msg and (sickbeard.KODI_ALWAYS_ON or self.test_mode):
+ self._log(msg + (not sickbeard.KODI_ALWAYS_ON and self.test_mode and
+ ' (Test mode ignores "Always On")' or ''), log_level)
+
+ def _maybe_log_failed_detection(self, host, msg='connect to'):
+
+ self._maybe_log(u'Failed to %s %s, check device(s) and config.' % (msg, host), logger.ERROR)
+
+ # noinspection PyUnusedLocal
+ def cb_response(self, r, *args, **kwargs):
+ self.response = dict(status_code=r.status_code)
+ return r
+
def _get_kodi_version(self, host):
""" Return Kodi JSON-RPC API version (odd # = dev, even # = stable)
@@ -67,7 +87,8 @@ class KodiNotifier:
6 | v12 (Frodo) / v13 (Gotham)
"""
- response = self._send_to_kodi_json(host, dict(method='JSONRPC.Version'), 10)
+ timeout = 10
+ response = self._send_to_kodi_json(host, dict(method='JSONRPC.Version'), timeout)
if self.response and 401 == self.response.get('status_code'):
return False
@@ -77,13 +98,13 @@ class KodiNotifier:
# fallback to legacy HTTPAPI method
test_command = {'command': 'Help'}
- if self._send_to_kodi(host, test_command):
+ if self._send_to_kodi(host, test_command, timeout):
# return fake version number to use the legacy method
return 1
if self.response and 404 == self.response.get('status_code'):
self.prefix = 'xbmc'
- if self._send_to_kodi(host, test_command):
+ if self._send_to_kodi(host, test_command, timeout):
# return fake version number to use the legacy method
return 1
@@ -125,7 +146,7 @@ class KodiNotifier:
elif not api_version:
total_success = False
message += ['Fail: No supported Kodi found at %s' % cur_host]
- self._maybe_log_failed_detection(cur_host)
+ self._maybe_log_failed_detection(cur_host, 'connect and detect version for')
else:
if 4 >= api_version:
self._log(u'Detected %sversion <= 11, using HTTP API'
@@ -141,12 +162,62 @@ class KodiNotifier:
'message': '%s' % msg,
'image': '%s' % self.sg_logo_url})
- response_notify = __method_send(cur_host, command)
+ response_notify = __method_send(cur_host, command, 10)
if response_notify:
message += ['%s: %s' % ((response_notify, 'OK')['OK' in response_notify], cur_host)]
return total_success, ' \n'.join(message)
+ def _update_library(self, show_name=None):
+ """ Wrapper for the update library functions
+
+ Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
+
+ Uses a list of comma delimited hosts where only one is updated, the first to respond with success. This is a
+ workaround for SQL backend users because updating multiple clients causes duplicate entries.
+
+ Future plan is to revisit how host/ip/username/pw/options are stored so that this may become more flexible.
+
+ Args:
+ show_name: Name of a TV show to target for a library update
+
+ Returns: True if processing succeeded with no issues else False if any issues found
+ """
+ if not sickbeard.KODI_HOST:
+ self._log(u'No Kodi hosts specified, check your settings')
+ return False
+
+ # either update each host, or only attempt to update until one successful result
+ result = 0
+ only_first = dict(show='', first='', first_note='')
+ show_name and only_first.update(show=' for show;"%s"' % show_name)
+ sickbeard.KODI_UPDATE_ONLYFIRST and only_first.update(dict(
+ first=' first', first_note=' in line with the "Only update first host"%s' % ' setting'))
+
+ for cur_host in [x.strip() for x in sickbeard.KODI_HOST.split(',')]:
+
+ response = self._send_to_kodi_json(cur_host, dict(method='Profiles.GetCurrentProfile'))
+ if self.response and 401 == self.response.get('status_code'):
+ self._log(u'Failed to authenticate with %s' % cur_host, logger.DEBUG)
+ continue
+ if not response:
+ self._maybe_log_failed_detection(cur_host)
+ continue
+
+ if self._send_update_library(cur_host, show_name):
+ only_first.update(dict(profile=response.get('label') or 'Master', host=cur_host))
+ self._log('Success: profile;' +
+ u'"%(profile)s" at%(first)s host;%(host)s updated%(show)s%(first_note)s' % only_first)
+ else:
+ self._maybe_log_failed_detection(cur_host)
+ result += 1
+
+ if sickbeard.KODI_UPDATE_ONLYFIRST:
+ return True
+
+ # needed for the 'update kodi' submenu command as it only cares of the final result vs the individual ones
+ return 0 == result
+
def _send_update_library(self, host, show_name=None):
""" Internal wrapper for the update library function
@@ -164,7 +235,7 @@ class KodiNotifier:
api_version = self._get_kodi_version(host)
if api_version:
# try to update just the show, if it fails, do full update if enabled
- __method_update = (self._update_library, self._update_library_json)[4 < api_version]
+ __method_update = (self._update, self._update_json)[4 < api_version]
if __method_update(host, show_name):
return True
@@ -181,7 +252,7 @@ class KodiNotifier:
# Legacy HTTP API (pre Kodi 12) methods
##############################################################################
- def _send_to_kodi(self, host, command):
+ def _send_to_kodi(self, host, command, timeout=30):
""" Handle communication to Kodi servers via HTTP API
Args:
@@ -203,11 +274,12 @@ class KodiNotifier:
args['auth'] = (self.username or sickbeard.KODI_USERNAME, self.password or sickbeard.KODI_PASSWORD)
url = 'http://%s/%sCmds/%sHttp' % (host, self.prefix or 'kodi', self.prefix or 'kodi')
- response = sickbeard.helpers.getURL(url=url, params=command, hooks=dict(response=self.cb_response), **args)
+ response = sickbeard.helpers.getURL(url=url, params=command,
+ timeout=timeout, hooks=dict(response=self.cb_response), **args)
return response or False
- def _update_library(self, host=None, show_name=None):
+ def _update(self, host=None, show_name=None):
""" Handle updating Kodi host via HTTP API
Update the video library for a specific tv show if passed, otherwise update the whole library if option enabled.
@@ -323,12 +395,7 @@ class KodiNotifier:
% (json.dumps(response['error']), host, json.dumps(command)), logger.ERROR)
return result
- # noinspection PyUnusedLocal
- def cb_response(self, r, *args, **kwargs):
- self.response = dict(status_code=r.status_code)
- return r
-
- def _update_library_json(self, host=None, show_name=None):
+ def _update_json(self, host=None, show_name=None):
""" Handle updating Kodi host via HTTP JSON-RPC
Update the video library for a specific tv show if passed, otherwise update the whole library if option enabled.
@@ -410,99 +477,49 @@ class KodiNotifier:
return True
- def _maybe_log_failed_detection(self, host):
-
- self._maybe_log(u'Failed to detect version for %s, check configuration.' % host)
-
- def _maybe_log(self, msg, log_level=None):
-
- if msg and (sickbeard.KODI_ALWAYS_ON or self.test_mode):
- self._log(msg + (not sickbeard.KODI_ALWAYS_ON and self.test_mode and ' (Test mode always logs)' or ''),
- log_level)
-
- @staticmethod
- def _log(msg, log_level=logger.WARNING):
-
- logger.log(u'Kodi: %s' % msg, log_level)
-
##############################################################################
# Public functions which will call the JSON or Legacy HTTP API methods
##############################################################################
def notify_snatch(self, ep_name):
+
if sickbeard.KODI_NOTIFY_ONSNATCH:
self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_SNATCH])
def notify_download(self, ep_name):
+
if sickbeard.KODI_NOTIFY_ONDOWNLOAD:
self._notify_kodi(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD])
def notify_subtitle_download(self, ep_name, lang):
+
if sickbeard.KODI_NOTIFY_ONSUBTITLEDOWNLOAD:
self._notify_kodi('%s: %s' % (ep_name, lang), common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD])
def notify_git_update(self, new_version='??'):
+
if sickbeard.USE_KODI:
update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT]
title = common.notifyStrings[common.NOTIFY_GIT_UPDATE]
self._notify_kodi('%s %s' % (update_text, new_version), title)
def test_notify(self, host, username, password):
- self.test_mode, self.username, self.password = True, username, password
- return self._notify_kodi('Testing SickGear Kodi notifier', 'Test Notification', kodi_hosts=host)
- def update_library(self, showName=None):
+ self.test_mode, self.username, self.password = True, username, password
+ result = self._notify_kodi('Testing SickGear Kodi notifier', 'Test Notification', kodi_hosts=host)
+ self.test_mode = False
+ return result
+
+ def update_library(self, showName=None, force=False):
""" Wrapper for the update library functions
- Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.
+ :param showName: Name of a TV show
+ :param force: True force update process
- Uses a list of comma delimited hosts where only one is updated, the first to respond with success. This is a
- workaround for SQL backend users because updating multiple clients causes duplicate entries.
-
- Future plan is to revisit how host/ip/username/pw/options are stored so that this may become more flexible.
-
- Args:
- showName: Name of a TV show to target for a library update
-
- Returns:
- True or False
+ Returns: None if no processing done, True if processing succeeded with no issues else False if any issues found
"""
-
- if sickbeard.USE_KODI and sickbeard.KODI_UPDATE_LIBRARY:
- if not sickbeard.KODI_HOST:
- self._log(u'No Kodi hosts specified, check your settings', logger.DEBUG)
- return False
-
- # either update each host, or only attempt to update until one successful result
- result = 0
- only_first = dict(show='', first='', first_note='')
- showName and only_first.update(show=' for show;"%s"' % showName)
- sickbeard.KODI_UPDATE_ONLYFIRST and only_first.update(dict(
- first=' first', first_note=' in line with the "Only update first host"%s' % ' setting'))
-
- for cur_host in [x.strip() for x in sickbeard.KODI_HOST.split(',')]:
-
- response = self._send_to_kodi_json(cur_host, dict(method='Profiles.GetCurrentProfile'))
- if self.response and 401 == self.response.get('status_code'):
- self._log(u'Failed to authenticate with %s' % cur_host, logger.DEBUG)
- continue
- if not response:
- self._maybe_log_failed_detection(cur_host)
- continue
-
- if self._send_update_library(cur_host, showName):
- only_first.update(dict(profile=response.get('label') or 'Master', host=cur_host))
- self._log('Success: profile;' +
- u'"%(profile)s" at%(first)s host;%(host)s updated%(show)s%(first_note)s' % only_first)
- else:
- self._maybe_log_failed_detection(cur_host)
- result += 1
-
- if sickbeard.KODI_UPDATE_ONLYFIRST:
- return True
-
- # needed for the 'update kodi' submenu command as it only cares of the final result vs the individual ones
- return 0 == result
+ if sickbeard.USE_KODI and (sickbeard.KODI_UPDATE_LIBRARY or force):
+ return self._update_library(showName)
notifier = KodiNotifier
diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py
index 06e21278..d1c2ac6a 100644
--- a/sickbeard/postProcessor.py
+++ b/sickbeard/postProcessor.py
@@ -1035,12 +1035,15 @@ class PostProcessor(object):
# send notifications
notifiers.notify_download(ep_obj._format_pattern('%SN - %Sx%0E - %EN - %QN'))
- # do the library update for XBMC
- notifiers.xbmc_notifier.update_library(ep_obj.show.name)
+ # do the library update for Emby
+ notifiers.emby_notifier.update_library(ep_obj.show)
# do the library update for Kodi
notifiers.kodi_notifier.update_library(ep_obj.show.name)
+ # do the library update for XBMC
+ notifiers.xbmc_notifier.update_library(ep_obj.show.name)
+
# do the library update for Plex
notifiers.plex_notifier.update_library(ep_obj)
diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py
index b7c1eec5..43da6b78 100644
--- a/sickbeard/properFinder.py
+++ b/sickbeard/properFinder.py
@@ -42,7 +42,7 @@ def search_propers():
logger.log(u'Beginning search for new propers')
- age_shows, age_anime = 2, 14
+ age_shows, age_anime = sickbeard.BACKLOG_DAYS + 2, 14
aired_since_shows = datetime.datetime.today() - datetime.timedelta(days=age_shows)
aired_since_anime = datetime.datetime.today() - datetime.timedelta(days=age_anime)
recent_shows, recent_anime = _recent_history(aired_since_shows, aired_since_anime)
@@ -100,7 +100,7 @@ def _get_proper_list(aired_since_shows, recent_shows, recent_anime):
name = _generic_name(x.name)
if name not in propers:
try:
- parse_result = np.parse(x.title)
+ parse_result = np.parse(x.name)
if parse_result.series_name and parse_result.episode_numbers and \
parse_result.show.indexerid in recent_shows + recent_anime:
logger.log(u'Found new proper: ' + x.name, logger.DEBUG)
diff --git a/sickbeard/providers/generic.py b/sickbeard/providers/generic.py
index db018ccb..06411219 100644
--- a/sickbeard/providers/generic.py
+++ b/sickbeard/providers/generic.py
@@ -599,7 +599,6 @@ class NZBProvider(object, GenericProvider):
index = 0
alt_search = ('nzbs_org' == self.get_id())
- term_items_found = False
do_search_alt = False
search_terms = []
@@ -614,21 +613,16 @@ class NZBProvider(object, GenericProvider):
regex += [terms]
proper_check = re.compile(r'(?i)(%s)' % '|'.join(regex))
+ urls = []
while index < len(search_terms):
- search_params = {'q': search_terms[index], 'maxage': 4}
+ search_params = {'q': search_terms[index], 'maxage': sickbeard.BACKLOG_DAYS + 2}
if alt_search:
if do_search_alt:
+ search_params['t'] = 'search'
index += 1
- if term_items_found:
- do_search_alt = True
- term_items_found = False
- else:
- if do_search_alt:
- search_params['t'] = 'search'
-
- do_search_alt = (True, False)[do_search_alt]
+ do_search_alt = not do_search_alt
else:
index += 1
@@ -637,8 +631,9 @@ class NZBProvider(object, GenericProvider):
(title, url) = self._title_and_url(item)
- if not proper_check.search(title):
+ if not proper_check.search(title) or url in urls:
continue
+ urls.append(url)
if 'published_parsed' in item and item['published_parsed']:
result_date = item.published_parsed
@@ -651,10 +646,8 @@ class NZBProvider(object, GenericProvider):
if not search_date or search_date < result_date:
search_result = classes.Proper(title, url, result_date, self.show)
results.append(search_result)
- term_items_found = True
- do_search_alt = False
- time.sleep(0.2)
+ time.sleep(0.5)
return results
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index f3fee55a..12b1b964 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -533,20 +533,25 @@ class Home(MainHandler):
return [
{'title': 'Add Shows', 'path': 'home/addShows/', },
{'title': 'Manual Post-Processing', 'path': 'home/postprocess/'},
- {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': self.haveXBMC},
+ {'title': 'Update Emby', 'path': 'home/updateEMBY/', 'requires': self.haveEMBY},
{'title': 'Update Kodi', 'path': 'home/updateKODI/', 'requires': self.haveKODI},
+ {'title': 'Update XBMC', 'path': 'home/updateXBMC/', 'requires': self.haveXBMC},
{'title': 'Update Plex', 'path': 'home/updatePLEX/', 'requires': self.havePLEX},
{'title': 'Restart', 'path': 'home/restart/?pid=' + str(sickbeard.PID), 'confirm': True},
{'title': 'Shutdown', 'path': 'home/shutdown/?pid=' + str(sickbeard.PID), 'confirm': True},
]
@staticmethod
- def haveXBMC():
- return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY
+ def haveEMBY():
+ return sickbeard.USE_EMBY
@staticmethod
def haveKODI():
- return sickbeard.USE_KODI and sickbeard.KODI_UPDATE_LIBRARY
+ return sickbeard.USE_KODI
+
+ @staticmethod
+ def haveXBMC():
+ return sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY
@staticmethod
def havePLEX():
@@ -771,6 +776,29 @@ class Home(MainHandler):
else:
return 'Error sending tweet'
+ def testEMBY(self, host=None, apikey=None):
+ self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+ hosts = config.clean_hosts(host)
+ if not hosts:
+ return 'Fail: At least one invalid host'
+
+ total_success, cur_message = notifiers.emby_notifier.test_notify(hosts, apikey)
+ return (cur_message, u'Success. All Emby hosts tested.')[total_success]
+
+ def testKODI(self, host=None, username=None, password=None):
+ self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
+
+ hosts = config.clean_hosts(host)
+ if not hosts:
+ return 'Fail: At least one invalid host'
+
+ if None is not password and set('*') == set(password):
+ password = sickbeard.KODI_PASSWORD
+
+ total_success, cur_message = notifiers.kodi_notifier.test_notify(hosts, username, password)
+ return (cur_message, u'Success. All Kodi hosts tested.')[total_success]
+
def testXBMC(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@@ -789,19 +817,6 @@ class Home(MainHandler):
return finalResult
- def testKODI(self, host=None, username=None, password=None):
- self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
-
- hosts = config.clean_hosts(host)
- if not hosts:
- return 'Fail: At least one invalid host'
-
- if None is not password and set('*') == set(password):
- password = sickbeard.KODI_PASSWORD
-
- total_success, cur_message = notifiers.kodi_notifier.test_notify(hosts, username, password)
- return (cur_message, u'Success. All Kodi hosts tested.')[total_success]
-
def testPMC(self, host=None, username=None, password=None):
self.set_header('Cache-Control', 'max-age=0,no-cache,no-store')
@@ -1184,12 +1199,16 @@ class Home(MainHandler):
t.submenu.append({'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.indexerid})
t.submenu.append(
{'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&force=1&web=1' % showObj.indexerid})
- t.submenu.append({'title': 'Update show in XBMC',
- 'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus(
- showObj.name.encode('utf-8')), 'requires': self.haveXBMC})
+ t.submenu.append({'title': 'Update show in Emby',
+ 'path': 'home/updateEMBY%s' %
+ (INDEXER_TVDB == showObj.indexer and ('?show=%s' % showObj.indexerid) or '/'),
+ 'requires': self.haveEMBY})
t.submenu.append({'title': 'Update show in Kodi',
'path': 'home/updateKODI?showName=%s' % urllib.quote_plus(
showObj.name.encode('utf-8')), 'requires': self.haveKODI})
+ t.submenu.append({'title': 'Update show in XBMC',
+ 'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus(
+ showObj.name.encode('utf-8')), 'requires': self.haveXBMC})
t.submenu.append({'title': 'Media Renamer', 'path': 'home/testRename?show=%d' % showObj.indexerid})
if sickbeard.USE_SUBTITLES and not sickbeard.showQueueScheduler.action.isBeingSubtitled(
showObj) and showObj.subtitles:
@@ -1617,6 +1636,30 @@ class Home(MainHandler):
self.redirect('/home/displayShow?show=' + str(showObj.indexerid))
+ def updateEMBY(self, show=None):
+
+ if notifiers.emby_notifier.update_library(
+ sickbeard.helpers.findCertainShow(sickbeard.showList,helpers.tryInt(show, None)), force=True):
+ ui.notifications.message('Library update command sent to Emby host(s): ' + sickbeard.EMBY_HOST)
+ else:
+ ui.notifications.error('Unable to contact one or more Emby host(s): ' + sickbeard.EMBY_HOST)
+ self.redirect('/home/')
+
+ def updateKODI(self, showName=None):
+
+ # only send update to first host in the list -- workaround for kodi sql backend users
+ if sickbeard.KODI_UPDATE_ONLYFIRST:
+ # only send update to first host in the list -- workaround for kodi sql backend users
+ host = sickbeard.KODI_HOST.split(',')[0].strip()
+ else:
+ host = sickbeard.KODI_HOST
+
+ if notifiers.kodi_notifier.update_library(showName=showName, force=True):
+ ui.notifications.message('Library update command sent to Kodi host(s): ' + host)
+ else:
+ ui.notifications.error('Unable to contact one or more Kodi host(s): ' + host)
+ self.redirect('/home/')
+
def updateXBMC(self, showName=None):
# only send update to first host in the list -- workaround for xbmc sql backend users
@@ -1632,21 +1675,6 @@ class Home(MainHandler):
ui.notifications.error('Unable to contact one or more XBMC host(s): ' + host)
self.redirect('/home/')
- def updateKODI(self, showName=None):
-
- # only send update to first host in the list -- workaround for kodi sql backend users
- if sickbeard.KODI_UPDATE_ONLYFIRST:
- # only send update to first host in the list -- workaround for kodi sql backend users
- host = sickbeard.KODI_HOST.split(',')[0].strip()
- else:
- host = sickbeard.KODI_HOST
-
- if notifiers.kodi_notifier.update_library(showName=showName):
- ui.notifications.message('Library update command sent to Kodi host(s): ' + host)
- else:
- ui.notifications.error('Unable to contact one or more Kodi host(s): ' + host)
- self.redirect('/home/')
-
def updatePLEX(self, *args, **kwargs):
result = notifiers.plex_notifier.update_library()
if None is result:
@@ -4873,14 +4901,16 @@ class ConfigNotifications(Config):
'b64': base64.urlsafe_b64encode(location)})
return t.respond()
- def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None,
- xbmc_notify_ondownload=None,
- xbmc_notify_onsubtitledownload=None, xbmc_update_onlyfirst=None,
- xbmc_update_library=None, xbmc_update_full=None, xbmc_host=None, xbmc_username=None,
- xbmc_password=None,
+ def saveNotifications(self,
+ use_emby=None, emby_update_library=None, emby_host=None, emby_apikey=None,
use_kodi=None, kodi_always_on=None, kodi_notify_onsnatch=None, kodi_notify_ondownload=None,
- kodi_notify_onsubtitledownload=None, kodi_update_onlyfirst=None, kodi_update_library=None,
- kodi_update_full=None, kodi_host=None, kodi_username=None, kodi_password=None,
+ kodi_notify_onsubtitledownload=None, kodi_update_onlyfirst=None,
+ kodi_update_library=None, kodi_update_full=None,
+ kodi_host=None, kodi_username=None, kodi_password=None,
+ use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None,
+ xbmc_notify_onsubtitledownload=None, xbmc_update_onlyfirst=None,
+ xbmc_update_library=None, xbmc_update_full=None,
+ xbmc_host=None, xbmc_username=None, xbmc_password=None,
use_plex=None, plex_notify_onsnatch=None, plex_notify_ondownload=None,
plex_notify_onsubtitledownload=None, plex_update_library=None,
plex_server_host=None, plex_host=None, plex_username=None, plex_password=None,
@@ -4922,18 +4952,24 @@ class ConfigNotifications(Config):
results = []
- sickbeard.USE_XBMC = config.checkbox_to_value(use_xbmc)
- sickbeard.XBMC_ALWAYS_ON = config.checkbox_to_value(xbmc_always_on)
- sickbeard.XBMC_NOTIFY_ONSNATCH = config.checkbox_to_value(xbmc_notify_onsnatch)
- sickbeard.XBMC_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(xbmc_notify_ondownload)
- sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(xbmc_notify_onsubtitledownload)
- sickbeard.XBMC_UPDATE_LIBRARY = config.checkbox_to_value(xbmc_update_library)
- sickbeard.XBMC_UPDATE_FULL = config.checkbox_to_value(xbmc_update_full)
- sickbeard.XBMC_UPDATE_ONLYFIRST = config.checkbox_to_value(xbmc_update_onlyfirst)
- sickbeard.XBMC_HOST = config.clean_hosts(xbmc_host)
- sickbeard.XBMC_USERNAME = xbmc_username
- if set('*') != set(xbmc_password):
- sickbeard.XBMC_PASSWORD = xbmc_password
+ sickbeard.USE_EMBY = config.checkbox_to_value(use_emby)
+ sickbeard.EMBY_UPDATE_LIBRARY = config.checkbox_to_value(emby_update_library)
+ sickbeard.EMBY_HOST = config.clean_hosts(emby_host)
+ keys_changed = False
+ all_keys = []
+ old_keys = [x.strip() for x in sickbeard.EMBY_APIKEY.split(',') if x.strip()]
+ new_keys = [x.strip() for x in emby_apikey.split(',') if x.strip()]
+ for key in new_keys:
+ if not starify(key, True):
+ keys_changed = True
+ all_keys += [key]
+ continue
+ for x in old_keys:
+ if key.startswith(x[0:3]) and key.endswith(x[-4:]):
+ all_keys += [x]
+ break
+ if keys_changed or (len(all_keys) != len(old_keys)):
+ sickbeard.EMBY_APIKEY = ','.join(all_keys)
sickbeard.USE_KODI = config.checkbox_to_value(use_kodi)
sickbeard.KODI_ALWAYS_ON = config.checkbox_to_value(kodi_always_on)
@@ -4948,6 +4984,19 @@ class ConfigNotifications(Config):
if set('*') != set(kodi_password):
sickbeard.KODI_PASSWORD = kodi_password
+ sickbeard.USE_XBMC = config.checkbox_to_value(use_xbmc)
+ sickbeard.XBMC_ALWAYS_ON = config.checkbox_to_value(xbmc_always_on)
+ sickbeard.XBMC_NOTIFY_ONSNATCH = config.checkbox_to_value(xbmc_notify_onsnatch)
+ sickbeard.XBMC_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(xbmc_notify_ondownload)
+ sickbeard.XBMC_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(xbmc_notify_onsubtitledownload)
+ sickbeard.XBMC_UPDATE_LIBRARY = config.checkbox_to_value(xbmc_update_library)
+ sickbeard.XBMC_UPDATE_FULL = config.checkbox_to_value(xbmc_update_full)
+ sickbeard.XBMC_UPDATE_ONLYFIRST = config.checkbox_to_value(xbmc_update_onlyfirst)
+ sickbeard.XBMC_HOST = config.clean_hosts(xbmc_host)
+ sickbeard.XBMC_USERNAME = xbmc_username
+ if set('*') != set(xbmc_password):
+ sickbeard.XBMC_PASSWORD = xbmc_password
+
sickbeard.USE_PLEX = config.checkbox_to_value(use_plex)
sickbeard.PLEX_NOTIFY_ONSNATCH = config.checkbox_to_value(plex_notify_onsnatch)
sickbeard.PLEX_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(plex_notify_ondownload)