From 3cf28750497aa7cddad3a23f94f207db76bd3afc Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Tue, 13 May 2014 23:48:53 +0200 Subject: [PATCH 1/8] Change detection non seasons packs --- sickbeard/name_parser/parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 20ffdb7cb8..2812dbd7e8 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -125,8 +125,8 @@ def _parse_string(self, name): if tmp_extra_info: result.is_proper = re.search('(^|[\. _-])(proper|repack)([\. _-]|$)', tmp_extra_info, re.I) is not None - # Show.S04.Special is almost certainly not every episode in the season - if tmp_extra_info and cur_regex_name == 'season_only' and re.match(r'([. _-]|^)(special|extra)\w*([. _-]|$)', tmp_extra_info, re.I): + # Show.S04.Special or Show.S05.Part.2.Extras is almost certainly not every episode in the season + if tmp_extra_info and cur_regex_name == 'season_only' and re.search(r'([. _-]|^)(special|extra)s?\w*([. _-]|$)', tmp_extra_info, re.I): continue result.extra_info = tmp_extra_info From 4d13557f80b7bec626a4d63ea2dd0fed1d959fb8 Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Wed, 30 Apr 2014 17:45:19 +0200 Subject: [PATCH 2/8] pep8 cleanup --- sickbeard/name_parser/parser.py | 74 +++++++++++++++++---------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 2812dbd7e8..1a3e39761d 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -26,6 +26,7 @@ from sickbeard import logger + class NameParser(object): def __init__(self, file_name=True): @@ -36,20 +37,20 @@ def __init__(self, file_name=True): def clean_series_name(self, series_name): """Cleans up series name by removing any . and _ characters, along with any trailing hyphens. - + Is basically equivalent to replacing all _ and . with a space, but handles decimal numbers in string, for example: - + >>> cleanRegexedSeriesName("an.example.1.0.test") 'an example 1.0 test' >>> cleanRegexedSeriesName("an_example_1.0_test") 'an example 1.0 test' - + Stolen from dbr's tvnamer """ - + series_name = re.sub("(\D)\.(?!\s)(\D)", "\\1 \\2", series_name) - series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot + series_name = re.sub("(\d)\.(\d{4})", "\\1 \\2", series_name) # if it ends in a year then don't keep the dot series_name = re.sub("(\D)\.(?!\s)", "\\1 ", series_name) series_name = re.sub("\.(?!\s)(\D)", " \\1", series_name) series_name = series_name.replace("_", " ") @@ -66,36 +67,36 @@ def _compile_regexes(self): self.compiled_regexes.append((cur_pattern_name, cur_regex)) def _parse_string(self, name): - + if not name: return None - + for (cur_regex_name, cur_regex) in self.compiled_regexes: match = cur_regex.match(name) if not match: continue - + result = ParseResult(name) result.which_regex = [cur_regex_name] - + named_groups = match.groupdict().keys() if 'series_name' in named_groups: result.series_name = match.group('series_name') if result.series_name: result.series_name = self.clean_series_name(result.series_name) - + if 'season_num' in named_groups: tmp_season = int(match.group('season_num')) - if cur_regex_name == 'bare' and tmp_season in (19,20): + if cur_regex_name == 'bare' and tmp_season in (19, 20): continue result.season_number = tmp_season - + if 'ep_num' in named_groups: ep_num = self._convert_number(match.group('ep_num')) if 'extra_ep_num' in named_groups and match.group('extra_ep_num'): - result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num'))+1) + result.episode_numbers = range(ep_num, self._convert_number(match.group('extra_ep_num')) + 1) else: result.episode_numbers = [ep_num] @@ -103,7 +104,7 @@ def _parse_string(self, name): year = int(match.group('air_year')) month = int(match.group('air_month')) day = int(match.group('air_day')) - + # make an attempt to detect YYYY-DD-MM formats if month > 12: tmp_month = month @@ -148,10 +149,10 @@ def _combine_results(self, first, second, attr): # if the second doesn't exist then return the first if not second: return getattr(first, attr) - + a = getattr(first, attr) b = getattr(second, attr) - + # if a is good use it if a != None or (type(a) == list and len(a)): return a @@ -159,7 +160,7 @@ def _combine_results(self, first, second, attr): else: return b - def _unicodify(self, obj, encoding = "utf-8"): + def _unicodify(self, obj, encoding="utf-8"): if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) @@ -203,9 +204,9 @@ def _convert_number(self, number): return int(number) def parse(self, name): - + name = self._unicodify(name) - + cached = name_parser_cache.get(name) if cached: return cached @@ -217,16 +218,16 @@ def parse(self, name): base_file_name = ext_match.group(1) else: base_file_name = file_name - + # use only the direct parent dir dir_name = os.path.basename(dir_name) - + # set up a result to use final_result = ParseResult(name) - + # try parsing the file name file_name_result = self._parse_string(base_file_name) - + # parse the dirname for extra info if needed dir_name_result = self._parse_string(dir_name) @@ -263,6 +264,7 @@ def parse(self, name): # return it return final_result + class ParseResult(object): def __init__(self, original_name, @@ -275,7 +277,7 @@ def __init__(self, ): self.original_name = original_name - + self.series_name = series_name self.season_number = season_number if not episode_numbers: @@ -285,15 +287,15 @@ def __init__(self, self.extra_info = extra_info self.release_group = release_group - + self.air_date = air_date - + self.which_regex = None - + def __eq__(self, other): if not other: return False - + if self.series_name != other.series_name: return False if self.season_number != other.season_number: @@ -306,7 +308,7 @@ def __eq__(self, other): return False if self.air_date != other.air_date: return False - + return True def __str__(self): @@ -315,10 +317,10 @@ def __str__(self): else: to_return = u'' if self.season_number != None: - to_return += 'S'+str(self.season_number) + to_return += 'S' + str(self.season_number) if self.episode_numbers and len(self.episode_numbers): for e in self.episode_numbers: - to_return += 'E'+str(e) + to_return += 'E' + str(e) if self.air_by_date: to_return += str(self.air_date) @@ -328,7 +330,7 @@ def __str__(self): if self.release_group: to_return += ' (' + self.release_group + ')' - to_return += ' [ABD: '+str(self.air_by_date)+']' + to_return += ' [ABD: ' + str(self.air_by_date) + ']' return to_return.encode('utf-8') @@ -338,19 +340,20 @@ def _is_air_by_date(self): return False air_by_date = property(_is_air_by_date) + class NameParserCache(object): #TODO: check if the fifo list can beskiped and only use one dict - _previous_parsed_list = [] # keep a fifo list of the cached items + _previous_parsed_list = [] # keep a fifo list of the cached items _previous_parsed = {} _cache_size = 100 - + def add(self, name, parse_result): self._previous_parsed[name] = parse_result self._previous_parsed_list.append(name) while len(self._previous_parsed_list) > self._cache_size: del_me = self._previous_parsed_list.pop(0) self._previous_parsed.pop(del_me) - + def get(self, name): if name in self._previous_parsed: logger.log("Using cached parse result for: " + name, logger.DEBUG) @@ -360,5 +363,6 @@ def get(self, name): name_parser_cache = NameParserCache() + class InvalidNameException(Exception): "The given name is not valid" From b2b7db647332c7013cf7b795a4973e2d084c4efa Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Wed, 30 Apr 2014 17:50:12 +0200 Subject: [PATCH 3/8] Add cleanup release name, group * strip non release groups * remove extensions --- sickbeard/helpers.py | 27 +++++++++++++++++ sickbeard/name_parser/parser.py | 16 ++++++----- sickbeard/postProcessor.py | 51 ++++++++++----------------------- sickbeard/tv.py | 28 ++++++++++-------- 4 files changed, 67 insertions(+), 55 deletions(-) diff --git a/sickbeard/helpers.py b/sickbeard/helpers.py index d63f150e74..db9cb7348d 100644 --- a/sickbeard/helpers.py +++ b/sickbeard/helpers.py @@ -50,6 +50,7 @@ from sickbeard.exceptions import MultipleShowObjectsException, ex from sickbeard import logger, classes from sickbeard.common import USER_AGENT, mediaExtensions, XML_NSMAP +from sickbeard.common import mediaExtensions from sickbeard import db from sickbeard import encodingKludge as ek @@ -82,6 +83,32 @@ def indentXML(elem, level=0): elem.tail = i +def remove_extension(name): + """ + Remove download or media extension from name (if any) + """ + + if name and "." in name: + base_name, sep, extension = name.rpartition('.') # @UnusedVariable + if base_name and extension.lower() in ['nzb', 'torrent'] + mediaExtensions: + name = base_name + + return name + + +def remove_non_release_groups(name): + """ + Remove non release groups from name + """ + + if name and "-" in name: + name_group = name.rsplit('-', 1) + if name_group[-1].upper() in ["RP", "NZBGEEK"]: + name = name_group[0] + + return name + + def replaceExtension(filename, newExt): ''' >>> replaceExtension('foo.avi', 'mkv') diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 1a3e39761d..720ab9617d 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -25,12 +25,14 @@ import sickbeard from sickbeard import logger +from sickbeard import encodingKludge as ek +from sickbeard import helpers class NameParser(object): - def __init__(self, file_name=True): + def __init__(self, is_file_name=True): - self.file_name = file_name + self.is_file_name = is_file_name self.compiled_regexes = [] self._compile_regexes() @@ -212,15 +214,15 @@ def parse(self, name): return cached # break it into parts if there are any (dirname, file name, extension) - dir_name, file_name = os.path.split(name) - ext_match = re.match('(.*)\.\w{3,4}$', file_name) - if ext_match and self.file_name: - base_file_name = ext_match.group(1) + dir_name, file_name = ek.ek(os.path.split, name) + + if self.is_file_name: + base_file_name = helpers.remove_extension(file_name) else: base_file_name = file_name # use only the direct parent dir - dir_name = os.path.basename(dir_name) + dir_name = ek.ek(os.path.basename, dir_name) # set up a result to use final_result = ParseResult(name) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 1ce5885c80..a2969edabc 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -83,12 +83,12 @@ def __init__(self, file_path, nzb_name=None, pp_options={}): self.force_replace = pp_options.get('force_replace', False) self.in_history = False + self.release_group = None - self.is_proper = False - self.good_results = {self.NZB_NAME: False, - self.FOLDER_NAME: False, - self.FILE_NAME: False} + self.release_name = None + + self.is_proper = False self.log = '' @@ -359,8 +359,10 @@ def _analyze_name(self, name, file_name=True): if not name: return to_return + name = helpers.remove_non_release_groups(helpers.remove_extension(name)) + # parse the name to break it into show name, season, and episode - np = NameParser(file_name) + np = NameParser(False) parse_result = np.parse(name) self._log(u"Parsed " + name + " into " + str(parse_result).decode('utf-8', 'xmlcharrefreplace'), logger.DEBUG) @@ -437,16 +439,10 @@ def _finalize(self, parse_result): self.is_proper = parse_result.is_proper # if the result is complete then remember that for later - if parse_result.series_name and parse_result.season_number != None and parse_result.episode_numbers and parse_result.release_group: - test_name = ek.ek(os.path.basename, parse_result.original_name) - if test_name == self.nzb_name: - self.good_results[self.NZB_NAME] = True - elif test_name == self.folder_name: - self.good_results[self.FOLDER_NAME] = True - elif test_name == self.file_name: - self.good_results[self.FILE_NAME] = True - else: - logger.log(u"Nothing was good, found " + repr(test_name) + " and wanted either " + repr(self.nzb_name) + ", " + repr(self.folder_name) + ", or " + repr(self.file_name)) + if parse_result.series_name and parse_result.season_number is not None and parse_result.episode_numbers and parse_result.release_group: + if not self.release_name: + self.release_name = helpers.remove_extension(ek.ek(os.path.basename, parse_result.original_name)) + else: logger.log(u"Parse result not sufficient (all following have to be set). will not save release name", logger.DEBUG) logger.log(u"Parse result(series_name): " + str(parse_result.series_name), logger.DEBUG) @@ -813,28 +809,11 @@ def process(self): # update the ep info before we rename so the quality & release name go into the name properly for cur_ep in [ep_obj] + ep_obj.relatedEps: - cur_release_name = None - - # use the best possible representation of the release name - if self.good_results[self.NZB_NAME]: - cur_release_name = self.nzb_name - if cur_release_name.lower().endswith('.nzb'): - cur_release_name = cur_release_name.rpartition('.')[0] - - elif self.good_results[self.FILE_NAME]: - cur_release_name = self.file_name - # take the extension off the filename, it's not needed - if '.' in self.file_name: - cur_release_name = self.file_name.rpartition('.')[0] - - elif self.good_results[self.FOLDER_NAME]: - cur_release_name = self.folder_name - - if cur_release_name: - self._log("Found release name " + cur_release_name, logger.DEBUG) - cur_ep.release_name = cur_release_name + + if self.release_name: + self._log("Found release name " + self.release_name, logger.DEBUG) + cur_ep.release_name = self.release_name else: - logger.log(u"good results: " + repr(self.good_results), logger.DEBUG) cur_ep.release_name = "" cur_ep.status = common.Quality.compositeStatus(common.DOWNLOADED, new_ep_quality) diff --git a/sickbeard/tv.py b/sickbeard/tv.py index 7ca977f201..fe6a056474 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -294,18 +294,18 @@ def loadEpisodesFromDir(self): # see if we should save the release name in the db ep_file_name = ek.ek(os.path.basename, curEpisode.location) - ep_file_name = ek.ek(os.path.splitext, ep_file_name)[0] + ep_base_name = helpers.remove_non_release_groups(helpers.remove_extension(ep_file_name)) parse_result = None try: np = NameParser(False) - parse_result = np.parse(ep_file_name) + parse_result = np.parse(ep_base_name) except InvalidNameException: pass - if not ' ' in ep_file_name and parse_result and parse_result.release_group: - logger.log(u"Name " + ep_file_name + u" gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG) - curEpisode.release_name = ep_file_name + if not ' ' in ep_base_name and parse_result and parse_result.release_group: + logger.log(u"Name " + ep_base_name + u" gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG) + curEpisode.release_name = ep_base_name # store the reference in the show if curEpisode != None: @@ -1446,24 +1446,28 @@ def us(name): return re.sub('[ -]', '_', name) def release_name(name): - if name and name.lower().endswith('.nzb'): - name = name.rpartition('.')[0] + if name: + name = helpers.remove_non_release_groups(helpers.remove_extension(name)) return name def release_group(name): - if not name: - return '' - np = NameParser(name) + if name: + name = helpers.remove_non_release_groups(helpers.remove_extension(name)) + else: + return "" + + np = NameParser(False) try: parse_result = np.parse(name) except InvalidNameException, e: logger.log(u"Unable to get parse release_group: " + ex(e), logger.DEBUG) - return '' + return "" if not parse_result.release_group: - return '' + return "" + return parse_result.release_group epStatus, epQual = Quality.splitCompositeStatus(self.status) # @UnusedVariable From 5f7627bab66db038ac69dad46b2131700368a227 Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Sun, 4 May 2014 17:01:17 +0200 Subject: [PATCH 4/8] Fix rescan overwriting existing release_name --- sickbeard/tv.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/sickbeard/tv.py b/sickbeard/tv.py index fe6a056474..ac785f7a02 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -292,20 +292,20 @@ def loadEpisodesFromDir(self): if curEpisode is None: continue - # see if we should save the release name in the db - ep_file_name = ek.ek(os.path.basename, curEpisode.location) - ep_base_name = helpers.remove_non_release_groups(helpers.remove_extension(ep_file_name)) + if not curEpisode.release_name: + ep_file_name = ek.ek(os.path.basename, curEpisode.location) + ep_base_name = helpers.remove_non_release_groups(helpers.remove_extension(ep_file_name)) - parse_result = None - try: - np = NameParser(False) - parse_result = np.parse(ep_base_name) - except InvalidNameException: - pass - - if not ' ' in ep_base_name and parse_result and parse_result.release_group: - logger.log(u"Name " + ep_base_name + u" gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG) - curEpisode.release_name = ep_base_name + parse_result = None + try: + np = NameParser(False) + parse_result = np.parse(ep_base_name) + except InvalidNameException: + pass + + if not ' ' in ep_base_name and parse_result and parse_result.release_group: + logger.log(u"Name " + ep_base_name + u" gave release group of " + parse_result.release_group + ", seems valid", logger.DEBUG) + curEpisode.release_name = ep_base_name # store the reference in the show if curEpisode != None: From 5e152cf5a558b1991ab612d1dd69cf5f2cdb9d6d Mon Sep 17 00:00:00 2001 From: Jonathon Saine Date: Mon, 5 May 2014 23:14:13 -0500 Subject: [PATCH 5/8] Update notifiers pass 3 * Instead of calling each notifier update_library routine individually, we now just call all of them similar to the notifications * Safeguard notifiers from affecting the post-processing routine, catch and log exceptions. * Cleaned up notifier ui page wording so they follow similar pattern. * Converted updateXBMC to use show id rather than show name (works the same way as refreshShow/updateShow/etc) * Better exception handling for pyTivo and NMJ * Cleaned up logging entries, trying to standardize on what level we report and verbiage (not using contractions, prune out duplicate/excessive entries) * Added notification support for Synology (notifies Synology DSM) with migration of old setting * Dropped getURL helper reference since importing helper is more trouble than its worth, will just rewrite each notifier using Requests when we move to python 2.6+ * Add copyright NMA_Notifier --- .../default/config_notifications.tmpl | 48 +++++++++--- data/js/configNotifications.js | 11 +++ sickbeard/__init__.py | 14 +++- sickbeard/config.py | 7 ++ sickbeard/notifiers/__init__.py | 25 +++++-- sickbeard/notifiers/boxcar.py | 18 ++--- sickbeard/notifiers/growl.py | 8 +- sickbeard/notifiers/libnotify.py | 8 +- sickbeard/notifiers/nma.py | 28 +++++-- sickbeard/notifiers/nmj.py | 40 ++++++---- sickbeard/notifiers/nmjv2.py | 23 +++--- sickbeard/notifiers/plex.py | 20 +++-- sickbeard/notifiers/prowl.py | 11 +-- sickbeard/notifiers/pushover.py | 16 ++-- sickbeard/notifiers/pytivo.py | 24 +++--- sickbeard/notifiers/synoindex.py | 48 ++++++++++-- sickbeard/notifiers/trakt.py | 13 ++-- sickbeard/notifiers/tweet.py | 24 +++--- sickbeard/notifiers/xbmc.py | 73 +++++++++---------- sickbeard/postProcessor.py | 21 +----- sickbeard/webserve.py | 38 +++++++--- 21 files changed, 318 insertions(+), 200 deletions(-) diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index 24222ad182..b8c472058b 100644 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -17,7 +17,7 @@
-

Home Theater

+

Home Theater / NAS


@@ -366,17 +366,18 @@
- -

Synology Indexer

+ +

Synology

+

The Synology DiskStation NAS.

Synology Indexer is the daemon running on the Synology NAS to build its media database.

+

Synology Notifier is the notification system of Synology DSM.

-
+
+ + +
+
+ + +
+
+ + +
+
Click below to test.
+ -
+
@@ -397,14 +421,14 @@

pyTivo

-

pyTivo is both an HMO and GoBack server. This notifier will load the completed downloads to your Tivo.

+

pyTivo is both an HMO and GoBack server. This notifier will load the completed downloads to your Tivo.

@@ -678,14 +702,14 @@

Boxcar

-

Read your messages where and when you want them! A subscription will be sent if needed.

+

Universal push notification for iOS. Read your messages where and when you want them! A subscription will be sent if needed.

@@ -792,7 +816,7 @@

-

Online

+

Social


diff --git a/data/js/configNotifications.js b/data/js/configNotifications.js index a97b339cb1..c23d689c3f 100644 --- a/data/js/configNotifications.js +++ b/data/js/configNotifications.js @@ -257,4 +257,15 @@ $(document).ready(function () { $("#testNMA").attr("disabled", false); }); }); + + $("#testSynoNotify").click(function () { + $(this).attr("disabled", true); + $("#testSynoNotify-result").html(loading); + $.get(sbRoot + "/home/testSynoNotify") + .done(function (data) { + $("#testSynoNotify-result").html(data); + $("#testSynoNotify").attr("disabled", false); + }); + }); + }); diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 0b78865377..d5e330a808 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -58,7 +58,7 @@ CONFIG_FILE = None # this is the version of the config we EXPECT to find -CONFIG_VERSION = 5 +CONFIG_VERSION = 6 PROG_DIR = '.' MY_FULLNAME = None @@ -267,6 +267,9 @@ NMJ_MOUNT = None USE_SYNOINDEX = False +SYNOINDEX_NOTIFY_ONSNATCH = False +SYNOINDEX_NOTIFY_ONDOWNLOAD = False +SYNOINDEX_UPDATE_LIBRARY = False USE_NMJv2 = False NMJv2_HOST = None @@ -341,7 +344,8 @@ def initialize(consoleLogging=True): EXTRA_SCRIPTS, USE_TWITTER, TWITTER_USERNAME, TWITTER_PASSWORD, TWITTER_PREFIX, \ USE_BOXCAR, BOXCAR_USERNAME, BOXCAR_PASSWORD, BOXCAR_NOTIFY_ONDOWNLOAD, BOXCAR_NOTIFY_ONSNATCH, \ USE_PUSHOVER, PUSHOVER_USERKEY, PUSHOVER_NOTIFY_ONDOWNLOAD, PUSHOVER_NOTIFY_ONSNATCH, \ - USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, USE_SYNOINDEX, \ + USE_LIBNOTIFY, LIBNOTIFY_NOTIFY_ONSNATCH, LIBNOTIFY_NOTIFY_ONDOWNLOAD, USE_NMJ, NMJ_HOST, NMJ_DATABASE, NMJ_MOUNT, USE_NMJv2, NMJv2_HOST, NMJv2_DATABASE, NMJv2_DBLOC, \ + USE_SYNOINDEX, SYNOINDEX_NOTIFY_ONSNATCH, SYNOINDEX_NOTIFY_ONDOWNLOAD, SYNOINDEX_UPDATE_LIBRARY, \ USE_LISTVIEW, METADATA_XBMC, METADATA_XBMC_12PLUS, METADATA_MEDIABROWSER, METADATA_MEDE8ER, METADATA_PS3, metadata_provider_dict, \ GIT_PATH, MOVE_ASSOCIATED_FILES, \ COMING_EPS_LAYOUT, COMING_EPS_SORT, COMING_EPS_DISPLAY_PAUSED, METADATA_WDTV, METADATA_TIVO, IGNORE_WORDS, CREATE_MISSING_SHOW_DIRS, \ @@ -602,6 +606,9 @@ def initialize(consoleLogging=True): CheckSection(CFG, 'Synology') USE_SYNOINDEX = bool(check_setting_int(CFG, 'Synology', 'use_synoindex', 0)) + SYNOINDEX_NOTIFY_ONSNATCH = bool(check_setting_int(CFG, 'Synology', 'synoindex_notify_onsnatch', 0)) + SYNOINDEX_NOTIFY_ONDOWNLOAD = bool(check_setting_int(CFG, 'Synology', 'synoindex_notify_ondownload', 0)) + SYNOINDEX_UPDATE_LIBRARY = bool(check_setting_int(CFG, 'Synology', 'synoindex_update_library', 0)) CheckSection(CFG, 'Trakt') USE_TRAKT = bool(check_setting_int(CFG, 'Trakt', 'use_trakt', 0)) @@ -1114,6 +1121,9 @@ def save_config(): new_config['Synology'] = {} new_config['Synology']['use_synoindex'] = int(USE_SYNOINDEX) + new_config['Synology']['synoindex_notify_onsnatch'] = int(SYNOINDEX_NOTIFY_ONSNATCH) + new_config['Synology']['synoindex_notify_ondownload'] = int(SYNOINDEX_NOTIFY_ONDOWNLOAD) + new_config['Synology']['synoindex_update_library'] = int(SYNOINDEX_UPDATE_LIBRARY) new_config['NMJv2'] = {} new_config['NMJv2']['use_nmjv2'] = int(USE_NMJv2) diff --git a/sickbeard/config.py b/sickbeard/config.py index 89acbac676..ad0035747a 100644 --- a/sickbeard/config.py +++ b/sickbeard/config.py @@ -645,3 +645,10 @@ def _migrate_metadata(metadata, metadata_name, use_banner): sickbeard.METADATA_WDTV = _migrate_metadata(metadata_wdtv, 'WDTV', use_banner) sickbeard.METADATA_TIVO = _migrate_metadata(metadata_tivo, 'TIVO', use_banner) sickbeard.METADATA_MEDE8ER = _migrate_metadata(metadata_mede8er, 'Mede8er', use_banner) + + # Migration v6: Synology notifier update + def _migrate_v6(self): + """ Updates Synology notifier to reflect that their now is an update library option instead misusing the enable option """ + + # clone use_synoindex to update_library since this now has notification options + sickbeard.SYNOINDEX_UPDATE_LIBRARY = bool(check_setting_int(self.config_obj, 'Synology', 'use_synoindex', 0)) diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index cc4e769c21..c65d7c4293 100755 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -36,13 +36,15 @@ import trakt from sickbeard.common import * +from sickbeard import logger +from sickbeard.exceptions import ex -# home theater +# home theater/nas xbmc_notifier = xbmc.XBMCNotifier() plex_notifier = plex.PLEXNotifier() nmj_notifier = nmj.NMJNotifier() -synoindex_notifier = synoindex.synoIndexNotifier() nmjv2_notifier = nmjv2.NMJv2Notifier() +synoindex_notifier = synoindex.synoIndexNotifier() pytivo_notifier = pytivo.pyTivoNotifier() # devices growl_notifier = growl.GrowlNotifier() @@ -51,12 +53,12 @@ pushover_notifier = pushover.PushoverNotifier() boxcar_notifier = boxcar.BoxcarNotifier() nma_notifier = nma.NMA_Notifier() -# online +# social twitter_notifier = tweet.TwitterNotifier() trakt_notifier = trakt.TraktNotifier() notifiers = [ - libnotify_notifier, # Libnotify notifier goes first because it doesn't involve blocking on network activity. + libnotify_notifier, # Libnotify notifier goes first because it doesn't involve blocking on network activity. xbmc_notifier, plex_notifier, nmj_notifier, @@ -75,14 +77,23 @@ def notify_download(ep_name): for n in notifiers: - n.notify_download(ep_name) + try: + n.notify_download(ep_name) + except Exception, e: + logger.log(n.__class__.__name__ + ": " + ex(e), logger.ERROR) def notify_snatch(ep_name): for n in notifiers: - n.notify_snatch(ep_name) + try: + n.notify_snatch(ep_name) + except Exception, e: + logger.log(n.__class__.__name__ + ": " + ex(e), logger.ERROR) def update_library(ep_obj): for n in notifiers: - n.update_library(ep_obj) + try: + n.update_library(ep_obj=ep_obj) + except Exception, e: + logger.log(n.__class__.__name__ + ": " + ex(e), logger.ERROR) diff --git a/sickbeard/notifiers/boxcar.py b/sickbeard/notifiers/boxcar.py index 474bac4d8f..315a4f5916 100644 --- a/sickbeard/notifiers/boxcar.py +++ b/sickbeard/notifiers/boxcar.py @@ -64,7 +64,6 @@ def _sendBoxcar(self, msg, title, email, subscribe=False): # send the request to boxcar try: - # TODO: Use our getURL from helper? req = urllib2.Request(curUrl) handle = urllib2.urlopen(req, data) handle.close() @@ -72,10 +71,10 @@ def _sendBoxcar(self, msg, title, email, subscribe=False): except urllib2.URLError, e: # if we get an error back that doesn't have an error code then who knows what's really happening if not hasattr(e, 'code'): - logger.log(u"BOXCAR: Boxcar notification failed." + ex(e), logger.ERROR) + logger.log(u"BOXCAR: Notification failed." + ex(e), logger.ERROR) return False else: - logger.log(u"BOXCAR: Boxcar notification failed. Error code: " + str(e.code), logger.WARNING) + logger.log(u"BOXCAR: Notification failed. Error code: " + str(e.code), logger.ERROR) # HTTP status 404 if the provided email address isn't a Boxcar user. if e.code == 404: @@ -103,10 +102,10 @@ def _sendBoxcar(self, msg, title, email, subscribe=False): # If you receive an HTTP status code of 400, it is because you failed to send the proper parameters elif e.code == 400: - logger.log(u"BOXCAR: Wrong data send to boxcar.", logger.ERROR) + logger.log(u"BOXCAR: Wrong data sent to boxcar.", logger.ERROR) return False - logger.log(u"BOXCAR: Boxcar notification successful.", logger.DEBUG) + logger.log(u"BOXCAR: Notification successful.", logger.MESSAGE) return True def _notify(self, title, message, username=None, force=False): @@ -129,8 +128,7 @@ def _notify(self, title, message, username=None, force=False): logger.log(u"BOXCAR: Sending notification for " + message, logger.DEBUG) - self._sendBoxcar(message, title, username) - return True + return self._sendBoxcar(message, title, username) ############################################################################## # Public functions @@ -144,10 +142,10 @@ def notify_download(self, ep_name): if sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD: self._notify(notifyStrings[NOTIFY_DOWNLOAD], ep_name) - def test_notify(self, email, title="Test"): - return self._sendBoxcar("This is a test notification from SickBeard", title, email) + def test_notify(self, boxcar_username): + return self._notify("This is a test notification from Sick Beard", "Test", boxcar_username, force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = BoxcarNotifier diff --git a/sickbeard/notifiers/growl.py b/sickbeard/notifiers/growl.py index 9d93bba727..e2b09b73ba 100644 --- a/sickbeard/notifiers/growl.py +++ b/sickbeard/notifiers/growl.py @@ -115,11 +115,11 @@ def _notify(self, title="Sick Beard Notification", message=None, name=None, host print pc opts['host'] = pc[0] opts['port'] = pc[1] - logger.log(u"GROWL: Sending message '" + message + "' to " + opts['host'] + ":" + str(opts['port'])) + logger.log(u"GROWL: Sending message '" + message + "' to " + opts['host'] + ":" + str(opts['port']), logger.DEBUG) try: return self._send_growl(opts, message) except Exception, e: - logger.log(u"GROWL: Unable to send growl to " + opts['host'] + ":" + str(opts['port']) + " - " + ex(e)) + logger.log(u"GROWL: Unable to send growl to " + opts['host'] + ":" + str(opts['port']) + " - " + ex(e), logger.WARNING) return False def _sendRegistration(self, host=None, password=None, name="Sick Beard Notification"): @@ -161,7 +161,7 @@ def _sendRegistration(self, host=None, password=None, name="Sick Beard Notificat try: return self._send(opts['host'], opts['port'], register.encode(), opts['debug']) except Exception, e: - logger.log(u"GROWL: Unable to send growl to " + opts['host'] + ":" + str(opts['port']) + " - " + ex(e)) + logger.log(u"GROWL: Unable to send growl to " + opts['host'] + ":" + str(opts['port']) + " - " + ex(e), logger.WARNING) return False ############################################################################## @@ -183,7 +183,7 @@ def test_notify(self, host, password): else: return result - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = GrowlNotifier diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py index d36c8fae0a..2071c07472 100644 --- a/sickbeard/notifiers/libnotify.py +++ b/sickbeard/notifiers/libnotify.py @@ -67,15 +67,15 @@ def init_pynotify(self): try: import pynotify except ImportError: - logger.log(u"LIBNOTIFY: Unable to import pynotify. libnotify notifications won't work.") + logger.log(u"LIBNOTIFY: Unable to import pynotify. libnotify notifications won't work.", logger.ERROR) return False try: import gobject except ImportError: - logger.log(u"LIBNOTIFY: Unable to import gobject. We can't catch a GError in display.") + logger.log(u"LIBNOTIFY: Unable to import gobject. We can't catch a GError in display.", logger.ERROR) return False if not pynotify.init('Sick Beard'): - logger.log(u"LIBNOTIFY: Initialization of pynotify failed. libnotify notifications won't work.") + logger.log(u"LIBNOTIFY: Initialization of pynotify failed. libnotify notifications won't work.", logger.ERROR) return False self.pynotify = pynotify self.gobject = gobject @@ -119,7 +119,7 @@ def notify_download(self, ep_name): def test_notify(self): return self._notify("Test notification", "This is a test notification from Sick Beard", force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = LibnotifyNotifier diff --git a/sickbeard/notifiers/nma.py b/sickbeard/notifiers/nma.py index ab9e4c2d5c..84c6f73875 100644 --- a/sickbeard/notifiers/nma.py +++ b/sickbeard/notifiers/nma.py @@ -1,3 +1,21 @@ +# Author: Adam Landry +# URL: http://code.google.com/p/sickbeard/ +# +# This file is part of Sick Beard. +# +# Sick Beard 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. +# +# Sick Beard 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 Sick Beard. If not, see . + import sickbeard from sickbeard import logger, common @@ -8,7 +26,7 @@ class NMA_Notifier: def _sendNMA(self, nma_api=None, nma_priority=None, event=None, message=None, force=False): - title = "Sick-Beard" + title = "Sick Beard" # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_NMA and not force: @@ -20,10 +38,6 @@ def _sendNMA(self, nma_api=None, nma_priority=None, event=None, message=None, fo if nma_priority == None: nma_priority = sickbeard.NMA_PRIORITY - logger.log(u"NMA: title: " + title, logger.DEBUG) - logger.log(u"NMA: event: " + event, logger.DEBUG) - logger.log(u"NMA: message: " + message, logger.DEBUG) - batch = False p = pynma.PyNMA() @@ -33,12 +47,14 @@ def _sendNMA(self, nma_api=None, nma_priority=None, event=None, message=None, fo if len(keys) > 1: batch = True + logger.log("NMA: Sending notice with details: event=\"%s\", message=\"%s\", priority=%s, batch=%s" % (event, message, nma_priority, batch), logger.DEBUG) response = p.push(title, event, message, priority=nma_priority, batch_mode=batch) if not response[nma_api][u'code'] == u'200': logger.log(u"NMA: Could not send notification to NotifyMyAndroid", logger.ERROR) return False else: + logger.log(u"NMA: Notification sent to NotifyMyAndroid", logger.MESSAGE) return True ############################################################################## @@ -56,7 +72,7 @@ def notify_download(self, ep_name): def test_notify(self, nma_api, nma_priority): return self._sendNMA(nma_api, nma_priority, event="Test", message="Testing NMA settings from Sick Beard", force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = NMA_Notifier diff --git a/sickbeard/notifiers/nmj.py b/sickbeard/notifiers/nmj.py index 2171103310..8c142ada81 100644 --- a/sickbeard/notifiers/nmj.py +++ b/sickbeard/notifiers/nmj.py @@ -23,6 +23,7 @@ import re from sickbeard import logger +from sickbeard.exceptions import ex try: import xml.etree.cElementTree as etree @@ -45,7 +46,7 @@ def notify_settings(self, host): try: terminal = telnetlib.Telnet(host) except Exception: - logger.log(u"NMJ: Warning: unable to get a telnet session to %s" % (host), logger.ERROR) + logger.log(u"NMJ: Unable to get a telnet session to %s" % (host), logger.WARNING) return False # tell the terminal to output the necessary info to the screen so we can search it later @@ -67,7 +68,7 @@ def notify_settings(self, host): logger.log(u"NMJ: Found NMJ database %s on device %s" % (database, device), logger.DEBUG) sickbeard.NMJ_DATABASE = database else: - logger.log(u"NMJ: Could not get current NMJ database on %s, NMJ is probably not running!" % (host), logger.ERROR) + logger.log(u"NMJ: Could not get current NMJ database on %s, NMJ is probably not running!" % (host), logger.WARNING) return False # if the device is a remote host then try to parse the mounting URL and save it to the config @@ -79,7 +80,7 @@ def notify_settings(self, host): logger.log(u"NMJ: Found mounting url on the Popcorn Hour in configuration: %s" % (mount), logger.DEBUG) sickbeard.NMJ_MOUNT = mount else: - logger.log(u"NMJ: Detected a network share on the Popcorn Hour, but could not get the mounting url", logger.DEBUG) + logger.log(u"NMJ: Detected a network share on the Popcorn Hour, but could not get the mounting url", logger.WARNING) return False return True @@ -98,12 +99,17 @@ def _sendNMJ(self, host, database, mount=None): # if a mount URL is provided then attempt to open a handle to that URL if mount: try: - # TODO: Use our getURL from helper? req = urllib2.Request(mount) logger.log(u"NMJ: Try to mount network drive via url: %s" % (mount), logger.DEBUG) handle = urllib2.urlopen(req) except IOError, e: - logger.log(u"NMJ: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) + if hasattr(e, 'reason'): + logger.log(u"NMJ: Could not contact Popcorn Hour on host %s: %s" % (host, e.reason), logger.WARNING) + elif hasattr(e, 'code'): + logger.log(u"NMJ: Problem with Popcorn Hour on host %s: %s" % (host, e.code), logger.WARNING) + return False + except Exception, e: + logger.log(u"NMJ: Unknown exception: " + ex(e), logger.ERROR) return False # build up the request URL and parameters @@ -112,19 +118,25 @@ def _sendNMJ(self, host, database, mount=None): "arg0": "scanner_start", "arg1": database, "arg2": "background", - "arg3": ""} + "arg3": "" + } params = urllib.urlencode(params) updateUrl = UPDATE_URL % {"host": host, "params": params} # send the request to the server try: - # TODO: Use our getURL from helper? req = urllib2.Request(updateUrl) logger.log(u"NMJ: Sending NMJ scan update command via url: %s" % (updateUrl), logger.DEBUG) handle = urllib2.urlopen(req) response = handle.read() except IOError, e: - logger.log(u"NMJ: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) + if hasattr(e, 'reason'): + logger.log(u"NMJ: Could not contact Popcorn Hour on host %s: %s" % (host, e.reason), logger.WARNING) + elif hasattr(e, 'code'): + logger.log(u"NMJ: Problem with Popcorn Hour on host %s: %s" % (host, e.code), logger.WARNING) + return False + except Exception, e: + logger.log(u"NMJ: Unknown exception: " + ex(e), logger.ERROR) return False # try to parse the resulting XML @@ -137,10 +149,10 @@ def _sendNMJ(self, host, database, mount=None): # if the result was a number then consider that an error if int(result) > 0: - logger.log(u"NMJ: Popcorn Hour returned an errorcode: %s" % (result)) + logger.log(u"NMJ: Popcorn Hour returned an errorcode: %s" % (result), logger.ERROR) return False else: - logger.log(u"NMJ: Started background scan.") + logger.log(u"NMJ: Started background scan.", logger.MESSAGE) return True def _notifyNMJ(self, host=None, database=None, mount=None, force=False): @@ -176,14 +188,12 @@ def notify_snatch(self, ep_name): pass def notify_download(self, ep_name): - # TODO: Drop this and use update_library - if sickbeard.USE_NMJ: - self._notifyNMJ() + pass def test_notify(self, host, database, mount): - return self._sendNMJ(host, database, mount) + return self._notifyNMJ(host, database, mount, force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): if sickbeard.USE_NMJ: self._notifyNMJ() diff --git a/sickbeard/notifiers/nmjv2.py b/sickbeard/notifiers/nmjv2.py index 679381ae39..1128946837 100644 --- a/sickbeard/notifiers/nmjv2.py +++ b/sickbeard/notifiers/nmjv2.py @@ -44,7 +44,6 @@ def notify_settings(self, host, dbloc, instance): """ try: url_loc = "http://" + host + ":8008/file_operation?arg0=list_user_storage_file&arg1=&arg2=" + instance + "&arg3=20&arg4=true&arg5=true&arg6=true&arg7=all&arg8=name_asc&arg9=false&arg10=false" - # TODO: Use our getURL from helper? req = urllib2.Request(url_loc) handle1 = urllib2.urlopen(req) response1 = handle1.read() @@ -71,7 +70,7 @@ def notify_settings(self, host, dbloc, instance): sickbeard.NMJv2_DATABASE = DB_path return True except IOError, e: - logger.log(u"NMJv2: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) + logger.log(u"NMJv2: Could not contact Popcorn Hour on host %s: %s" % (host, e), logger.WARNING) return False return False @@ -90,9 +89,9 @@ def _sendNMJ(self, host): #if a host is provided then attempt to open a handle to that URL try: url_scandir = "http://" + host + ":8008/metadata_database?arg0=update_scandir&arg1=" + sickbeard.NMJv2_DATABASE + "&arg2=&arg3=update_all" - logger.log(u"NMJv2 scan update command send to host: %s" % (host)) + logger.log(u"NMJv2: Scan update command send to host: %s" % (host), logger.DEBUG) url_updatedb = "http://" + host + ":8008/metadata_database?arg0=scanner_start&arg1=" + sickbeard.NMJv2_DATABASE + "&arg2=background&arg3=" - logger.log(u"Try to mount network drive via url: %s" % (host), logger.DEBUG) + logger.log(u"NMJv2: Try to mount network drive via url: %s" % (host), logger.DEBUG) prereq = urllib2.Request(url_scandir) req = urllib2.Request(url_updatedb) handle1 = urllib2.urlopen(prereq) @@ -101,7 +100,7 @@ def _sendNMJ(self, host): handle2 = urllib2.urlopen(req) response2 = handle2.read() except IOError, e: - logger.log(u"NMJv2: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) + logger.log(u"NMJv2: Could not contact Popcorn Hour on host %s: %s" % (host, e), logger.WARNING) return False try: @@ -130,15 +129,15 @@ def _sendNMJ(self, host): if int(result1) > 0: index = error_codes.index(result1) - logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index])) + logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index]), logger.ERROR) return False else: if int(result2) > 0: index = error_codes.index(result2) - logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index])) + logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index]), logger.ERROR) return False else: - logger.log(u"NMJv2: Started background scan.") + logger.log(u"NMJv2: Started background scan.", logger.MESSAGE) return True def _notifyNMJ(self, host=None, force=False): @@ -170,14 +169,12 @@ def notify_snatch(self, ep_name): pass def notify_download(self, ep_name): - # TODO: Drop this and use update_library - if sickbeard.USE_NMJv2: - self._notifyNMJ() + pass def test_notify(self, host): - return self._sendNMJ(host) + return self._notifyNMJ(host, force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): if sickbeard.USE_NMJv2: self._notifyNMJ() diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py index e870a86102..18d87aa835 100644 --- a/sickbeard/notifiers/plex.py +++ b/sickbeard/notifiers/plex.py @@ -56,7 +56,7 @@ def _send_to_plex(self, command, host, username=None, password=None): password = sickbeard.PLEX_PASSWORD if not host: - logger.log(u"PLEX: No Plex host specified, check your settings", logger.DEBUG) + logger.log(u"PLEX: No host specified, check your settings", logger.ERROR) return False for key in command: @@ -64,7 +64,7 @@ def _send_to_plex(self, command, host, username=None, password=None): command[key] = command[key].encode('utf-8') enc_command = urllib.urlencode(command) - logger.log(u"PLEX: 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: @@ -74,17 +74,16 @@ def _send_to_plex(self, command, host, username=None, password=None): base64string = base64.encodestring('%s:%s' % (username, password))[:-1] authheader = "Basic %s" % base64string req.add_header("Authorization", authheader) - logger.log(u"PLEX: Contacting Plex (with auth header) via url: " + url, logger.DEBUG) + logger.log(u"PLEX: Contacting (with auth header) via url: " + url, logger.DEBUG) else: - logger.log(u"PLEX: Contacting Plex via url: " + url, logger.DEBUG) + logger.log(u"PLEX: Contacting via url: " + url, logger.DEBUG) - # TODO: Use our getURL from helper? response = urllib2.urlopen(req) result = response.read().decode(sickbeard.SYS_ENCODING) response.close() - logger.log(u"PLEX: 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' @@ -101,7 +100,7 @@ def _notify(self, message, title="Sick Beard", host=None, username=None, passwor host: Plex Media Client(s) host:port username: Plex username password: Plex password - force: Used for the Test method to override config saftey checks + force: Used for the Test method to override config safety checks Returns: Returns a list results in the format of host:ip:result @@ -123,7 +122,7 @@ def _notify(self, message, title="Sick Beard", host=None, username=None, passwor result = '' for curHost in [x.strip() for x in host.split(",")]: - logger.log(u"PLEX: Sending Plex notification to '" + curHost + "' - " + message, logger.MESSAGE) + logger.log(u"PLEX: Sending notification to '" + 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) @@ -147,7 +146,7 @@ def notify_download(self, ep_name): def test_notify(self, host, username, password): return self._notify("Testing Plex notifications from Sick Beard", "Test Notification", host, username, password, force=True) - def update_library(self): + def update_library(self, ep_obj=None): """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. @@ -159,14 +158,13 @@ def update_library(self): if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY: if not sickbeard.PLEX_SERVER_HOST: - logger.log(u"PLEX: No Plex 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: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE) url = "http://%s/library/sections" % sickbeard.PLEX_SERVER_HOST try: - # TODO: Use our getURL from helper? xml_tree = etree.parse(urllib.urlopen(url)) media_container = xml_tree.getroot() except IOError, e: diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py index 74cf9657ae..30d78ade5a 100644 --- a/sickbeard/notifiers/prowl.py +++ b/sickbeard/notifiers/prowl.py @@ -42,12 +42,7 @@ def _notify(self, prowl_api=None, prowl_priority=None, event=None, message=None, title = "Sick Beard" - # TODO: Consolidate this to one logging dict? - logger.log(u"PROWL: title: " + title, logger.DEBUG) - logger.log(u"PROWL: event: " + event, logger.DEBUG) - logger.log(u"PROWL: message: " + message, logger.DEBUG) - logger.log(u"PROWL: api: " + prowl_api, logger.DEBUG) - logger.log(u"PROWL: priority: " + prowl_priority, logger.DEBUG) + logger.log("PROWL: Sending notice with details: event=\"%s\", message=\"%s\", priority=%s, api=%s" % (event, message, prowl_priority, prowl_api), logger.DEBUG) try: @@ -74,7 +69,7 @@ def _notify(self, prowl_api=None, prowl_priority=None, event=None, message=None, return False if request_status == 200: - logger.log(u"PROWL: Notifications sent.", logger.DEBUG) + logger.log(u"PROWL: Notifications sent.", logger.MESSAGE) return True elif request_status == 401: logger.log(u"PROWL: Auth failed: %s" % response.reason, logger.ERROR) @@ -98,7 +93,7 @@ def notify_download(self, ep_name): def test_notify(self, prowl_api, prowl_priority): return self._notify(prowl_api, prowl_priority, event="Test", message="Testing Prowl settings from Sick Beard", force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = ProwlNotifier diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py index 386a194d7f..7ff796f18b 100644 --- a/sickbeard/notifiers/pushover.py +++ b/sickbeard/notifiers/pushover.py @@ -62,7 +62,6 @@ def _sendPushover(self, msg, title, userKey=None): # send the request to pushover try: - # TODO: Use our getURL from helper? req = urllib2.Request(curUrl) handle = urllib2.urlopen(req, data) handle.close() @@ -73,7 +72,7 @@ def _sendPushover(self, msg, title, userKey=None): logger.log(u"PUSHOVER: Notification failed." + ex(e), logger.ERROR) return False else: - logger.log(u"PUSHOVER: Notification failed. Error code: " + str(e.code), logger.WARNING) + logger.log(u"PUSHOVER: Notification failed. Error code: " + str(e.code), logger.ERROR) # HTTP status 404 if the provided email address isn't a Pushover user. if e.code == 404: @@ -97,10 +96,10 @@ def _sendPushover(self, msg, title, userKey=None): logger.log(u"PUSHOVER: Wrong data sent to Pushover", logger.ERROR) return False - logger.log(u"PUSHOVER: Notification successful.", logger.DEBUG) + logger.log(u"PUSHOVER: Notification successful.", logger.MESSAGE) return True - def _notify(self, title, message, userKey=None ): + def _notify(self, title, message, userKey=None, force=False): """ Sends a pushover notification based on the provided info or SB config @@ -110,7 +109,7 @@ def _notify(self, title, message, userKey=None ): """ # suppress notifications if the notifier is disabled but the notify options are checked - if not sickbeard.USE_PUSHOVER: + if not sickbeard.USE_PUSHOVER and not force: return False # fill in omitted parameters @@ -119,8 +118,7 @@ def _notify(self, title, message, userKey=None ): logger.log(u"PUSHOVER: Sending notification for " + message, logger.DEBUG) - self._sendPushover(message, title) - return True + return self._sendPushover(message, title) ############################################################################## # Public functions @@ -135,9 +133,9 @@ def notify_download(self, ep_name): self._notify(notifyStrings[NOTIFY_DOWNLOAD], ep_name) def test_notify(self, userKey=None): - return self._sendPushover("This is a test notification from SickBeard", 'Test', userKey) + return self._notify("This is a test notification from Sick Beard", "Test", userKey, force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None): pass notifier = PushoverNotifier diff --git a/sickbeard/notifiers/pytivo.py b/sickbeard/notifiers/pytivo.py index 6172fa2f3b..5c3446dd4d 100644 --- a/sickbeard/notifiers/pytivo.py +++ b/sickbeard/notifiers/pytivo.py @@ -20,9 +20,10 @@ import sickbeard from urllib import urlencode -from urllib2 import Request, urlopen, URLError +from urllib2 import Request, urlopen from sickbeard import logger +from sickbeard.exceptions import ex from sickbeard import encodingKludge as ek @@ -38,7 +39,7 @@ def notify_snatch(self, ep_name): def notify_download(self, ep_name): pass - def update_library(self, ep_obj): + def update_library(self, ep_obj=None): if not sickbeard.USE_PYTIVO: return False @@ -74,22 +75,23 @@ def update_library(self, ep_obj): # Finally create the url and make request requestUrl = "http://" + host + "/TiVoConnect?" + urlencode( {'Command': 'Push', 'Container': container, 'File': mediaFile, 'tsn': tsn}) - logger.log(u"PYTIVO: Requesting " + requestUrl) + logger.log(u"PYTIVO: Requesting " + requestUrl, logger.DEBUG) - # TODO: Use our getURL from helper? - request = Request( requestUrl ) + request = Request(requestUrl) try: response = urlopen(request) # @UnusedVariable - except URLError, e: + except IOError, e: if hasattr(e, 'reason'): - logger.log(u"PYTIVO: Error, failed to reach a server - " + str(e.reason)) - return False + logger.log(u"PYTIVO: Failed to reach server '%s' - %s" % (host, e.reason), logger.WARNING) elif hasattr(e, 'code'): - logger.log(u"PYTIVO: Error, the server couldn't fulfill the request - " + str(e.code)) - return False + logger.log(u"PYTIVO: The server could not fulfill the request '%s' - %s" % (host, e.code), logger.WARNING) + return False + except Exception, e: + logger.log(u"PYTIVO: Unknown exception: " + ex(e), logger.ERROR) + return False else: - logger.log(u"PYTIVO: Successfully requested transfer of file") + logger.log(u"PYTIVO: Successfully requested transfer of file", logger.MESSAGE) return True notifier = pyTivoNotifier diff --git a/sickbeard/notifiers/synoindex.py b/sickbeard/notifiers/synoindex.py index 3edcefd2dd..7daaea6769 100755 --- a/sickbeard/notifiers/synoindex.py +++ b/sickbeard/notifiers/synoindex.py @@ -22,6 +22,7 @@ import sickbeard from sickbeard import logger +from sickbeard.common import notifyStrings, NOTIFY_SNATCH, NOTIFY_DOWNLOAD from sickbeard import encodingKludge as ek from sickbeard.exceptions import ex @@ -37,14 +38,14 @@ def moveFile(self, old_file, new_file): def moveObject(self, old_path, new_path): if sickbeard.USE_SYNOINDEX: synoindex_cmd = ['/usr/syno/bin/synoindex', '-N', ek.ek(os.path.abspath, new_path), ek.ek(os.path.abspath, old_path)] - logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd)) + logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd), logger.DEBUG) logger.log(u"SYNOINDEX: Absolute path to command: " + ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG) try: p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR) out, err = p.communicate() # @UnusedVariable logger.log(u"SYNOINDEX: Script result: " + str(out), logger.DEBUG) except OSError, e: - logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e)) + logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e), logger.WARNING) def deleteFolder(self, cur_path): self.makeObject('-D', cur_path) @@ -61,23 +62,58 @@ def addFile(self, cur_file): def makeObject(self, cmd_arg, cur_path): if sickbeard.USE_SYNOINDEX: synoindex_cmd = ['/usr/syno/bin/synoindex', cmd_arg, ek.ek(os.path.abspath, cur_path)] - logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd)) + logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd), logger.DEBUG) logger.log(u"SYNOINDEX: Absolute path to command: " + ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG) try: p = subprocess.Popen(synoindex_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=sickbeard.PROG_DIR) out, err = p.communicate() # @UnusedVariable logger.log(u"SYNOINDEX: Script result: " + str(out), logger.DEBUG) except OSError, e: - logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e)) + logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e), logger.WARNING) + + def _notify(self, message, title, force=False): + # suppress notifications if the notifier is disabled but the notify options are checked + if not sickbeard.USE_SYNOINDEX and not force: + return False + + synodsmnotify_cmd = ['/usr/syno/bin/synodsmnotify', '@administrators', title, message] + logger.log(u"SYNOINDEX: Executing command " + str(synodsmnotify_cmd), logger.DEBUG) + + try: + p = subprocess.Popen(synodsmnotify_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + cwd=sickbeard.PROG_DIR) + + output, err = p.communicate() # @UnusedVariable + exit_status = p.returncode + + logger.log(u"SYNOINDEX: Script result: " + str(output), logger.DEBUG) + + if exit_status == 0: + return True + else: + return False + + except OSError, e: + logger.log(u"SYNOINDEX: Unable to run synodsmnotify: " + ex(e), logger.WARNING) + return False ############################################################################## # Public functions ############################################################################## def notify_snatch(self, ep_name): - pass + if sickbeard.SYNOINDEX_NOTIFY_ONSNATCH: + self._notify(notifyStrings[NOTIFY_SNATCH], ep_name) def notify_download(self, ep_name): - pass + if sickbeard.SYNOINDEX_NOTIFY_ONDOWNLOAD: + self._notify(notifyStrings[NOTIFY_DOWNLOAD], ep_name) + + def test_notify(self): + return self._notify("This is a test notification from Sick Beard", "Test", force=True) + + def update_library(self, ep_obj=None): + if sickbeard.USE_SYNOINDEX: + self.addFile(ep_obj.location) notifier = synoIndexNotifier diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py index 45cbed60f7..1d51366d17 100644 --- a/sickbeard/notifiers/trakt.py +++ b/sickbeard/notifiers/trakt.py @@ -32,7 +32,7 @@ class TraktNotifier: - def _notifyTrakt(self, method, api, username, password, data={}): + def _notifyTrakt(self, method, api, username, password, data={}, force=False): """ A generic method for communicating with trakt. Uses the method and data provided along with the auth info to send the command. @@ -44,6 +44,10 @@ def _notifyTrakt(self, method, api, username, password, data={}): Returns: A boolean representing success """ + # suppress notifications if the notifier is disabled but the notify options are checked + if not sickbeard.USE_TRAKT and not force: + return False + logger.log(u"TRAKT: Calling method " + method, logger.DEBUG) # if the API isn't given then use the config API @@ -71,7 +75,6 @@ def _notifyTrakt(self, method, api, username, password, data={}): # request the URL from trakt and parse the result as json try: logger.log(u"TRAKT: Calling method http://api.trakt.tv/" + method + ", with data" + encoded_data, logger.DEBUG) - # TODO: Use our getURL from helper? stream = urllib2.urlopen("http://api.trakt.tv/" + method, encoded_data) resp = stream.read() @@ -85,7 +88,7 @@ def _notifyTrakt(self, method, api, username, password, data={}): return False if (resp["status"] == "success"): - logger.log(u"TRAKT: Succeeded calling method. Result: " + resp["message"], logger.DEBUG) + logger.log(u"TRAKT: Succeeded calling method. Result: " + resp["message"], logger.MESSAGE) return True logger.log(u"TRAKT: Failed calling method", logger.ERROR) @@ -114,9 +117,9 @@ def test_notify(self, api, username, password): """ method = "account/test/" - return self._notifyTrakt(method, api, username, password, {}) + return self._notifyTrakt(method, api, username, password, {}, force=True) - def update_library(self, ep_obj): + def update_library(self, ep_obj=None): """ Sends a request to trakt indicating that the given episode is part of our library. diff --git a/sickbeard/notifiers/tweet.py b/sickbeard/notifiers/tweet.py index 68f2ea2d4d..b243c09a62 100644 --- a/sickbeard/notifiers/tweet.py +++ b/sickbeard/notifiers/tweet.py @@ -47,12 +47,12 @@ def _get_authorization(self): oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) oauth_client = oauth.Client(oauth_consumer) - logger.log(u'TWITTER: Requesting temp token from Twitter') + logger.log(u'TWITTER: Requesting temp token from Twitter', logger.DEBUG) resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET') if resp['status'] != '200': - logger.log(u"TWITTER: Invalid respond from Twitter requesting temp token: %s" % resp['status']) + logger.log(u"TWITTER: Invalid respond from Twitter requesting temp token: %s" % resp['status'], logger.ERROR) else: request_token = dict(parse_qsl(content)) @@ -71,26 +71,26 @@ def _get_credentials(self, key): token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(key) - logger.log(u"TWITTER: Generating and signing request for an access token using key " + key) + logger.log(u"TWITTER: Generating and signing request for an access token using key " + key, logger.DEBUG) signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() # @UnusedVariable oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) - logger.log(u"TWITTER: oauth_consumer: " + str(oauth_consumer)) + logger.log(u"TWITTER: oauth_consumer: " + str(oauth_consumer), logger.DEBUG) oauth_client = oauth.Client(oauth_consumer, token) - logger.log(u"TWITTER: oauth_client: " + str(oauth_client)) + logger.log(u"TWITTER: oauth_client: " + str(oauth_client), logger.DEBUG) resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key) - logger.log(u"TWITTER: resp, content: " + str(resp) + "," + str(content)) + logger.log(u"TWITTER: resp, content: " + str(resp) + "," + str(content), logger.DEBUG) access_token = dict(parse_qsl(content)) - logger.log(u"TWITTER: access_token: " + str(access_token)) + logger.log(u"TWITTER: access_token: " + str(access_token), logger.DEBUG) - logger.log(u"TWITTER: resp[status] = " + str(resp['status'])) + logger.log(u"TWITTER: resp[status] = " + str(resp['status']), logger.DEBUG) if resp['status'] != '200': logger.log(u"TWITTER: The request for a token with did not succeed: " + str(resp['status']), logger.ERROR) return False else: - logger.log(u"TWITTER: Your Twitter Access Token key: %s" % access_token['oauth_token']) - logger.log(u"TWITTER: Access Token secret: %s" % access_token['oauth_token_secret']) + logger.log(u"TWITTER: Your Twitter Access Token key: %s" % access_token['oauth_token'], logger.DEBUG) + logger.log(u"TWITTER: Access Token secret: %s" % access_token['oauth_token_secret'], logger.DEBUG) sickbeard.TWITTER_USERNAME = access_token['oauth_token'] sickbeard.TWITTER_PASSWORD = access_token['oauth_token_secret'] return True @@ -102,7 +102,7 @@ def _send_tweet(self, message=None): access_token_key = sickbeard.TWITTER_USERNAME access_token_secret = sickbeard.TWITTER_PASSWORD - logger.log(u"TWITTER: Sending tweet: " + message) + logger.log(u"TWITTER: Sending tweet: " + message, logger.DEBUG) api = twitter.Api(username, password, access_token_key, access_token_secret) @@ -136,7 +136,7 @@ def notify_download(self, ep_name): def test_notify(self): return self._notify("This is a test notification from Sick Beard", force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj): pass notifier = TwitterNotifier diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py index 3488a35c67..21c713803a 100644 --- a/sickbeard/notifiers/xbmc.py +++ b/sickbeard/notifiers/xbmc.py @@ -67,7 +67,7 @@ def _get_xbmc_version(self, host, username, password): 3 | (pre Eden) 4 | v11 (Eden) 5 | (pre Frodo) - 6 | v12 (Frodo) + 6 | v12 (Frodo) / v13 (Gotham) """ @@ -174,7 +174,7 @@ def _send_to_xbmc(self, command, host=None, username=None, password=None): password = sickbeard.XBMC_PASSWORD if not host: - logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) + logger.log(u"XBMC: No host specified, check your settings", logger.DEBUG) return False for key in command: @@ -182,7 +182,7 @@ def _send_to_xbmc(self, command, host=None, username=None, password=None): command[key] = command[key].encode('utf-8') enc_command = urllib.urlencode(command) - logger.log(u"XBMC: XBMC encoded API command: " + enc_command, logger.DEBUG) + logger.log(u"XBMC: Encoded API command: " + enc_command, logger.DEBUG) url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command) try: @@ -197,11 +197,11 @@ def _send_to_xbmc(self, command, host=None, username=None, password=None): result = response.read().decode(sickbeard.SYS_ENCODING) response.close() - logger.log(u"XBMC: XBMC HTTP response: " + result.replace('\n', ''), logger.DEBUG) + logger.log(u"XBMC: HTTP response: " + result.replace('\n', ''), logger.DEBUG) return result except (urllib2.URLError, IOError), e: - logger.log(u"XBMC: Warning: Couldn't contact XBMC HTTP at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) + logger.log(u"XBMC: Could not contact XBMC HTTP at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) return False def _update_library(self, host=None, showName=None): @@ -220,14 +220,12 @@ def _update_library(self, host=None, showName=None): """ if not host: - logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) + logger.log(u"XBMC: No host specified, check your settings", logger.DEBUG) return False - logger.log(u"XBMC: Updating XBMC library via HTTP method for host: " + host, logger.DEBUG) - # if we're doing per-show if showName: - logger.log(u"XBMC: Updating library in XBMC via HTTP method for show " + showName, logger.DEBUG) + logger.log(u"XBMC: Updating library via HTTP method for show " + showName, logger.MESSAGE) pathSql = 'select path.strPath from path, tvshow, tvshowlinkpath where ' \ 'tvshow.c00 = "%s" and tvshowlinkpath.idShow = tvshow.idShow ' \ @@ -268,23 +266,23 @@ def _update_library(self, host=None, showName=None): for path in paths: # we do not need it double-encoded, gawd this is dumb unEncPath = urllib.unquote(path.text).decode(sickbeard.SYS_ENCODING) - logger.log(u"XBMC: XBMC Updating " + showName + " on " + host + " at " + unEncPath, logger.MESSAGE) + logger.log(u"XBMC: Updating " + showName + " on " + host + " at " + unEncPath, logger.MESSAGE) updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video, %s)' % (unEncPath)} request = self._send_to_xbmc(updateCommand, host) if not request: - logger.log(u"XBMC: Update of show directory failed on " + showName + " on " + host + " at " + unEncPath, logger.ERROR) + logger.log(u"XBMC: Update of show directory failed on " + showName + " on " + host + " at " + unEncPath, logger.WARNING) return False # sleep for a few seconds just to be sure xbmc has a chance to finish each directory if len(paths) > 1: time.sleep(5) # do a full update if requested else: - logger.log(u"XBMC: Doing Full Library XBMC update on host: " + host, logger.DEBUG) + logger.log(u"XBMC: Doing Full Library update via HTTP method for host: " + host, logger.MESSAGE) updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'} request = self._send_to_xbmc(updateCommand, host) if not request: - logger.log(u"XBMC: XBMC Full Library update failed on: " + host, logger.ERROR) + logger.log(u"XBMC: Full Library update failed on: " + host, logger.ERROR) return False return True @@ -314,11 +312,11 @@ def _send_to_xbmc_json(self, command, host=None, username=None, password=None): password = sickbeard.XBMC_PASSWORD if not host: - logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) + logger.log(u"XBMC: No host specified, check your settings", logger.DEBUG) return False command = command.encode('utf-8') - logger.log(u"XBMC: XBMC JSON command: " + command, logger.DEBUG) + logger.log(u"XBMC: JSON command: " + command, logger.DEBUG) url = 'http://%s/jsonrpc' % (host) try: @@ -340,14 +338,14 @@ def _send_to_xbmc_json(self, command, host=None, username=None, password=None): try: result = json.load(response) response.close() - logger.log(u"XBMC: XBMC JSON response: " + str(result), logger.DEBUG) + logger.log(u"XBMC: JSON response: " + str(result), logger.DEBUG) return result # need to return response for parsing except ValueError, e: logger.log(u"XBMC: Unable to decode JSON: " + response, logger.WARNING) return False except IOError, e: - logger.log(u"XBMC: Warning: Couldn't contact XBMC JSON API at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) + logger.log(u"XBMC: Could not contact XBMC JSON API at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) return False def _update_library_json(self, host=None, showName=None): @@ -366,15 +364,13 @@ def _update_library_json(self, host=None, showName=None): """ if not host: - logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) + logger.log(u"XBMC: No host specified, check your settings", logger.DEBUG) return False - logger.log(u"XBMC: Updating XBMC library via JSON method for host: " + host, logger.MESSAGE) - # if we're doing per-show if showName: tvshowid = -1 - logger.log(u"XBMC: Updating library in XBMC via JSON method for show " + showName, logger.DEBUG) + logger.log(u"XBMC: Updating library via JSON method for show " + showName, logger.MESSAGE) # get tvshowid by showName showsCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}' @@ -410,11 +406,11 @@ def _update_library_json(self, host=None, showName=None): logger.log(u"XBMC: No valid path found for " + showName + " with ID: " + str(tvshowid) + " on " + host, logger.WARNING) return False - logger.log(u"XBMC: XBMC Updating " + showName + " on " + host + " at " + path, logger.MESSAGE) + logger.log(u"XBMC: Updating " + showName + " on " + host + " at " + path, logger.MESSAGE) updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","params":{"directory":%s},"id":1}' % (json.dumps(path)) request = self._send_to_xbmc_json(updateCommand, host) if not request: - logger.log(u"XBMC: Update of show directory failed on " + showName + " on " + host + " at " + path, logger.ERROR) + logger.log(u"XBMC: Update of show directory failed on " + showName + " on " + host + " at " + path, logger.WARNING) return False # catch if there was an error in the returned request @@ -425,12 +421,12 @@ def _update_library_json(self, host=None, showName=None): # do a full update if requested else: - logger.log(u"XBMC: Doing Full Library XBMC update on host: " + host, logger.MESSAGE) + logger.log(u"XBMC: Doing Full Library update via JSON method for host: " + host, logger.MESSAGE) updateCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.Scan","id":1}' request = self._send_to_xbmc_json(updateCommand, host, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD) if not request: - logger.log(u"XBMC: XBMC Full Library update failed on: " + host, logger.ERROR) + logger.log(u"XBMC: Full Library update failed on: " + host, logger.ERROR) return False return True @@ -450,7 +446,7 @@ def notify_download(self, ep_name): def test_notify(self, host, username, password): return self._notify("Testing XBMC notifications from Sick Beard", "Test Notification", host, username, password, force=True) - def update_library(self, showName=None): + def update_library(self, ep_obj=None, show_obj=None): """Public wrapper for the update library functions to branch the logic for JSON-RPC or legacy HTTP API Checks the XBMC API version to branch the logic to call either the legacy HTTP API or the newer JSON-RPC over HTTP methods. @@ -466,9 +462,16 @@ def update_library(self, showName=None): """ + if ep_obj: + showName = ep_obj.show.name + elif show_obj: + showName = show_obj.name + else: + showName = None + if sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY: if not sickbeard.XBMC_HOST: - logger.log(u"XBMC: No XBMC hosts specified, check your settings", logger.DEBUG) + logger.log(u"XBMC: No host specified, check your settings", logger.DEBUG) return False if sickbeard.XBMC_UPDATE_ONLYFIRST: @@ -479,26 +482,20 @@ def update_library(self, showName=None): result = 0 for curHost in [x.strip() for x in host.split(",")]: - logger.log(u"XBMC: Sending request to update library for XBMC host: '" + curHost + "'", logger.MESSAGE) + logger.log(u"XBMC: Sending request to update library for host: '" + curHost + "'", logger.MESSAGE) xbmcapi = self._get_xbmc_version(curHost, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD) if xbmcapi: if (xbmcapi <= 4): # try to update for just the show, if it fails, do full update if enabled if not self._update_library(curHost, showName): - if showName: - logger.log(u"XBMC: XBMC single show update failed", logger.WARNING) - if sickbeard.XBMC_UPDATE_FULL: - logger.log(u"XBMC: Falling back to XBMC full update") - self._update_library(curHost) + if showName and sickbeard.XBMC_UPDATE_FULL: + self._update_library(curHost) else: # try to update for just the show, if it fails, do full update if enabled if not self._update_library_json(curHost, showName): - if showName: - logger.log(u"XBMC: XBMC single show update failed", logger.WARNING) - if sickbeard.XBMC_UPDATE_FULL: - logger.log(u"XBMC: Falling back to XBMC full update") - self._update_library_json(curHost) + if showName and sickbeard.XBMC_UPDATE_FULL: + self._update_library_json(curHost) else: if sickbeard.XBMC_ALWAYS_ON: logger.log(u"XBMC: Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 1ce5885c80..6223864065 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -883,30 +883,15 @@ def process(self): # log it to history history.logDownload(ep_obj, self.file_path, new_ep_quality, self.release_group) - # send notifications + # send notifiers download notification notifiers.notify_download(ep_obj.prettyName()) # generate nfo/tbn ep_obj.createMetaFiles() ep_obj.saveToDB() - # 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() - - # do the library update for NMJ - # nmj_notifier kicks off its library update when the notify_download is issued (inside notifiers) - - # do the library update for Synology Indexer - notifiers.synoindex_notifier.addFile(ep_obj.location) - - # do the library update for pyTivo - notifiers.pytivo_notifier.update_library(ep_obj) - - # do the library update for Trakt - notifiers.trakt_notifier.update_library(ep_obj) + # send notifiers library update + notifiers.update_library(ep_obj) self._run_extra_scripts(ep_obj) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 3f293107bc..a661961fe9 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1089,26 +1089,28 @@ def index(self): return _munge(t) @cherrypy.expose - def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=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_xbmc=None, xbmc_always_on=None, xbmc_notify_onsnatch=None, xbmc_notify_ondownload=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_update_library=None, - plex_server_host=None, plex_host=None, plex_username=None, plex_password=None, + plex_server_host=None, plex_host=None, plex_username=None, plex_password=None, use_growl=None, growl_notify_onsnatch=None, growl_notify_ondownload=None, growl_host=None, growl_password=None, use_prowl=None, prowl_notify_onsnatch=None, prowl_notify_ondownload=None, prowl_api=None, prowl_priority=0, use_twitter=None, twitter_notify_onsnatch=None, twitter_notify_ondownload=None, use_boxcar=None, boxcar_notify_onsnatch=None, boxcar_notify_ondownload=None, boxcar_username=None, use_pushover=None, pushover_notify_onsnatch=None, pushover_notify_ondownload=None, pushover_userkey=None, use_libnotify=None, libnotify_notify_onsnatch=None, libnotify_notify_ondownload=None, - use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, use_synoindex=None, + use_nmj=None, nmj_host=None, nmj_database=None, nmj_mount=None, + use_synoindex=None, synoindex_notify_onsnatch=None, synoindex_notify_ondownload=None, synoindex_update_library=None, use_nmjv2=None, nmjv2_host=None, nmjv2_dbloc=None, nmjv2_database=None, use_trakt=None, trakt_username=None, trakt_password=None, trakt_api=None, use_pytivo=None, pytivo_notify_onsnatch=None, pytivo_notify_ondownload=None, pytivo_update_library=None, - pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None, + pytivo_host=None, pytivo_share_name=None, pytivo_tivo_name=None, use_nma=None, nma_notify_onsnatch=None, nma_notify_ondownload=None, nma_api=None, nma_priority=0): results = [] - # Home Theater + # Home Theater / NAS 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) @@ -1140,6 +1142,9 @@ def saveNotifications(self, use_xbmc=None, xbmc_always_on=None, xbmc_notify_onsn sickbeard.NMJv2_DBLOC = nmjv2_dbloc sickbeard.USE_SYNOINDEX = config.checkbox_to_value(use_synoindex) + sickbeard.SYNOINDEX_NOTIFY_ONSNATCH = config.checkbox_to_value(synoindex_notify_onsnatch) + sickbeard.SYNOINDEX_NOTIFY_ONDOWNLOAD = config.checkbox_to_value(synoindex_notify_ondownload) + sickbeard.SYNOINDEX_UPDATE_LIBRARY = config.checkbox_to_value(synoindex_update_library) sickbeard.USE_PYTIVO = config.checkbox_to_value(use_pytivo) # sickbeard.PYTIVO_NOTIFY_ONSNATCH = config.checkbox_to_value(pytivo_notify_onsnatch) @@ -2029,6 +2034,16 @@ def testNMA(self, nma_api=None, nma_priority=0): else: return "Test NMA notice failed" + @cherrypy.expose + def testSynoNotify(self): + cherrypy.response.headers['Cache-Control'] = "max-age=0,no-cache,no-store" + + result = notifiers.synoindex_notifier.test_notify() + if result: + return "Test Synology notice sent successfully" + else: + return "Test Synology notice failed" + @cherrypy.expose def shutdown(self, pid=None): @@ -2125,7 +2140,7 @@ def displayShow(self, show=None): t.submenu.append({ 'title': 'Delete', 'path': 'home/deleteShow?show=%d' % showObj.tvdbid, 'confirm': True }) t.submenu.append({ 'title': 'Re-scan files', 'path': 'home/refreshShow?show=%d' % showObj.tvdbid }) t.submenu.append({ 'title': 'Force Full Update', 'path': 'home/updateShow?show=%d&force=1' % showObj.tvdbid }) - t.submenu.append({ 'title': 'Update show in XBMC', 'path': 'home/updateXBMC?showName=%s' % urllib.quote_plus(showObj.name.encode('utf-8')), 'requires': haveXBMC }) + t.submenu.append({ 'title': 'Update show in XBMC', 'path': 'home/updateXBMC?show=%d' % showObj.tvdbid, 'requires': haveXBMC }) t.submenu.append({ 'title': 'Preview Rename', 'path': 'home/testRename?show=%d' % showObj.tvdbid }) t.show = showObj @@ -2343,14 +2358,19 @@ def updateShow(self, show=None, force=0): redirect("/home/displayShow?show=" + str(showObj.tvdbid)) @cherrypy.expose - def updateXBMC(self, showName=None): + def updateXBMC(self, show=None): if sickbeard.XBMC_UPDATE_ONLYFIRST: # only send update to first host in the list -- workaround for xbmc sql backend users host = sickbeard.XBMC_HOST.split(",")[0].strip() else: host = sickbeard.XBMC_HOST - if notifiers.xbmc_notifier.update_library(showName=showName): + if show: + show_obj = sickbeard.helpers.findCertainShow(sickbeard.showList, int(show)) + else: + show_obj = None + + if notifiers.xbmc_notifier.update_library(show_obj=show_obj): ui.notifications.message("Library update command sent to XBMC host(s): " + host) else: ui.notifications.error("Unable to contact one or more XBMC host(s): " + host) From 84cb03185c798ea20cfb0f00dc5d5a64052f84c3 Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Mon, 5 May 2014 11:18:07 +0200 Subject: [PATCH 6/8] Change Roman numerals conversion (PR-589) --- sickbeard/name_parser/parser.py | 69 ++++++++++++++++----------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/sickbeard/name_parser/parser.py b/sickbeard/name_parser/parser.py index 2812dbd7e8..211963d224 100644 --- a/sickbeard/name_parser/parser.py +++ b/sickbeard/name_parser/parser.py @@ -26,6 +26,7 @@ from sickbeard import logger + class NameParser(object): def __init__(self, file_name=True): @@ -165,42 +166,38 @@ def _unicodify(self, obj, encoding = "utf-8"): obj = unicode(obj, encoding) return obj - def _convert_number(self, number): - if type(number) == int: - return number - - # good lord I'm lazy - if number.lower() == 'i': return 1 - if number.lower() == 'ii': return 2 - if number.lower() == 'iii': return 3 - if number.lower() == 'iv': return 4 - if number.lower() == 'v': return 5 - if number.lower() == 'vi': return 6 - if number.lower() == 'vii': return 7 - if number.lower() == 'viii': return 8 - if number.lower() == 'ix': return 9 - if number.lower() == 'x': return 10 - if number.lower() == 'xi': return 11 - if number.lower() == 'xii': return 12 - if number.lower() == 'xiii': return 13 - if number.lower() == 'xiv': return 14 - if number.lower() == 'xv': return 15 - if number.lower() == 'xvi': return 16 - if number.lower() == 'xvii': return 17 - if number.lower() == 'xviii': return 18 - if number.lower() == 'xix': return 19 - if number.lower() == 'xx': return 20 - if number.lower() == 'xxi': return 21 - if number.lower() == 'xxii': return 22 - if number.lower() == 'xxiii': return 23 - if number.lower() == 'xxiv': return 24 - if number.lower() == 'xxv': return 25 - if number.lower() == 'xxvi': return 26 - if number.lower() == 'xxvii': return 27 - if number.lower() == 'xxviii': return 28 - if number.lower() == 'xxix': return 29 - - return int(number) + def _convert_number(self, org_number): + """ + Convert org_number into an integer + org_number: integer or representation of a number: string or unicode + Try force converting to int first, on error try converting from Roman numerals + returns integer or 0 + """ + + try: + # try forcing to int + if org_number: + number = int(org_number) + else: + number = 0 + + except: + # on error try converting from Roman numerals + roman_to_int_map = (('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), + ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), + ('IX', 9), ('V', 5), ('IV', 4), ('I', 1) + ) + + roman_numeral = str(org_number).upper() + number = 0 + index = 0 + + for numeral, integer in roman_to_int_map: + while roman_numeral[index:index + len(numeral)] == numeral: + number += integer + index += len(numeral) + + return number def parse(self, name): From d630b2b798653c410e3e2cf5336496073b9e71f1 Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Sat, 10 May 2014 12:41:18 +0200 Subject: [PATCH 7/8] Change Scheduler runImmediately to run_delay --- sickbeard/__init__.py | 80 ++++++++++++++++++++++++------------------ sickbeard/scheduler.py | 14 ++++---- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 0b78865377..8b78341735 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -665,55 +665,67 @@ def initialize(consoleLogging=True): newznabProviderList = providers.getNewznabProviderList(NEWZNAB_DATA) providerList = providers.makeProviderList() - # initialize schedulars - currentSearchScheduler = scheduler.Scheduler(searchCurrent.CurrentSearcher(), - cycleTime=datetime.timedelta(minutes=SEARCH_FREQUENCY), - threadName="SEARCH", - runImmediately=True) - - # the interval for this is stored inside the ShowUpdater class - showUpdaterInstance = showUpdater.ShowUpdater() - showUpdateScheduler = scheduler.Scheduler(showUpdaterInstance, - cycleTime=showUpdaterInstance.updateInterval, - threadName="SHOWUPDATER", - runImmediately=False) + # initialize schedulers + # updaters versionCheckScheduler = scheduler.Scheduler(versionChecker.CheckVersion(), - cycleTime=datetime.timedelta(hours=12), - threadName="CHECKVERSION", - runImmediately=True) + cycleTime=datetime.timedelta(hours=12), + threadName="CHECKVERSION" + ) showQueueScheduler = scheduler.Scheduler(show_queue.ShowQueue(), - cycleTime=datetime.timedelta(seconds=3), - threadName="SHOWQUEUE", - silent=True) + cycleTime=datetime.timedelta(seconds=3), + threadName="SHOWQUEUE", + silent=True) + + showUpdaterInstance = showUpdater.ShowUpdater() # the interval for this is stored inside the class + showUpdateScheduler = scheduler.Scheduler(showUpdaterInstance, + cycleTime=showUpdaterInstance.updateInterval, + threadName="SHOWUPDATER", + run_delay=showUpdaterInstance.updateInterval + ) + # searchers searchQueueScheduler = scheduler.Scheduler(search_queue.SearchQueue(), - cycleTime=datetime.timedelta(seconds=3), - threadName="SEARCHQUEUE", - silent=True) + cycleTime=datetime.timedelta(seconds=3), + threadName="SEARCHQUEUE", + silent=True + ) + + currentSearchScheduler = scheduler.Scheduler(searchCurrent.CurrentSearcher(), + cycleTime=datetime.timedelta(minutes=SEARCH_FREQUENCY), + threadName="SEARCH", + run_delay=datetime.timedelta(minutes=5) + ) + + backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(), + cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()), + threadName="BACKLOG", + run_delay=datetime.timedelta(minutes=17) + ) - properFinderInstance = properFinder.ProperFinder() + backlogSearchScheduler.action.cycleTime = BACKLOG_SEARCH_FREQUENCY + + properFinderInstance = properFinder.ProperFinder() # the interval for this is stored inside the class properFinderScheduler = scheduler.Scheduler(properFinderInstance, - cycleTime=properFinderInstance.updateInterval, - threadName="FINDPROPERS", - runImmediately=False) + cycleTime=properFinderInstance.updateInterval, + threadName="FINDPROPERS", + run_delay=properFinderInstance.updateInterval + ) + if not DOWNLOAD_PROPERS: properFinderScheduler.silent = True + # processors autoPostProcesserScheduler = scheduler.Scheduler(autoPostProcesser.PostProcesser(), - cycleTime=datetime.timedelta(minutes=10), - threadName="POSTPROCESSER", - runImmediately=True) + cycleTime=datetime.timedelta(minutes=10), + threadName="POSTPROCESSER", + run_delay=datetime.timedelta(minutes=5) + ) + if not PROCESS_AUTOMATICALLY: autoPostProcesserScheduler.silent = True - backlogSearchScheduler = searchBacklog.BacklogSearchScheduler(searchBacklog.BacklogSearcher(), - cycleTime=datetime.timedelta(minutes=get_backlog_cycle_time()), - threadName="BACKLOG", - runImmediately=True) - backlogSearchScheduler.action.cycleTime = BACKLOG_SEARCH_FREQUENCY - showList = [] loadingShowList = {} diff --git a/sickbeard/scheduler.py b/sickbeard/scheduler.py index a0973d5209..e7816f9859 100644 --- a/sickbeard/scheduler.py +++ b/sickbeard/scheduler.py @@ -24,14 +24,12 @@ from sickbeard import logger from sickbeard.exceptions import ex + class Scheduler: - def __init__(self, action, cycleTime=datetime.timedelta(minutes=10), runImmediately=True, threadName="ScheduledThread", silent=False): + def __init__(self, action, cycleTime=datetime.timedelta(minutes=10), run_delay=datetime.timedelta(minutes=0), threadName="ScheduledThread", silent=False): - if runImmediately: - self.lastRun = datetime.datetime.fromordinal(1) - else: - self.lastRun = datetime.datetime.now() + self.lastRun = datetime.datetime.now() + run_delay - cycleTime self.action = action self.cycleTime = cycleTime @@ -63,14 +61,14 @@ def runAction(self): currentTime = datetime.datetime.now() - if currentTime - self.lastRun > self.cycleTime: + if currentTime - self.lastRun >= self.cycleTime: self.lastRun = currentTime try: if not self.silent: - logger.log(u"Starting new thread: "+self.threadName, logger.DEBUG) + logger.log(u"Starting new thread: " + self.threadName, logger.DEBUG) self.action.run() except Exception, e: - logger.log(u"Exception generated in thread "+self.threadName+": " + ex(e), logger.ERROR) + logger.log(u"Exception generated in thread " + self.threadName + ": " + ex(e), logger.ERROR) logger.log(repr(traceback.format_exc()), logger.DEBUG) if self.abort: From d1d3b10597f284af6397091d3686f874341128bf Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Sat, 10 May 2014 12:49:25 +0200 Subject: [PATCH 8/8] Change proper finder and show updater to silent thread logging Only log when the processes actually run --- sickbeard/__init__.py | 9 ++++----- sickbeard/properFinder.py | 3 --- sickbeard/showUpdater.py | 3 --- sickbeard/webserve.py | 5 ----- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/sickbeard/__init__.py b/sickbeard/__init__.py index 8b78341735..2bcd0bd01e 100644 --- a/sickbeard/__init__.py +++ b/sickbeard/__init__.py @@ -682,7 +682,8 @@ def initialize(consoleLogging=True): showUpdateScheduler = scheduler.Scheduler(showUpdaterInstance, cycleTime=showUpdaterInstance.updateInterval, threadName="SHOWUPDATER", - run_delay=showUpdaterInstance.updateInterval + run_delay=showUpdaterInstance.updateInterval, + silent=True ) # searchers @@ -710,12 +711,10 @@ def initialize(consoleLogging=True): properFinderScheduler = scheduler.Scheduler(properFinderInstance, cycleTime=properFinderInstance.updateInterval, threadName="FINDPROPERS", - run_delay=properFinderInstance.updateInterval + run_delay=properFinderInstance.updateInterval, + silent=True ) - if not DOWNLOAD_PROPERS: - properFinderScheduler.silent = True - # processors autoPostProcesserScheduler = scheduler.Scheduler(autoPostProcesser.PostProcesser(), cycleTime=datetime.timedelta(minutes=10), diff --git a/sickbeard/properFinder.py b/sickbeard/properFinder.py index 37785358a3..1a8d041908 100644 --- a/sickbeard/properFinder.py +++ b/sickbeard/properFinder.py @@ -48,9 +48,6 @@ def run(self): # look for propers every night at 1 AM updateTime = datetime.time(hour=1) - - logger.log(u"Checking proper time", logger.DEBUG) - hourDiff = datetime.datetime.today().time().hour - updateTime.hour # if it's less than an interval after the update time then do an update diff --git a/sickbeard/showUpdater.py b/sickbeard/showUpdater.py index 8e0324b519..0e884dd339 100644 --- a/sickbeard/showUpdater.py +++ b/sickbeard/showUpdater.py @@ -41,9 +41,6 @@ def run(self, force=False): update_datetime = datetime.datetime.today() update_date = update_datetime.date() - - logger.log(u"Checking update interval", logger.DEBUG) - hour_diff = update_datetime.time().hour - run_updater_time.hour # if it's less than an interval after the update time then do an update (or if we're forcing it) diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 3f293107bc..37c502ef00 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -744,11 +744,6 @@ def saveSearch(self, use_nzbs=None, use_torrents=None, nzb_dir=None, sab_usernam # Episode Search sickbeard.DOWNLOAD_PROPERS = config.checkbox_to_value(download_propers) - if sickbeard.DOWNLOAD_PROPERS: - sickbeard.properFinderScheduler.silent = False - else: - sickbeard.properFinderScheduler.silent = True - config.change_SEARCH_FREQUENCY(search_frequency) sickbeard.USENET_RETENTION = config.to_int(usenet_retention, default=500)