+
Backlog search spread
@@ -115,7 +114,7 @@
-
+
Backlog search spread
diff --git a/gui/slick/interfaces/default/history.tmpl b/gui/slick/interfaces/default/history.tmpl
index 0bf2f46..0952d08 100644
--- a/gui/slick/interfaces/default/history.tmpl
+++ b/gui/slick/interfaces/default/history.tmpl
@@ -430,6 +430,11 @@
+ A mapping in the client notification section is needed for results if a player library folder is different to a parent folder
+
+
+
+
In Compact layout, deleting records removes all episode related records. Detailed layout allows for individual selection [Show me ]
diff --git a/gui/slick/js/config.js b/gui/slick/js/config.js
index 7db58f4..9a0134b 100644
--- a/gui/slick/js/config.js
+++ b/gui/slick/js/config.js
@@ -1,6 +1,18 @@
+function toggle$(el, cond){
+ var ifId = '-if-' + $(el).attr('id');
+ if(cond){
+ $('.hide' + ifId).fadeOut('fast', 'linear');
+ $('.show' + ifId).fadeIn('fast', 'linear');
+ } else {
+ $('.show' + ifId).fadeOut('fast', 'linear');
+ $('.hide' + ifId).fadeIn('fast', 'linear');
+ }
+}
+
$(document).ready(function () {
var enabler = $('.enabler'),
- viewIf = $('.viewIf');
+ viewIf = $('input.view-if'),
+ viewIfSel = $('select.view-if');
enabler.each(function () {
if (!$(this).prop('checked'))
@@ -16,18 +28,19 @@ $(document).ready(function () {
});
viewIf.each(function () {
- $(($(this).prop('checked') ? '.hide_if_' : '.show_if_') + $(this).attr('id')).hide();
+ $(($(this).prop('checked') ? '.hide-if-' : '.show-if-') + $(this).attr('id')).hide();
});
viewIf.click(function () {
- var if_id = '_if_' + $(this).attr('id');
- if ($(this).prop('checked')) {
- $('.hide' + if_id).fadeOut('fast', 'linear');
- $('.show' + if_id).fadeIn('fast', 'linear');
- } else {
- $('.show' + if_id).fadeOut('fast', 'linear');
- $('.hide' + if_id).fadeIn('fast', 'linear');
- }
+ toggle$(this, $(this).prop('checked'));
+ });
+
+ viewIfSel.each(function () {
+ $((0 < $(this).find('option:selected').val() ? '.hide-if-' : '.show-if-') + $(this).attr('id')).hide();
+ });
+
+ viewIfSel.change(function(){
+ toggle$(this, 0 < $(this).find('option:selected').val());
});
var idSelect = '#imdb-accounts', idDel = '#imdb-list-del', idInput = '#imdb-url', idOnOff = '#imdb-list-onoff',
diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py
index 5faa22b..8897e94 100755
--- a/sickbeard/__init__.py
+++ b/sickbeard/__init__.py
@@ -285,6 +285,7 @@ TORRENT_VERIFY_CERT = False
USE_EMBY = False
EMBY_UPDATE_LIBRARY = False
+EMBY_PARENT_MAPS = None
EMBY_HOST = None
EMBY_APIKEY = None
EMBY_WATCHEDSTATE_SCHEDULED = False
@@ -298,6 +299,7 @@ KODI_NOTIFY_ONSUBTITLEDOWNLOAD = False
KODI_UPDATE_LIBRARY = False
KODI_UPDATE_FULL = False
KODI_UPDATE_ONLYFIRST = False
+KODI_PARENT_MAPS = None
KODI_HOST = ''
KODI_USERNAME = None
KODI_PASSWORD = None
@@ -307,6 +309,7 @@ PLEX_NOTIFY_ONSNATCH = False
PLEX_NOTIFY_ONDOWNLOAD = False
PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = False
PLEX_UPDATE_LIBRARY = False
+PLEX_PARENT_MAPS = None
PLEX_SERVER_HOST = None
PLEX_HOST = None
PLEX_USERNAME = None
@@ -639,14 +642,14 @@ def initialize(console_logging=True):
global metadata_provider_dict, METADATA_KODI, METADATA_MEDE8ER, METADATA_XBMC, METADATA_MEDIABROWSER, \
METADATA_PS3, METADATA_TIVO, METADATA_WDTV, METADATA_XBMC_12PLUS
# Notification Settings/HT and NAS
- global USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_HOST, EMBY_APIKEY, \
+ global USE_EMBY, EMBY_UPDATE_LIBRARY, EMBY_PARENT_MAPS, EMBY_HOST, EMBY_APIKEY, \
EMBY_WATCHEDSTATE_SCHEDULED, EMBY_WATCHEDSTATE_FREQUENCY, \
USE_KODI, KODI_ALWAYS_ON, KODI_UPDATE_LIBRARY, KODI_UPDATE_FULL, KODI_UPDATE_ONLYFIRST, \
- KODI_HOST, KODI_USERNAME, KODI_PASSWORD, KODI_NOTIFY_ONSNATCH, \
+ KODI_PARENT_MAPS, KODI_HOST, KODI_USERNAME, KODI_PASSWORD, KODI_NOTIFY_ONSNATCH, \
KODI_NOTIFY_ONDOWNLOAD, KODI_NOTIFY_ONSUBTITLEDOWNLOAD, \
USE_XBMC, XBMC_ALWAYS_ON, XBMC_NOTIFY_ONSNATCH, XBMC_NOTIFY_ONDOWNLOAD, XBMC_NOTIFY_ONSUBTITLEDOWNLOAD, \
XBMC_UPDATE_LIBRARY, XBMC_UPDATE_FULL, XBMC_UPDATE_ONLYFIRST, XBMC_HOST, XBMC_USERNAME, XBMC_PASSWORD, \
- USE_PLEX, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE_LIBRARY, PLEX_SERVER_HOST, \
+ USE_PLEX, PLEX_USERNAME, PLEX_PASSWORD, PLEX_UPDATE_LIBRARY, PLEX_PARENT_MAPS, PLEX_SERVER_HOST, \
PLEX_NOTIFY_ONSNATCH, PLEX_NOTIFY_ONDOWNLOAD, PLEX_NOTIFY_ONSUBTITLEDOWNLOAD, PLEX_HOST, \
PLEX_WATCHEDSTATE_SCHEDULED, PLEX_WATCHEDSTATE_FREQUENCY, \
USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, \
@@ -947,6 +950,7 @@ def initialize(console_logging=True):
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_PARENT_MAPS = check_setting_str(CFG, 'Emby', 'emby_parent_maps', '')
EMBY_HOST = check_setting_str(CFG, 'Emby', 'emby_host', '')
EMBY_APIKEY = check_setting_str(CFG, 'Emby', 'emby_apikey', '')
EMBY_WATCHEDSTATE_SCHEDULED = bool(check_setting_int(CFG, 'Emby', 'emby_watchedstate_scheduled', 0))
@@ -962,6 +966,7 @@ def initialize(console_logging=True):
KODI_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_library', 0))
KODI_UPDATE_FULL = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_full', 0))
KODI_UPDATE_ONLYFIRST = bool(check_setting_int(CFG, 'Kodi', 'kodi_update_onlyfirst', 0))
+ KODI_PARENT_MAPS = check_setting_str(CFG, 'Kodi', 'kodi_parent_maps', '')
KODI_HOST = check_setting_str(CFG, 'Kodi', 'kodi_host', '')
KODI_USERNAME = check_setting_str(CFG, 'Kodi', 'kodi_username', '')
KODI_PASSWORD = check_setting_str(CFG, 'Kodi', 'kodi_password', '')
@@ -983,6 +988,7 @@ def initialize(console_logging=True):
PLEX_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_ondownload', 0))
PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = bool(check_setting_int(CFG, 'Plex', 'plex_notify_onsubtitledownload', 0))
PLEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Plex', 'plex_update_library', 0))
+ PLEX_PARENT_MAPS = check_setting_str(CFG, 'Plex', 'plex_parent_maps', '')
PLEX_SERVER_HOST = check_setting_str(CFG, 'Plex', 'plex_server_host', '')
PLEX_HOST = check_setting_str(CFG, 'Plex', 'plex_host', '')
PLEX_USERNAME = check_setting_str(CFG, 'Plex', 'plex_username', '')
@@ -1375,7 +1381,8 @@ def initialize(console_logging=True):
cycleTime=datetime.timedelta(seconds=3),
threadName='SEARCHQUEUE')
- update_interval = datetime.timedelta(minutes=(RECENTSEARCH_FREQUENCY, 1)[4489 == RECENTSEARCH_FREQUENCY])
+ # enter 4490 (was 4489) for experimental internal provider frequencies
+ update_interval = datetime.timedelta(minutes=(RECENTSEARCH_FREQUENCY, 1)[4499 == RECENTSEARCH_FREQUENCY])
recentSearchScheduler = scheduler.Scheduler(
search_recent.RecentSearcher(),
cycleTime=update_interval,
@@ -1776,6 +1783,7 @@ def save_config():
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_parent_maps'] = EMBY_PARENT_MAPS
new_config['Emby']['emby_host'] = EMBY_HOST
new_config['Emby']['emby_apikey'] = EMBY_APIKEY
new_config['Emby']['emby_watchedstate_scheduled'] = int(EMBY_WATCHEDSTATE_SCHEDULED)
@@ -1787,6 +1795,7 @@ def save_config():
new_config['Kodi']['kodi_update_library'] = int(KODI_UPDATE_LIBRARY)
new_config['Kodi']['kodi_update_full'] = int(KODI_UPDATE_FULL)
new_config['Kodi']['kodi_update_onlyfirst'] = int(KODI_UPDATE_ONLYFIRST)
+ new_config['Kodi']['kodi_parent_maps'] = KODI_PARENT_MAPS
new_config['Kodi']['kodi_host'] = KODI_HOST
new_config['Kodi']['kodi_username'] = KODI_USERNAME
new_config['Kodi']['kodi_password'] = helpers.encrypt(KODI_PASSWORD, ENCRYPTION_VERSION)
@@ -1799,6 +1808,7 @@ def save_config():
new_config['Plex']['plex_username'] = PLEX_USERNAME
new_config['Plex']['plex_password'] = helpers.encrypt(PLEX_PASSWORD, ENCRYPTION_VERSION)
new_config['Plex']['plex_update_library'] = int(PLEX_UPDATE_LIBRARY)
+ new_config['Plex']['plex_parent_maps'] = PLEX_PARENT_MAPS
new_config['Plex']['plex_server_host'] = PLEX_SERVER_HOST
new_config['Plex']['plex_notify_onsnatch'] = int(PLEX_NOTIFY_ONSNATCH)
new_config['Plex']['plex_notify_ondownload'] = int(PLEX_NOTIFY_ONDOWNLOAD)
diff --git a/sickbeard/config.py b/sickbeard/config.py
index 352fc5c..b9c4a6b 100644
--- a/sickbeard/config.py
+++ b/sickbeard/config.py
@@ -342,6 +342,19 @@ def clean_url(url, add_slash=True):
return cleaned_url
+def kv_csv(data, default=''):
+ """
+ Returns a cleansed CSV string of key value pairs
+ Elements must have one '=' in order to be returned
+ Elements are stripped of leading/trailing whitespace but may contain whitespace (e.g. "tv shows")
+ """
+ if not isinstance(data, basestring):
+ return default
+
+ return ','.join(['='.join(i.strip() for i in i.split('=')) for i in data.split(',')
+ if 1 == len(re.findall('=', i)) and all(i.replace(' ', '').split('='))])
+
+
def to_int(val, default=0):
""" Return int value of val or default on error """
diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py
index afcbcb8..ed9d795 100644
--- a/sickbeard/helpers.py
+++ b/sickbeard/helpers.py
@@ -1618,3 +1618,28 @@ def freespace(path=None):
pass
return result
+
+
+def path_mapper(search, replace, subject):
+ """
+ Substitute strings in a path
+
+ :param search: Search text
+ :type search: String
+ :param replace: Replacement text
+ :type replace: String
+ :param subject: Path text to search
+ :type subject: String
+ :return: Subject with or without substitution, True if a change was made otherwise False
+ :rtype: Tuple
+ """
+ delim = '/!~!/'
+ search = re.sub(r'[\\]', delim, search)
+ replace = re.sub(r'[\\]', delim, replace)
+ path = re.sub(r'[\\]', delim, subject)
+ result = re.sub('(?i)^%s' % search, replace, path)
+
+ if re.search(delim, path):
+ result = os.path.normpath(re.sub(delim, '/', result))
+
+ return result, result != subject
diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py
index 65f9f66..7a16c4c 100644
--- a/sickbeard/webserve.py
+++ b/sickbeard/webserve.py
@@ -874,6 +874,9 @@ class MainHandler(WebHandler):
except (StandardError, Exception):
pass
+ mapped = 0
+ mapping = None
+ maps = [x.split('=') for x in sickbeard.KODI_PARENT_MAPS.split(',') if any(x)]
for k, d in data.iteritems():
d['label'] = '%s%s{Kodi}' % (d['label'], bool(d['label']) and ' ' or '')
try:
@@ -881,6 +884,19 @@ class MainHandler(WebHandler):
except (StandardError, Exception):
d['played'] = 0
+ for m in maps:
+ result, change = helpers.path_mapper(m[0], m[1], d['path_file'])
+ if change:
+ if not mapping:
+ mapping = (states[idx]['path_file'], result)
+ mapped += 1
+ states[idx]['path_file'] = result
+ break
+
+ if mapping:
+ logger.log('Folder mappings used, the first of %s is [%s] in Kodi is [%s] in SickGear' %
+ (mapped, mapping[0], mapping[1]))
+
return self.update_watched_state(data, as_json)
@staticmethod
@@ -5284,13 +5300,17 @@ class History(MainHandler):
if sickbeard.USE_EMBY and hosts:
logger.log('Beginning Emby update watched episode states')
- rd = sickbeard.ROOT_DIRS.split('|')[1:]
- rootpaths = sorted(['%s%s' % (ek.ek(os.path.splitdrive, x)[1], os.path.sep) for x in rd],
- key=len, reverse=True)
+ rd = sickbeard.ROOT_DIRS.split('|')[1:] + \
+ [x.split('=')[0] for x in sickbeard.EMBY_PARENT_MAPS.split(',') if any(x)]
+ rootpaths = sorted(
+ ['%s%s' % (ek.ek(os.path.splitdrive, x)[1], os.path.sep) for x in rd], key=len, reverse=True)
rootdirs = sorted([x for x in rd], key=len, reverse=True)
headers = {'Content-type': 'application/json'}
states = {}
idx = 0
+ mapped = 0
+ mapping = None
+ maps = [x.split('=') for x in sickbeard.EMBY_PARENT_MAPS.split(',') if any(x)]
for i, cur_host in enumerate(hosts):
base_url = 'http://%s/emby/Users' % cur_host
headers.update({'X-MediaBrowser-Token': keys[i]})
@@ -5345,9 +5365,22 @@ class History(MainHandler):
label='%s%s{Emby}' % (user.get('Name', ''), bool(user.get('Name')) and ' ' or ''),
date_watched=sickbeard.sbdatetime.sbdatetime.totimestamp(
dateutil.parser.parse(d.get('UserData', {}).get('LastPlayedDate'))))
+
+ for m in maps:
+ result, change = helpers.path_mapper(m[0], m[1], states[idx]['path_file'])
+ if change:
+ if not mapping:
+ mapping = (states[idx]['path_file'], result)
+ mapped += 1
+ states[idx]['path_file'] = result
+ break
+
idx += 1
except(StandardError, Exception):
continue
+ if mapping:
+ logger.log('Folder mappings used, the first of %s is [%s] in Emby is [%s] in SickGear' %
+ (mapped, mapping[0], mapping[1]))
if states:
# Prune user removed items that are no longer being returned by API
@@ -5371,10 +5404,15 @@ class History(MainHandler):
import urllib2
plex = Plex(dict(username=sickbeard.PLEX_USERNAME, password=sickbeard.PLEX_PASSWORD,
- section_filter_path=sickbeard.ROOT_DIRS.split('|')[1:]))
+ section_filter_path=sickbeard.ROOT_DIRS.split('|')[1:] +
+ [x.split('=')[0] for x in sickbeard.PLEX_PARENT_MAPS.split(',') if any(x)]))
states = {}
idx = 0
+ played = 0
+ mapped = 0
+ mapping = None
+ maps = [x.split('=') for x in sickbeard.PLEX_PARENT_MAPS.split(',') if any(x)]
for cur_host in hosts:
parts = urllib2.splitport(cur_host)
if parts[0]:
@@ -5386,10 +5424,26 @@ class History(MainHandler):
for k, v in plex.show_states.iteritems():
if 0 < v.get('played') or 0:
+ played += 1
states[idx] = v
states[idx]['label'] = '%s%s{Plex}' % (v['label'], bool(v['label']) and ' ' or '')
+
+ for m in maps:
+ result, change = helpers.path_mapper(m[0], m[1], states[idx]['path_file'])
+ if change:
+ if not mapping:
+ mapping = (states[idx]['path_file'], result)
+ mapped += 1
+ states[idx]['path_file'] = result
+ break
+
idx += 1
+ logger.log('Fetched %s of %s played for host : %s' % (len(plex.show_states), played, cur_host))
+ if mapping:
+ logger.log('Folder mappings used, the first of %s is [%s] in Plex is [%s] in SickGear' %
+ (mapped, mapping[0], mapping[1]))
+
if states:
# Prune user removed items that are no longer being returned by API
my_db = db.DBConnection(row_type='dict')
@@ -6420,11 +6474,12 @@ class ConfigNotifications(Config):
def save_notifications(
self,
- use_emby=None, emby_update_library=None, emby_watched_interval=None, emby_host=None, emby_apikey=None,
+ use_emby=None, emby_update_library=None, emby_watched_interval=None, emby_parent_maps=None,
+ emby_host=None, emby_apikey=None,
use_kodi=None, kodi_always_on=None, kodi_update_library=None, kodi_update_full=None,
- kodi_update_onlyfirst=None, kodi_host=None, kodi_username=None, kodi_password=None,
+ kodi_update_onlyfirst=None, kodi_parent_maps=None, kodi_host=None, kodi_username=None, kodi_password=None,
kodi_notify_onsnatch=None, kodi_notify_ondownload=None, kodi_notify_onsubtitledownload=None,
- use_plex=None, plex_update_library=None, plex_watched_interval=None,
+ use_plex=None, plex_update_library=None, plex_watched_interval=None, plex_parent_maps=None,
plex_username=None, plex_password=None, plex_server_host=None,
plex_notify_onsnatch=None, plex_notify_ondownload=None, plex_notify_onsubtitledownload=None, plex_host=None,
# use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=None,
@@ -6480,6 +6535,7 @@ class ConfigNotifications(Config):
sickbeard.USE_EMBY = config.checkbox_to_value(use_emby)
sickbeard.EMBY_UPDATE_LIBRARY = config.checkbox_to_value(emby_update_library)
+ sickbeard.EMBY_PARENT_MAPS = config.kv_csv(emby_parent_maps)
sickbeard.EMBY_HOST = config.clean_hosts(emby_host)
keys_changed = False
all_keys = []
@@ -6505,6 +6561,7 @@ class ConfigNotifications(Config):
sickbeard.KODI_UPDATE_LIBRARY = config.checkbox_to_value(kodi_update_library)
sickbeard.KODI_UPDATE_FULL = config.checkbox_to_value(kodi_update_full)
sickbeard.KODI_UPDATE_ONLYFIRST = config.checkbox_to_value(kodi_update_onlyfirst)
+ sickbeard.KODI_PARENT_MAPS = config.kv_csv(kodi_parent_maps)
sickbeard.KODI_HOST = config.clean_hosts(kodi_host)
sickbeard.KODI_USERNAME = kodi_username
if set('*') != set(kodi_password):
@@ -6528,6 +6585,7 @@ class ConfigNotifications(Config):
sickbeard.PLEX_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(plex_notify_ondownload)
sickbeard.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD = config.checkbox_to_value(plex_notify_onsubtitledownload)
sickbeard.PLEX_UPDATE_LIBRARY = config.checkbox_to_value(plex_update_library)
+ sickbeard.PLEX_PARENT_MAPS = config.kv_csv(plex_parent_maps)
sickbeard.PLEX_HOST = config.clean_hosts(plex_host)
sickbeard.PLEX_SERVER_HOST = config.clean_hosts(plex_server_host)
sickbeard.PLEX_USERNAME = plex_username