From 28e72ce994a82a6a448ff5e31f4e0d989a842de0 Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Tue, 18 Mar 2014 22:11:01 +0100 Subject: [PATCH 01/39] Fix mixed line endings --- sickbeard/gh_api.py | 56 +-- sickbeard/versionChecker.py | 838 ++++++++++++++++++------------------ 2 files changed, 447 insertions(+), 447 deletions(-) diff --git a/sickbeard/gh_api.py b/sickbeard/gh_api.py index 1e4539bbd6..27b2dd6d7e 100644 --- a/sickbeard/gh_api.py +++ b/sickbeard/gh_api.py @@ -21,53 +21,53 @@ except ImportError: from lib import simplejson as json -import helpers - +import helpers + class GitHub(object): """ Simple api wrapper for the Github API v3. Currently only supports the small thing that SB - needs it for - list of commits. + needs it for - list of commits. """ - - def __init__(self, github_repo_user, github_repo, branch='master'): - - self.github_repo_user = github_repo_user - self.github_repo = github_repo - self.branch = branch - + + def __init__(self, github_repo_user, github_repo, branch='master'): + + self.github_repo_user = github_repo_user + self.github_repo = github_repo + self.branch = branch + def _access_API(self, path, params=None): """ Access the API at the path given and with the optional params given. - + path: A list of the path elements to use (eg. ['repos', 'midgetspy', 'Sick-Beard', 'commits']) params: Optional dict of name/value pairs for extra params to send. (eg. {'per_page': 10}) - + Returns a deserialized json object of the result. Doesn't do any error checking (hope it works). """ - + url = 'https://api.github.com/' + '/'.join(path) - + if params and type(params) is dict: url += '?' + '&'.join([str(x) + '=' + str(params[x]) for x in params.keys()]) - - data = helpers.getURL(url) - - if data: - json_data = json.loads(data) - return json_data - else: - return [] - - def commits(self): + + data = helpers.getURL(url) + + if data: + json_data = json.loads(data) + return json_data + else: + return [] + + def commits(self): """ Uses the API to get a list of the 100 most recent commits from the specified user/repo/branch, starting from HEAD. - + user: The github username of the person whose repo you're querying repo: The repo name to query branch: Optional, the branch name to show commits from - + Returns a deserialized json object containing the commit info. See http://developer.github.com/v3/repos/commits/ """ - access_API = self._access_API(['repos', self.github_repo_user, self.github_repo, 'commits'], {'per_page': 100, 'sha': self.branch}) - return access_API + access_API = self._access_API(['repos', self.github_repo_user, self.github_repo, 'commits'], {'per_page': 100, 'sha': self.branch}) + return access_API diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index 1123388b52..aa49719cc6 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -19,27 +19,27 @@ from __future__ import with_statement import sickbeard -from sickbeard import helpers +from sickbeard import helpers from sickbeard import version, ui from sickbeard import logger from sickbeard import scene_exceptions from sickbeard.exceptions import ex -from sickbeard import encodingKludge as ek - -import os -import platform -import shutil -import subprocess -import re - -import urllib - -import zipfile -import tarfile +from sickbeard import encodingKludge as ek + +import os +import platform +import shutil +import subprocess +import re + +import urllib + +import zipfile +import tarfile import gh_api as github - + class CheckVersion(): """ Version check class meant to run as a thread object with the SB scheduler. @@ -59,25 +59,25 @@ def __init__(self): def run(self): self.check_for_new_version() - + # refresh scene exceptions too scene_exceptions.retrieve_exceptions() def find_install_type(self): """ Determines how this copy of SB was installed. - + returns: type of installation. Possible values are: 'win': any compiled windows build 'git': running from source using git 'source': running from source without git - """ - - # check if we're a windows build - if sickbeard.version.SICKBEARD_VERSION.startswith('build '): - install_type = 'win' - elif os.path.isdir(ek.ek(os.path.join, sickbeard.PROG_DIR, u'.git')): - install_type = 'git' + """ + + # check if we're a windows build + if sickbeard.version.SICKBEARD_VERSION.startswith('build '): + install_type = 'win' + elif os.path.isdir(ek.ek(os.path.join, sickbeard.PROG_DIR, u'.git')): + install_type = 'git' else: install_type = 'source' @@ -86,9 +86,9 @@ def find_install_type(self): def check_for_new_version(self, force=False): """ Checks the internet for a newer version. - + returns: bool, True for new version or False for no new version. - + force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced """ @@ -96,11 +96,11 @@ def check_for_new_version(self, force=False): logger.log(u"Version checking is disabled, not checking for the newest version") return False - logger.log(u"Checking if " + self.install_type + " needs an update") + logger.log(u"Checking if " + self.install_type + " needs an update") if not self.updater.need_update(): - sickbeard.NEWEST_VERSION_STRING = None + sickbeard.NEWEST_VERSION_STRING = None logger.log(u"No update needed") - + if force: ui.notifications.message('No update needed') return False @@ -112,40 +112,40 @@ def update(self): if self.updater.need_update(): return self.updater.update() - + class UpdateManager(): - - def get_github_repo_user(self): - return 'midgetspy' - - def get_github_repo(self): - return 'Sick-Beard' - - def get_update_url(self): - return sickbeard.WEB_ROOT + "/home/update/?pid=" + str(sickbeard.PID) - + + def get_github_repo_user(self): + return 'midgetspy' + + def get_github_repo(self): + return 'Sick-Beard' + + def get_update_url(self): + return sickbeard.WEB_ROOT + "/home/update/?pid=" + str(sickbeard.PID) + class WindowsUpdateManager(UpdateManager): def __init__(self): - self.github_repo_user = self.get_github_repo_user() - self.github_repo = self.get_github_repo() - self.branch = 'windows_binaries' - + self.github_repo_user = self.get_github_repo_user() + self.github_repo = self.get_github_repo() + self.branch = 'windows_binaries' + self._cur_version = None - self._cur_commit_hash = None + self._cur_commit_hash = None self._newest_version = None self.gc_url = 'http://code.google.com/p/sickbeard/downloads/list' - self.version_url = 'https://raw.github.com/' + self.github_repo_user + '/' + self.github_repo + '/' + self.branch + '/updates.txt' + self.version_url = 'https://raw.github.com/' + self.github_repo_user + '/' + self.github_repo + '/' + self.branch + '/updates.txt' def _find_installed_version(self): - try: - version = sickbeard.version.SICKBEARD_VERSION - return int(version[6:]) - except ValueError: - logger.log(u"Unknown SickBeard Windows binary release: " + version, logger.ERROR) - return None + try: + version = sickbeard.version.SICKBEARD_VERSION + return int(version[6:]) + except ValueError: + logger.log(u"Unknown SickBeard Windows binary release: " + version, logger.ERROR) + return None def _find_newest_version(self, whole_link=False): """ @@ -158,20 +158,20 @@ def _find_newest_version(self, whole_link=False): regex = ".*SickBeard\-win32\-alpha\-build(\d+)(?:\.\d+)?\.zip" - version_url_data = helpers.getURL(self.version_url) - - if version_url_data is None: - return None - else: - for curLine in version_url_data.splitlines(): - logger.log(u"checking line " + curLine, logger.DEBUG) - match = re.match(regex, curLine) - if match: - logger.log(u"found a match", logger.DEBUG) - if whole_link: - return curLine.strip() - else: - return int(match.group(1)) + version_url_data = helpers.getURL(self.version_url) + + if version_url_data is None: + return None + else: + for curLine in version_url_data.splitlines(): + logger.log(u"checking line " + curLine, logger.DEBUG) + match = re.match(regex, curLine) + if match: + logger.log(u"found a match", logger.DEBUG) + if whole_link: + return curLine.strip() + else: + return int(match.group(1)) return None @@ -179,191 +179,191 @@ def need_update(self): self._cur_version = self._find_installed_version() self._newest_version = self._find_newest_version() - logger.log(u"newest version: " + repr(self._newest_version), logger.DEBUG) + logger.log(u"newest version: " + repr(self._newest_version), logger.DEBUG) if self._newest_version and self._newest_version > self._cur_version: return True def set_newest_text(self): - sickbeard.NEWEST_VERSION_STRING = None - - if not self._cur_version: - newest_text = "Unknown SickBeard Windows binary version. Not updating with original version." - else: - newest_text = 'There is a newer version available (build ' + str(self._newest_version) + ')' - newest_text += "— Update Now" - - sickbeard.NEWEST_VERSION_STRING = newest_text + sickbeard.NEWEST_VERSION_STRING = None + + if not self._cur_version: + newest_text = "Unknown SickBeard Windows binary version. Not updating with original version." + else: + newest_text = 'There is a newer version available (build ' + str(self._newest_version) + ')' + newest_text += "— Update Now" + + sickbeard.NEWEST_VERSION_STRING = newest_text - def update(self): + def update(self): - zip_download_url = self._find_newest_version(True) - logger.log(u"new_link: " + repr(zip_download_url), logger.DEBUG) + zip_download_url = self._find_newest_version(True) + logger.log(u"new_link: " + repr(zip_download_url), logger.DEBUG) - if not zip_download_url: + if not zip_download_url: logger.log(u"Unable to find a new version link on google code, not updating") return False try: # prepare the update dir - sb_update_dir = ek.ek(os.path.join, sickbeard.PROG_DIR, u'sb-update') - + sb_update_dir = ek.ek(os.path.join, sickbeard.PROG_DIR, u'sb-update') + if os.path.isdir(sb_update_dir): - logger.log(u"Clearing out update folder " + sb_update_dir + " before extracting") + logger.log(u"Clearing out update folder " + sb_update_dir + " before extracting") shutil.rmtree(sb_update_dir) - logger.log(u"Creating update folder " + sb_update_dir + " before extracting") - os.makedirs(sb_update_dir) - - # retrieve file - logger.log(u"Downloading update from " + zip_download_url) - zip_download_path = os.path.join(sb_update_dir, u'sb-update.zip') - urllib.urlretrieve(zip_download_url, zip_download_path) - - if not ek.ek(os.path.isfile, zip_download_path): - logger.log(u"Unable to retrieve new version from " + zip_download_url + ", can't update", logger.ERROR) - return False - - # extract to sb-update dir - logger.log(u"Unzipping from " + str(zip_download_path) + " to " + sb_update_dir) - update_zip = zipfile.ZipFile(zip_download_path, 'r') + logger.log(u"Creating update folder " + sb_update_dir + " before extracting") + os.makedirs(sb_update_dir) + + # retrieve file + logger.log(u"Downloading update from " + zip_download_url) + zip_download_path = os.path.join(sb_update_dir, u'sb-update.zip') + urllib.urlretrieve(zip_download_url, zip_download_path) + + if not ek.ek(os.path.isfile, zip_download_path): + logger.log(u"Unable to retrieve new version from " + zip_download_url + ", can't update", logger.ERROR) + return False + + # extract to sb-update dir + logger.log(u"Unzipping from " + str(zip_download_path) + " to " + sb_update_dir) + update_zip = zipfile.ZipFile(zip_download_path, 'r') update_zip.extractall(sb_update_dir) update_zip.close() - - # delete the zip - logger.log(u"Deleting zip file from " + str(zip_download_path)) - os.remove(zip_download_path) - + + # delete the zip + logger.log(u"Deleting zip file from " + str(zip_download_path)) + os.remove(zip_download_path) + # find update dir name - update_dir_contents = [x for x in os.listdir(sb_update_dir) if os.path.isdir(os.path.join(sb_update_dir, x))] - + update_dir_contents = [x for x in os.listdir(sb_update_dir) if os.path.isdir(os.path.join(sb_update_dir, x))] + if len(update_dir_contents) != 1: - logger.log(u"Invalid update data, update failed. Maybe try deleting your sb-update folder?", logger.ERROR) + logger.log(u"Invalid update data, update failed. Maybe try deleting your sb-update folder?", logger.ERROR) return False content_dir = os.path.join(sb_update_dir, update_dir_contents[0]) - old_update_path = os.path.join(content_dir, u'updater.exe') - new_update_path = os.path.join(sickbeard.PROG_DIR, u'updater.exe') - logger.log(u"Copying new update.exe file from " + old_update_path + " to " + new_update_path) + old_update_path = os.path.join(content_dir, u'updater.exe') + new_update_path = os.path.join(sickbeard.PROG_DIR, u'updater.exe') + logger.log(u"Copying new update.exe file from " + old_update_path + " to " + new_update_path) shutil.move(old_update_path, new_update_path) except Exception, e: - logger.log(u"Error while trying to update: " + ex(e), logger.ERROR) + logger.log(u"Error while trying to update: " + ex(e), logger.ERROR) return False return True - -class GitUpdateManager(UpdateManager): - - def __init__(self): - self._git_path = self._find_working_git() - self.github_repo_user = self.get_github_repo_user() - self.github_repo = self.get_github_repo() - self.branch = self._find_git_branch() - - self._cur_commit_hash = None - self._newest_commit_hash = None - self._num_commits_behind = 0 - - def _git_error(self): - error_message = 'Unable to find your git executable - Shutdown SickBeard and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.' - sickbeard.NEWEST_VERSION_STRING = error_message - - def _find_working_git(self): - test_cmd = 'version' - - if sickbeard.GIT_PATH: - main_git = '"' + sickbeard.GIT_PATH + '"' - else: - main_git = 'git' - - logger.log(u"Checking if we can use git commands: " + main_git + ' ' + test_cmd, logger.DEBUG) - output, err, exit_status = self._run_git(main_git, test_cmd) # @UnusedVariable - - if exit_status == 0: - logger.log(u"Using: " + main_git, logger.DEBUG) - return main_git - else: - logger.log(u"Not using: " + main_git, logger.DEBUG) - - # trying alternatives - - alternative_git = [] - - # osx people who start SB from launchd have a broken path, so try a hail-mary attempt for them - if platform.system().lower() == 'darwin': - alternative_git.append('/usr/local/git/bin/git') - - if platform.system().lower() == 'windows': - if main_git != main_git.lower(): - alternative_git.append(main_git.lower()) - - if alternative_git: - logger.log(u"Trying known alternative git locations", logger.DEBUG) - - for cur_git in alternative_git: - logger.log(u"Checking if we can use git commands: " + cur_git + ' ' + test_cmd, logger.DEBUG) - output, err, exit_status = self._run_git(cur_git, test_cmd) # @UnusedVariable - - if exit_status == 0: - logger.log(u"Using: " + cur_git, logger.DEBUG) - return cur_git - else: - logger.log(u"Not using: " + cur_git, logger.DEBUG) - - # Still haven't found a working git - error_message = 'Unable to find your git executable - Shutdown SickBeard and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.' - sickbeard.NEWEST_VERSION_STRING = error_message - - return None - - def _run_git(self, git_path, args): - - output = err = exit_status = None - - if not git_path: - logger.log(u"No git specified, can't use git commands", logger.ERROR) - exit_status = 1 - return (output, err, exit_status) - - cmd = git_path + ' ' + args - - try: - logger.log(u"Executing " + cmd + " with your shell in " + sickbeard.PROG_DIR, logger.DEBUG) - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=sickbeard.PROG_DIR) - output, err = p.communicate() - exit_status = p.returncode - - if output: - output = output.strip() - logger.log(u"git output: " + output, logger.DEBUG) - - except OSError: - logger.log(u"Command " + cmd + " didn't work") - exit_status = 1 - - if exit_status == 0: - logger.log(cmd + u" : returned successful", logger.DEBUG) - exit_status = 0 - - elif exit_status == 1: - logger.log(cmd + u" returned : " + output, logger.ERROR) - exit_status = 1 - - elif exit_status == 128 or 'fatal:' in output or err: - logger.log(cmd + u" returned : " + output, logger.ERROR) - exit_status = 128 - - else: - logger.log(cmd + u" returned : " + output + u", treat as error for now", logger.ERROR) - exit_status = 1 - - return (output, err, exit_status) - - def _find_installed_version(self): - """ + +class GitUpdateManager(UpdateManager): + + def __init__(self): + self._git_path = self._find_working_git() + self.github_repo_user = self.get_github_repo_user() + self.github_repo = self.get_github_repo() + self.branch = self._find_git_branch() + + self._cur_commit_hash = None + self._newest_commit_hash = None + self._num_commits_behind = 0 + + def _git_error(self): + error_message = 'Unable to find your git executable - Shutdown SickBeard and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.' + sickbeard.NEWEST_VERSION_STRING = error_message + + def _find_working_git(self): + test_cmd = 'version' + + if sickbeard.GIT_PATH: + main_git = '"' + sickbeard.GIT_PATH + '"' + else: + main_git = 'git' + + logger.log(u"Checking if we can use git commands: " + main_git + ' ' + test_cmd, logger.DEBUG) + output, err, exit_status = self._run_git(main_git, test_cmd) # @UnusedVariable + + if exit_status == 0: + logger.log(u"Using: " + main_git, logger.DEBUG) + return main_git + else: + logger.log(u"Not using: " + main_git, logger.DEBUG) + + # trying alternatives + + alternative_git = [] + + # osx people who start SB from launchd have a broken path, so try a hail-mary attempt for them + if platform.system().lower() == 'darwin': + alternative_git.append('/usr/local/git/bin/git') + + if platform.system().lower() == 'windows': + if main_git != main_git.lower(): + alternative_git.append(main_git.lower()) + + if alternative_git: + logger.log(u"Trying known alternative git locations", logger.DEBUG) + + for cur_git in alternative_git: + logger.log(u"Checking if we can use git commands: " + cur_git + ' ' + test_cmd, logger.DEBUG) + output, err, exit_status = self._run_git(cur_git, test_cmd) # @UnusedVariable + + if exit_status == 0: + logger.log(u"Using: " + cur_git, logger.DEBUG) + return cur_git + else: + logger.log(u"Not using: " + cur_git, logger.DEBUG) + + # Still haven't found a working git + error_message = 'Unable to find your git executable - Shutdown SickBeard and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.' + sickbeard.NEWEST_VERSION_STRING = error_message + + return None + + def _run_git(self, git_path, args): + + output = err = exit_status = None + + if not git_path: + logger.log(u"No git specified, can't use git commands", logger.ERROR) + exit_status = 1 + return (output, err, exit_status) + + cmd = git_path + ' ' + args + + try: + logger.log(u"Executing " + cmd + " with your shell in " + sickbeard.PROG_DIR, logger.DEBUG) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=sickbeard.PROG_DIR) + output, err = p.communicate() + exit_status = p.returncode + + if output: + output = output.strip() + logger.log(u"git output: " + output, logger.DEBUG) + + except OSError: + logger.log(u"Command " + cmd + " didn't work") + exit_status = 1 + + if exit_status == 0: + logger.log(cmd + u" : returned successful", logger.DEBUG) + exit_status = 0 + + elif exit_status == 1: + logger.log(cmd + u" returned : " + output, logger.ERROR) + exit_status = 1 + + elif exit_status == 128 or 'fatal:' in output or err: + logger.log(cmd + u" returned : " + output, logger.ERROR) + exit_status = 128 + + else: + logger.log(cmd + u" returned : " + output + u", treat as error for now", logger.ERROR) + exit_status = 1 + + return (output, err, exit_status) + + def _find_installed_version(self): + """ Attempts to find the currently installed version of Sick Beard. Uses git show to get commit version. @@ -371,25 +371,25 @@ def _find_installed_version(self): Returns: True for success or False for failure """ - output, err, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') # @UnusedVariable + output, err, exit_status = self._run_git(self._git_path, 'rev-parse HEAD') # @UnusedVariable - if exit_status == 0 and output: - cur_commit_hash = output.strip() - if not re.match('^[a-z0-9]+$', cur_commit_hash): - logger.log(u"Output doesn't look like a hash, not using it", logger.ERROR) - return False - self._cur_commit_hash = cur_commit_hash - return True - else: - return False + if exit_status == 0 and output: + cur_commit_hash = output.strip() + if not re.match('^[a-z0-9]+$', cur_commit_hash): + logger.log(u"Output doesn't look like a hash, not using it", logger.ERROR) + return False + self._cur_commit_hash = cur_commit_hash + return True + else: + return False def _find_git_branch(self): - branch_info, err, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') # @UnusedVariable - if exit_status == 0 and branch_info: - branch = branch_info.strip().replace('refs/heads/', '', 1) - if branch: - sickbeard.version.SICKBEARD_VERSION = branch - return sickbeard.version.SICKBEARD_VERSION + branch_info, err, exit_status = self._run_git(self._git_path, 'symbolic-ref -q HEAD') # @UnusedVariable + if exit_status == 0 and branch_info: + branch = branch_info.strip().replace('refs/heads/', '', 1) + if branch: + sickbeard.version.SICKBEARD_VERSION = branch + return sickbeard.version.SICKBEARD_VERSION def _check_github_for_update(self): """ @@ -402,10 +402,10 @@ def _check_github_for_update(self): self._num_commits_behind = 0 self._newest_commit_hash = None - gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) + gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) # find newest commit - for curCommit in gh.commits(): + for curCommit in gh.commits(): if not self._newest_commit_hash: self._newest_commit_hash = curCommit['sha'] if not self._cur_commit_hash: @@ -416,51 +416,51 @@ def _check_github_for_update(self): self._num_commits_behind += 1 - logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) + logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) def set_newest_text(self): # if we're up to date then don't set this - sickbeard.NEWEST_VERSION_STRING = None - + sickbeard.NEWEST_VERSION_STRING = None + if self._num_commits_behind == 100: - newest_text = "You are ahead of " + self.branch + ". Update not possible." + newest_text = "You are ahead of " + self.branch + ". Update not possible." elif self._num_commits_behind > 0: - base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo - if self._newest_commit_hash: - url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash - else: - url = base_url + '/commits/' + base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo + if self._newest_commit_hash: + url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash + else: + url = base_url + '/commits/' - newest_text = 'There is a newer version available ' - newest_text += " (you're " + str(self._num_commits_behind) + " commit" - if self._num_commits_behind > 1: - newest_text += 's' - newest_text += ' behind)' + "— Update Now" + newest_text = 'There is a newer version available ' + newest_text += " (you're " + str(self._num_commits_behind) + " commit" + if self._num_commits_behind > 1: + newest_text += 's' + newest_text += ' behind)' + "— Update Now" - else: - return + else: + return - sickbeard.NEWEST_VERSION_STRING = newest_text + sickbeard.NEWEST_VERSION_STRING = newest_text def need_update(self): - self._find_installed_version() + self._find_installed_version() - if not self._cur_commit_hash: + if not self._cur_commit_hash: return True - else: - try: - self._check_github_for_update() - except Exception, e: - logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) - return False - - logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) - - if self._num_commits_behind > 0: - return True + else: + try: + self._check_github_for_update() + except Exception, e: + logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) + return False + + logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) + + if self._num_commits_behind > 0: + return True return False @@ -470,30 +470,30 @@ def update(self): on the call's success. """ - output, err, exit_status = self._run_git(self._git_path, 'pull origin ' + self.branch) # @UnusedVariable + output, err, exit_status = self._run_git(self._git_path, 'pull origin ' + self.branch) # @UnusedVariable - if exit_status == 0: - return True - else: - return False + if exit_status == 0: + return True + else: + return False - return False + return False -class SourceUpdateManager(UpdateManager): +class SourceUpdateManager(UpdateManager): - def __init__(self): - self.github_repo_user = self.get_github_repo_user() - self.github_repo = self.get_github_repo() - self.branch = sickbeard.version.SICKBEARD_VERSION + def __init__(self): + self.github_repo_user = self.get_github_repo_user() + self.github_repo = self.get_github_repo() + self.branch = sickbeard.version.SICKBEARD_VERSION - self._cur_commit_hash = None - self._newest_commit_hash = None - self._num_commits_behind = 0 + self._cur_commit_hash = None + self._newest_commit_hash = None + self._num_commits_behind = 0 - def _find_installed_version(self): + def _find_installed_version(self): - version_file = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt') + version_file = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt') if not os.path.isfile(version_file): self._cur_commit_hash = None @@ -509,143 +509,143 @@ def _find_installed_version(self): self._cur_commit_hash = None def need_update(self): - - self._find_installed_version() - - try: - self._check_github_for_update() - except Exception, e: - logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) - return False - - logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) - - if not self._cur_commit_hash or self._num_commits_behind > 0: - return True - - return False - - def _check_github_for_update(self): - """ - Uses pygithub to ask github if there is a newer version that the provided - commit hash. If there is a newer version it sets Sick Beard's version text. - - commit_hash: hash that we're checking against - """ - - self._num_commits_behind = 0 - self._newest_commit_hash = None - - gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) - - # find newest commit - for curCommit in gh.commits(): - if not self._newest_commit_hash: - self._newest_commit_hash = curCommit['sha'] - if not self._cur_commit_hash: - break - - if curCommit['sha'] == self._cur_commit_hash: - break - - self._num_commits_behind += 1 - - logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) + + self._find_installed_version() + + try: + self._check_github_for_update() + except Exception, e: + logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) + return False + + logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) + + if not self._cur_commit_hash or self._num_commits_behind > 0: + return True + + return False + + def _check_github_for_update(self): + """ + Uses pygithub to ask github if there is a newer version that the provided + commit hash. If there is a newer version it sets Sick Beard's version text. + + commit_hash: hash that we're checking against + """ + + self._num_commits_behind = 0 + self._newest_commit_hash = None + + gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) + + # find newest commit + for curCommit in gh.commits(): + if not self._newest_commit_hash: + self._newest_commit_hash = curCommit['sha'] + if not self._cur_commit_hash: + break + + if curCommit['sha'] == self._cur_commit_hash: + break + + self._num_commits_behind += 1 + + logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) def set_newest_text(self): - # if we're up to date then don't set this - sickbeard.NEWEST_VERSION_STRING = None - - if not self._cur_commit_hash or self._num_commits_behind == 100: - logger.log(u"Unknown current version, don't know if we should update or not", logger.DEBUG) - - newest_text = "Unknown version: If you've never used the Sick Beard upgrade system then I don't know what version you have." - newest_text += "— Update Now" - - elif self._num_commits_behind > 0: - base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo - if self._newest_commit_hash: - url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash - else: - url = base_url + '/commits/' - - newest_text = 'There is a newer version available' - newest_text += " (you're " + str(self._num_commits_behind) + " commit" - if self._num_commits_behind > 1: - newest_text += "s" - newest_text += " behind)" + "— Update Now" - else: - return - - sickbeard.NEWEST_VERSION_STRING = newest_text - - def update(self): - """ - Downloads the latest source tarball from github and installs it over the existing version. - """ - base_url = 'https://github.com/' + self.github_repo_user + '/' + self.github_repo - tar_download_url = base_url + '/tarball/' + self.branch - version_path = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt') - - try: - # prepare the update dir - sb_update_dir = ek.ek(os.path.join, sickbeard.PROG_DIR, u'sb-update') - - if os.path.isdir(sb_update_dir): - logger.log(u"Clearing out update folder " + sb_update_dir + " before extracting") - shutil.rmtree(sb_update_dir) - - logger.log(u"Creating update folder " + sb_update_dir + " before extracting") - os.makedirs(sb_update_dir) - - # retrieve file - logger.log(u"Downloading update from " + repr(tar_download_url)) - tar_download_path = os.path.join(sb_update_dir, u'sb-update.tar') - urllib.urlretrieve(tar_download_url, tar_download_path) - - if not ek.ek(os.path.isfile, tar_download_path): - logger.log(u"Unable to retrieve new version from " + tar_download_url + ", can't update", logger.ERROR) - return False - - # extract to sb-update dir - logger.log(u"Extracting file " + tar_download_path) - tar = tarfile.open(tar_download_path) - tar.extractall(sb_update_dir) - tar.close() - - # delete .tar.gz - logger.log(u"Deleting file " + tar_download_path) - os.remove(tar_download_path) - - # find update dir name - update_dir_contents = [x for x in os.listdir(sb_update_dir) if os.path.isdir(os.path.join(sb_update_dir, x))] - if len(update_dir_contents) != 1: - logger.log(u"Invalid update data, update failed: " + str(update_dir_contents), logger.ERROR) - return False - content_dir = os.path.join(sb_update_dir, update_dir_contents[0]) - - # walk temp folder and move files to main folder - for dirname, dirnames, filenames in os.walk(content_dir): # @UnusedVariable - dirname = dirname[len(content_dir) + 1:] - for curfile in filenames: - old_path = os.path.join(content_dir, dirname, curfile) - new_path = os.path.join(sickbeard.PROG_DIR, dirname, curfile) - - if os.path.isfile(new_path): - os.remove(new_path) - os.renames(old_path, new_path) - - # update version.txt with commit hash + # if we're up to date then don't set this + sickbeard.NEWEST_VERSION_STRING = None + + if not self._cur_commit_hash or self._num_commits_behind == 100: + logger.log(u"Unknown current version, don't know if we should update or not", logger.DEBUG) + + newest_text = "Unknown version: If you've never used the Sick Beard upgrade system then I don't know what version you have." + newest_text += "— Update Now" + + elif self._num_commits_behind > 0: + base_url = 'http://github.com/' + self.github_repo_user + '/' + self.github_repo + if self._newest_commit_hash: + url = base_url + '/compare/' + self._cur_commit_hash + '...' + self._newest_commit_hash + else: + url = base_url + '/commits/' + + newest_text = 'There is a newer version available' + newest_text += " (you're " + str(self._num_commits_behind) + " commit" + if self._num_commits_behind > 1: + newest_text += "s" + newest_text += " behind)" + "— Update Now" + else: + return + + sickbeard.NEWEST_VERSION_STRING = newest_text + + def update(self): + """ + Downloads the latest source tarball from github and installs it over the existing version. + """ + base_url = 'https://github.com/' + self.github_repo_user + '/' + self.github_repo + tar_download_url = base_url + '/tarball/' + self.branch + version_path = ek.ek(os.path.join, sickbeard.PROG_DIR, u'version.txt') + + try: + # prepare the update dir + sb_update_dir = ek.ek(os.path.join, sickbeard.PROG_DIR, u'sb-update') + + if os.path.isdir(sb_update_dir): + logger.log(u"Clearing out update folder " + sb_update_dir + " before extracting") + shutil.rmtree(sb_update_dir) + + logger.log(u"Creating update folder " + sb_update_dir + " before extracting") + os.makedirs(sb_update_dir) + + # retrieve file + logger.log(u"Downloading update from " + repr(tar_download_url)) + tar_download_path = os.path.join(sb_update_dir, u'sb-update.tar') + urllib.urlretrieve(tar_download_url, tar_download_path) + + if not ek.ek(os.path.isfile, tar_download_path): + logger.log(u"Unable to retrieve new version from " + tar_download_url + ", can't update", logger.ERROR) + return False + + # extract to sb-update dir + logger.log(u"Extracting file " + tar_download_path) + tar = tarfile.open(tar_download_path) + tar.extractall(sb_update_dir) + tar.close() + + # delete .tar.gz + logger.log(u"Deleting file " + tar_download_path) + os.remove(tar_download_path) + + # find update dir name + update_dir_contents = [x for x in os.listdir(sb_update_dir) if os.path.isdir(os.path.join(sb_update_dir, x))] + if len(update_dir_contents) != 1: + logger.log(u"Invalid update data, update failed: " + str(update_dir_contents), logger.ERROR) + return False + content_dir = os.path.join(sb_update_dir, update_dir_contents[0]) + + # walk temp folder and move files to main folder + for dirname, dirnames, filenames in os.walk(content_dir): # @UnusedVariable + dirname = dirname[len(content_dir) + 1:] + for curfile in filenames: + old_path = os.path.join(content_dir, dirname, curfile) + new_path = os.path.join(sickbeard.PROG_DIR, dirname, curfile) + + if os.path.isfile(new_path): + os.remove(new_path) + os.renames(old_path, new_path) + + # update version.txt with commit hash try: - with open(version_path, 'w') as ver_file: - ver_file.write(self._newest_commit_hash) - except EnvironmentError, e: - logger.log(u"Unable to write version file, update not complete: " + ex(e), logger.ERROR) - return False - - except Exception, e: - logger.log(u"Error while trying to update: " + ex(e), logger.ERROR) - return False - - return True + with open(version_path, 'w') as ver_file: + ver_file.write(self._newest_commit_hash) + except EnvironmentError, e: + logger.log(u"Unable to write version file, update not complete: " + ex(e), logger.ERROR) + return False + + except Exception, e: + logger.log(u"Error while trying to update: " + ex(e), logger.ERROR) + return False + + return True From a9041391c2908ea4266632fde4de4daaeac97a4f Mon Sep 17 00:00:00 2001 From: Patrick Vos Date: Tue, 18 Mar 2014 22:17:48 +0100 Subject: [PATCH 02/39] Change git and source updater Switch to real git commands. Using lad1337/XDM as example. + Change source updater. Get commits behind directly using github api compare Add downloaded update file corruption check Add extra log lines Add stdin to subprocess.Popen to prevent WindowsError: [Error 6] The handle is invalid on pythonw --- sickbeard/gh_api.py | 17 +++++- sickbeard/versionChecker.py | 117 +++++++++++++++++++++++++----------- 2 files changed, 97 insertions(+), 37 deletions(-) diff --git a/sickbeard/gh_api.py b/sickbeard/gh_api.py index 27b2dd6d7e..3f24dfaabb 100644 --- a/sickbeard/gh_api.py +++ b/sickbeard/gh_api.py @@ -69,5 +69,20 @@ def commits(self): Returns a deserialized json object containing the commit info. See http://developer.github.com/v3/repos/commits/ """ - access_API = self._access_API(['repos', self.github_repo_user, self.github_repo, 'commits'], {'per_page': 100, 'sha': self.branch}) + access_API = self._access_API(['repos', self.github_repo_user, self.github_repo, 'commits'], params={'per_page': 100, 'sha': self.branch}) + return access_API + + def compare(self, base, head, per_page=1): + """ + Uses the API to get a list of compares between base and head. + + user: The github username of the person whose repo you're querying + repo: The repo name to query + base: Start compare from branch + head: Current commit sha or branch name to compare + per_page: number of items per page + + Returns a deserialized json object containing the compare info. See http://developer.github.com/v3/repos/commits/ + """ + access_API = self._access_API(['repos', self.github_repo_user, self.github_repo, 'compare', base + '...' + head], params={'per_page': per_page}) return access_API diff --git a/sickbeard/versionChecker.py b/sickbeard/versionChecker.py index aa49719cc6..b79e4abce5 100644 --- a/sickbeard/versionChecker.py +++ b/sickbeard/versionChecker.py @@ -184,6 +184,8 @@ def need_update(self): if self._newest_version and self._newest_version > self._cur_version: return True + return False + def set_newest_text(self): sickbeard.NEWEST_VERSION_STRING = None @@ -225,6 +227,10 @@ def update(self): logger.log(u"Unable to retrieve new version from " + zip_download_url + ", can't update", logger.ERROR) return False + if not ek.ek(zipfile.is_zipfile, zip_download_path): + logger.log(u"Retrieved version from " + zip_download_url + " is corrupt, can't update", logger.ERROR) + return False + # extract to sb-update dir logger.log(u"Unzipping from " + str(zip_download_path) + " to " + sb_update_dir) update_zip = zipfile.ZipFile(zip_download_path, 'r') @@ -266,6 +272,7 @@ def __init__(self): self._cur_commit_hash = None self._newest_commit_hash = None self._num_commits_behind = 0 + self._num_commits_ahead = 0 def _git_error(self): error_message = 'Unable to find your git executable - Shutdown SickBeard and EITHER set git_path in your config.ini OR delete your .git folder and run from source to enable updates.' @@ -332,7 +339,7 @@ def _run_git(self, git_path, args): try: logger.log(u"Executing " + cmd + " with your shell in " + sickbeard.PROG_DIR, logger.DEBUG) - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=sickbeard.PROG_DIR) + p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True, cwd=sickbeard.PROG_DIR) output, err = p.communicate() exit_status = p.returncode @@ -393,38 +400,61 @@ def _find_git_branch(self): def _check_github_for_update(self): """ - Uses pygithub to ask github if there is a newer version that the provided - commit hash. If there is a newer version it sets Sick Beard's version text. - - commit_hash: hash that we're checking against + Uses git commands to check if there is a newer version that the provided + commit hash. If there is a newer version it sets _num_commits_behind. """ - self._num_commits_behind = 0 self._newest_commit_hash = None + self._num_commits_behind = 0 + self._num_commits_ahead = 0 - gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) + # get all new info from github + output, err, exit_status = self._run_git(self._git_path, 'fetch origin') # @UnusedVariable - # find newest commit - for curCommit in gh.commits(): - if not self._newest_commit_hash: - self._newest_commit_hash = curCommit['sha'] - if not self._cur_commit_hash: - break + if not exit_status == 0: + logger.log(u"Unable to contact github, can't check for update", logger.ERROR) + return + + # get latest commit_hash from remote + output, err, exit_status = self._run_git(self._git_path, 'rev-parse --verify --quiet "@{upstream}"') # @UnusedVariable + + if exit_status == 0 and output: + cur_commit_hash = output.strip() + + if not re.match('^[a-z0-9]+$', cur_commit_hash): + logger.log(u"Output doesn't look like a hash, not using it", logger.DEBUG) + return - if curCommit['sha'] == self._cur_commit_hash: - break + else: + self._newest_commit_hash = cur_commit_hash + else: + logger.log(u"git didn't return newest commit hash", logger.DEBUG) + return + + # get number of commits behind and ahead (option --count not supported git < 1.7.2) + output, err, exit_status = self._run_git(self._git_path, 'rev-list --left-right "@{upstream}"...HEAD') # @UnusedVariable + + if exit_status == 0 and output: + + try: + self._num_commits_behind = int(output.count("<")) + self._num_commits_ahead = int(output.count(">")) - self._num_commits_behind += 1 + except: + logger.log(u"git didn't return numbers for behind and ahead, not using it", logger.DEBUG) + return - logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) + logger.log(u"cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + + u", num_commits_behind = " + str(self._num_commits_behind) + u", num_commits_ahead = " + str(self._num_commits_ahead), logger.DEBUG) def set_newest_text(self): # if we're up to date then don't set this sickbeard.NEWEST_VERSION_STRING = None - if self._num_commits_behind == 100: - newest_text = "You are ahead of " + self.branch + ". Update not possible." + if self._num_commits_ahead: + logger.log(u"Local branch is ahead of " + self.branch + ". Automatic update not possible.", logger.ERROR) + newest_text = "Local branch is ahead of " + self.branch + ". Automatic update not possible." elif self._num_commits_behind > 0: @@ -457,8 +487,6 @@ def need_update(self): logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) return False - logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) - if self._num_commits_behind > 0: return True @@ -518,8 +546,6 @@ def need_update(self): logger.log(u"Unable to contact github, can't check for update: " + repr(e), logger.ERROR) return False - logger.log(u"After checking, cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) - if not self._cur_commit_hash or self._num_commits_behind > 0: return True @@ -538,29 +564,43 @@ def _check_github_for_update(self): gh = github.GitHub(self.github_repo_user, self.github_repo, self.branch) - # find newest commit - for curCommit in gh.commits(): - if not self._newest_commit_hash: - self._newest_commit_hash = curCommit['sha'] - if not self._cur_commit_hash: - break + # try to get newest commit hash and commits behind directly by comparing branch and current commit + if self._cur_commit_hash: + branch_compared = gh.compare(base=self.branch, head=self._cur_commit_hash) + + if 'base_commit' in branch_compared: + self._newest_commit_hash = branch_compared['base_commit']['sha'] + + if 'behind_by' in branch_compared: + self._num_commits_behind = int(branch_compared['behind_by']) - if curCommit['sha'] == self._cur_commit_hash: - break + # fall back and iterate over last 100 (items per page in gh_api) commits + if not self._newest_commit_hash: - self._num_commits_behind += 1 + for curCommit in gh.commits(): + if not self._newest_commit_hash: + self._newest_commit_hash = curCommit['sha'] + if not self._cur_commit_hash: + break + + if curCommit['sha'] == self._cur_commit_hash: + break - logger.log(u"newest: " + str(self._newest_commit_hash) + " and current: " + str(self._cur_commit_hash) + " and num_commits: " + str(self._num_commits_behind), logger.DEBUG) + # when _cur_commit_hash doesn't match anything _num_commits_behind == 100 + self._num_commits_behind += 1 + + logger.log(u"cur_commit = " + str(self._cur_commit_hash) + u", newest_commit = " + str(self._newest_commit_hash) + + u", num_commits_behind = " + str(self._num_commits_behind), logger.DEBUG) def set_newest_text(self): # if we're up to date then don't set this sickbeard.NEWEST_VERSION_STRING = None - if not self._cur_commit_hash or self._num_commits_behind == 100: - logger.log(u"Unknown current version, don't know if we should update or not", logger.DEBUG) + if not self._cur_commit_hash: + logger.log(u"Unknown current version number, don't know if we should update or not", logger.DEBUG) - newest_text = "Unknown version: If you've never used the Sick Beard upgrade system then I don't know what version you have." + newest_text = "Unknown current version number: If you've never used the Sick Beard upgrade system before then current version is not set." newest_text += "— Update Now" elif self._num_commits_behind > 0: @@ -608,6 +648,10 @@ def update(self): logger.log(u"Unable to retrieve new version from " + tar_download_url + ", can't update", logger.ERROR) return False + if not ek.ek(tarfile.is_tarfile, tar_download_path): + logger.log(u"Retrieved version from " + tar_download_url + " is corrupt, can't update", logger.ERROR) + return False + # extract to sb-update dir logger.log(u"Extracting file " + tar_download_path) tar = tarfile.open(tar_download_path) @@ -626,6 +670,7 @@ def update(self): content_dir = os.path.join(sb_update_dir, update_dir_contents[0]) # walk temp folder and move files to main folder + logger.log(u"Moving files from " + content_dir + " to " + sickbeard.PROG_DIR) for dirname, dirnames, filenames in os.walk(content_dir): # @UnusedVariable dirname = dirname[len(content_dir) + 1:] for curfile in filenames: From 35175c8b6c214579260976bcdeea9384b11b60cc Mon Sep 17 00:00:00 2001 From: Jonathon Saine Date: Mon, 31 Mar 2014 06:39:37 -0500 Subject: [PATCH 03/39] Update notifiers: Initial pass to standardize each notifier Standardize internal notify call. Add update_library to each notifier Change logging. Prefix the notifier and reduce unnecessary logging. Use new globally named notify.png instead of xbmc-notify.png. Apply PEP8 fixes. synoindex is a bit different on how its used within sb, only applied pep8 fixes for now --- .../default/config_notifications.tmpl | 3 +- sickbeard/notifiers/__init__.py | 5 + sickbeard/notifiers/boxcar.py | 78 ++++---- sickbeard/notifiers/growl.py | 166 ++++++++++-------- sickbeard/notifiers/libnotify.py | 43 +++-- sickbeard/notifiers/nma.py | 68 ++++--- sickbeard/notifiers/nmj.py | 87 +++++---- sickbeard/notifiers/nmjv2.py | 126 +++++++------ sickbeard/notifiers/plex.py | 59 ++++--- sickbeard/notifiers/prowl.py | 124 +++++++------ sickbeard/notifiers/pushover.py | 79 +++++---- sickbeard/notifiers/pytivo.py | 56 +++--- sickbeard/notifiers/synoindex.py | 39 ++-- sickbeard/notifiers/trakt.py | 146 +++++++-------- sickbeard/notifiers/tweet.py | 144 +++++++-------- sickbeard/notifiers/xbmc.py | 121 ++++++------- sickbeard/webserve.py | 14 +- 17 files changed, 716 insertions(+), 642 deletions(-) diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index cc9235ef18..81e956ebfa 100644 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -455,6 +455,7 @@

Growl

A cross-platform unobtrusive global notification system.

+

For more information visit our Wiki.

@@ -669,7 +670,7 @@

Boxcar

-

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

+

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

diff --git a/sickbeard/notifiers/__init__.py b/sickbeard/notifiers/__init__.py index 13b62cde79..cc4e769c21 100755 --- a/sickbeard/notifiers/__init__.py +++ b/sickbeard/notifiers/__init__.py @@ -81,3 +81,8 @@ def notify_download(ep_name): def notify_snatch(ep_name): for n in notifiers: n.notify_snatch(ep_name) + + +def update_library(ep_obj): + for n in notifiers: + n.update_library(ep_obj) diff --git a/sickbeard/notifiers/boxcar.py b/sickbeard/notifiers/boxcar.py index 35a86942de..474bac4d8f 100644 --- a/sickbeard/notifiers/boxcar.py +++ b/sickbeard/notifiers/boxcar.py @@ -17,7 +17,8 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -import urllib, urllib2 +import urllib +import urllib2 import time import sickbeard @@ -28,23 +29,21 @@ API_URL = "https://boxcar.io/devices/providers/fWc4sgSmpcN6JujtBmR6/notifications" -class BoxcarNotifier: - def test_notify(self, email, title="Test"): - return self._sendBoxcar("This is a test notification from SickBeard", title, email) +class BoxcarNotifier: def _sendBoxcar(self, msg, title, email, subscribe=False): """ Sends a boxcar notification to the address provided - + msg: The message to send (unicode) title: The title of the message email: The email address to send the message to (or to subscribe with) subscribe: If true then instead of sending a message this function will send a subscription notification (optional, default is False) - + returns: True if the message succeeded, False otherwise """ - + # build up the URL and parameters msg = msg.strip() curUrl = API_URL @@ -53,7 +52,7 @@ def _sendBoxcar(self, msg, title, email, subscribe=False): if subscribe: data = urllib.urlencode({'email': email}) curUrl = curUrl + "/subscribe" - + # for normal requests we need all these parameters else: data = urllib.urlencode({ @@ -63,63 +62,54 @@ def _sendBoxcar(self, msg, title, email, subscribe=False): 'notification[from_remote_service_id]': int(time.time()) }) - # send the request to boxcar try: + # TODO: Use our getURL from helper? req = urllib2.Request(curUrl) handle = urllib2.urlopen(req, data) handle.close() - + 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("Boxcar notification failed." + ex(e), logger.ERROR) + logger.log(u"BOXCAR: Boxcar notification failed." + ex(e), logger.ERROR) return False else: - logger.log("Boxcar notification failed. Error code: " + str(e.code), logger.WARNING) + logger.log(u"BOXCAR: Boxcar notification failed. Error code: " + str(e.code), logger.WARNING) # HTTP status 404 if the provided email address isn't a Boxcar user. if e.code == 404: - logger.log("Username is wrong/not a boxcar email. Boxcar will send an email to it", logger.WARNING) + logger.log(u"BOXCAR: Username is wrong/not a boxcar email. Boxcar will send an email to it", logger.WARNING) return False - + # For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service. elif e.code == 401: - + # If the user has already added your service, we'll return an HTTP status code of 401. if subscribe: - logger.log("Already subscribed to service", logger.ERROR) + logger.log(u"BOXCAR: Already subscribed to service", logger.ERROR) # i dont know if this is true or false ... its neither but i also dont know how we got here in the first place return False - - #HTTP status 401 if the user doesn't have the service added + + # HTTP status 401 if the user doesn't have the service added else: subscribeNote = self._sendBoxcar(msg, title, email, True) if subscribeNote: - logger.log("Subscription send", logger.DEBUG) + logger.log(u"BOXCAR: Subscription sent.", logger.DEBUG) return True else: - logger.log("Subscription could not be send", logger.ERROR) + logger.log(u"BOXCAR: Subscription could not be sent.", logger.ERROR) return 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("Wrong data send to boxcar", logger.ERROR) + logger.log(u"BOXCAR: Wrong data send to boxcar.", logger.ERROR) return False - logger.log("Boxcar notification successful.", logger.DEBUG) + logger.log(u"BOXCAR: Boxcar notification successful.", logger.DEBUG) return True - def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]): - if sickbeard.BOXCAR_NOTIFY_ONSNATCH: - self._notifyBoxcar(title, ep_name) - - - def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]): - if sickbeard.BOXCAR_NOTIFY_ONDOWNLOAD: - self._notifyBoxcar(title, ep_name) - - def _notifyBoxcar(self, title, message, username=None, force=False): + def _notify(self, title, message, username=None, force=False): """ Sends a boxcar notification based on the provided info or SB config @@ -129,17 +119,35 @@ def _notifyBoxcar(self, title, message, username=None, force=False): force: If True then the notification will be sent even if Boxcar is disabled in the config """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_BOXCAR and not force: - logger.log("Notification for Boxcar not enabled, skipping this notification", logger.DEBUG) return False # if no username was given then use the one from the config if not username: username = sickbeard.BOXCAR_USERNAME - logger.log("Sending notification for " + message, logger.DEBUG) + logger.log(u"BOXCAR: Sending notification for " + message, logger.DEBUG) self._sendBoxcar(message, title, username) return True +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.BOXCAR_NOTIFY_ONSNATCH: + self._notify(notifyStrings[NOTIFY_SNATCH], ep_name) + + 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 update_library(self, showName=None): + pass + notifier = BoxcarNotifier diff --git a/sickbeard/notifiers/growl.py b/sickbeard/notifiers/growl.py index 72577489b9..9d93bba727 100644 --- a/sickbeard/notifiers/growl.py +++ b/sickbeard/notifiers/growl.py @@ -17,7 +17,6 @@ # along with Sick Beard. If not, see . import socket - import sickbeard from sickbeard import logger, common @@ -25,153 +24,166 @@ from lib.growl import gntp -class GrowlNotifier: - - def test_notify(self, host, password): - self._sendRegistration(host, password, 'Test') - return self._sendGrowl("Test Growl", "Testing Growl settings from Sick Beard", "Test", host, password, force=True) - - def notify_snatch(self, ep_name): - if sickbeard.GROWL_NOTIFY_ONSNATCH: - self._sendGrowl(common.notifyStrings[common.NOTIFY_SNATCH], ep_name) - def notify_download(self, ep_name): - if sickbeard.GROWL_NOTIFY_ONDOWNLOAD: - self._sendGrowl(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name) +class GrowlNotifier: - def _send_growl(self, options,message=None): - - #Send Notification + def _send_growl(self, options, message=None): + # send notification notice = gntp.GNTPNotice() - + #Required - notice.add_header('Application-Name',options['app']) - notice.add_header('Notification-Name',options['name']) - notice.add_header('Notification-Title',options['title']) - + notice.add_header('Application-Name', options['app']) + notice.add_header('Notification-Name', options['name']) + notice.add_header('Notification-Title', options['title']) + if options['password']: notice.set_password(options['password']) - - #Optional + + # optional if options['sticky']: - notice.add_header('Notification-Sticky',options['sticky']) + notice.add_header('Notification-Sticky', options['sticky']) if options['priority']: - notice.add_header('Notification-Priority',options['priority']) + notice.add_header('Notification-Priority', options['priority']) if options['icon']: - notice.add_header('Notification-Icon', 'https://raw.github.com/midgetspy/Sick-Beard/master/data/images/sickbeard.png') - + notice.add_header('Notification-Icon', 'http://www.sickbeard.com/notify.png') + if message: - notice.add_header('Notification-Text',message) + notice.add_header('Notification-Text', message) + + response = self._send(options['host'], options['port'], notice.encode(), options['debug']) + if isinstance(response, gntp.GNTPOK): + return True - response = self._send(options['host'],options['port'],notice.encode(),options['debug']) - if isinstance(response,gntp.GNTPOK): return True return False - def _send(self, host,port,data,debug=False): - if debug: print '\n',data,'\n' - + def _send(self, host, port, data, debug=False): + if debug: + print '\n', data, '\n' + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((host,port)) + s.connect((host, port)) s.send(data) response = gntp.parse_gntp(s.recv(1024)) s.close() - - if debug: print '\n',response,'\n' + + if debug: + print '\n', response, '\n' return response - def _sendGrowl(self, title="Sick Beard Notification", message=None, name=None, host=None, password=None, force=False): + def _notify(self, title="Sick Beard Notification", message=None, name=None, host=None, password=None, force=False): + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_GROWL and not force: return False - - if name == None: + + # fill in omitted parameters + if not name: name = title - - if host == None: + + if not host: hostParts = sickbeard.GROWL_HOST.split(':') else: hostParts = host.split(':') - + if len(hostParts) != 2 or hostParts[1] == '': port = 23053 else: port = int(hostParts[1]) - - growlHosts = [(hostParts[0],port)] - + + growlHosts = [(hostParts[0], port)] + opts = {} - + opts['name'] = name - + opts['title'] = title opts['app'] = 'SickBeard' - + opts['sticky'] = None opts['priority'] = None opts['debug'] = False - - if password == None: + + if not password: opts['password'] = sickbeard.GROWL_PASSWORD else: opts['password'] = password - + opts['icon'] = True - - + + # TODO: Multi hosts does not seem to work... registration only happens for the first for pc in growlHosts: + print pc opts['host'] = pc[0] opts['port'] = pc[1] - logger.log(u"Sending growl to "+opts['host']+":"+str(opts['port'])+": "+message) + logger.log(u"GROWL: Sending message '" + message + "' to " + opts['host'] + ":" + str(opts['port'])) try: return self._send_growl(opts, message) - except socket.error, e: - logger.log(u"Unable to send growl to "+opts['host']+":"+str(opts['port'])+": "+ex(e)) + except Exception, e: + logger.log(u"GROWL: Unable to send growl to " + opts['host'] + ":" + str(opts['port']) + " - " + ex(e)) return False - def _sendRegistration(self, host=None, password=None, name='Sick Beard Notification'): + def _sendRegistration(self, host=None, password=None, name="Sick Beard Notification"): opts = {} - - if host == None: + + if not host: hostParts = sickbeard.GROWL_HOST.split(':') else: hostParts = host.split(':') - + if len(hostParts) != 2 or hostParts[1] == '': port = 23053 else: port = int(hostParts[1]) - + opts['host'] = hostParts[0] opts['port'] = port - - - if password == None: + + if not password: opts['password'] = sickbeard.GROWL_PASSWORD else: opts['password'] = password - - + opts['app'] = 'SickBeard' opts['debug'] = False - - #Send Registration + + # send registration register = gntp.GNTPRegister() register.add_header('Application-Name', opts['app']) - register.add_header('Application-Icon', 'https://raw.github.com/midgetspy/Sick-Beard/master/data/images/sickbeard.png') - + register.add_header('Application-Icon', 'http://www.sickbeard.com/notify.png') + register.add_notification('Test', True) register.add_notification(common.notifyStrings[common.NOTIFY_SNATCH], True) register.add_notification(common.notifyStrings[common.NOTIFY_DOWNLOAD], True) if opts['password']: register.set_password(opts['password']) - + try: - return self._send(opts['host'],opts['port'],register.encode(),opts['debug']) - except socket.error, e: - logger.log(u"Unable to send growl to "+opts['host']+":"+str(opts['port'])+": "+str(e).decode('utf-8')) + 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)) return False - - - + +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.GROWL_NOTIFY_ONSNATCH: + self._notify(common.notifyStrings[common.NOTIFY_SNATCH], ep_name) + + def notify_download(self, ep_name): + if sickbeard.GROWL_NOTIFY_ONDOWNLOAD: + self._notify(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name) + + def test_notify(self, host, password): + result = self._sendRegistration(host, password, "Test") + if result: + return self._notify("Test Growl", "Testing Growl settings from Sick Beard", "Test", host, password, force=True) + else: + return result + + def update_library(self, showName=None): + pass + notifier = GrowlNotifier diff --git a/sickbeard/notifiers/libnotify.py b/sickbeard/notifiers/libnotify.py index a95f7d9dee..d36c8fae0a 100644 --- a/sickbeard/notifiers/libnotify.py +++ b/sickbeard/notifiers/libnotify.py @@ -22,13 +22,14 @@ from sickbeard import logger, common + def diagnose(): ''' Check the environment for reasons libnotify isn't working. Return a user-readable message indicating possible issues. ''' try: - import pynotify #@UnusedImport + import pynotify # @UnusedImport except ImportError: return (u"

Error: pynotify isn't installed. On Ubuntu/Debian, install the " u"python-notify package.") @@ -66,41 +67,33 @@ def init_pynotify(self): try: import pynotify except ImportError: - logger.log(u"Unable to import pynotify. libnotify notifications won't work.") + logger.log(u"LIBNOTIFY: Unable to import pynotify. libnotify notifications won't work.") return False try: import gobject except ImportError: - logger.log(u"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.") return False if not pynotify.init('Sick Beard'): - logger.log(u"Initialization of pynotify failed. libnotify notifications won't work.") + logger.log(u"LIBNOTIFY: Initialization of pynotify failed. libnotify notifications won't work.") return False self.pynotify = pynotify self.gobject = gobject return True - def notify_snatch(self, ep_name): - if sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH: - self._notify(common.notifyStrings[common.NOTIFY_SNATCH], ep_name) - - def notify_download(self, ep_name): - if sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD: - self._notify(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_name) - - def test_notify(self): - return self._notify('Test notification', "This is a test notification from Sick Beard", force=True) - def _notify(self, title, message, force=False): + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_LIBNOTIFY and not force: return False + + # detect if we can use pynotify if not self.init_pynotify(): return False # Can't make this a global constant because PROG_DIR isn't available # when the module is imported. icon_path = os.path.join(sickbeard.PROG_DIR, "data/images/sickbeard_touch_icon.png") - icon_uri = 'file://' + os.path.abspath(icon_path) + icon_uri = "file://" + os.path.abspath(icon_path) # If the session bus can't be acquired here a bunch of warning messages # will be printed but the call to show() will still return True. @@ -111,4 +104,22 @@ def _notify(self, title, message, force=False): except self.gobject.GError: return False +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.LIBNOTIFY_NOTIFY_ONSNATCH: + self._notify(common.notifyStrings[common.NOTIFY_SNATCH], ep_name) + + def notify_download(self, ep_name): + if sickbeard.LIBNOTIFY_NOTIFY_ONDOWNLOAD: + self._notify(common.notifyStrings[common.NOTIFY_DOWNLOAD], 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): + pass + notifier = LibnotifyNotifier diff --git a/sickbeard/notifiers/nma.py b/sickbeard/notifiers/nma.py index a85212715b..ab9e4c2d5c 100644 --- a/sickbeard/notifiers/nma.py +++ b/sickbeard/notifiers/nma.py @@ -3,50 +3,60 @@ from sickbeard import logger, common from lib.pynma import pynma -class NMA_Notifier: - - 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 notify_snatch(self, ep_name): - if sickbeard.NMA_NOTIFY_ONSNATCH: - self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name) +class NMA_Notifier: - def notify_download(self, ep_name): - if sickbeard.NMA_NOTIFY_ONDOWNLOAD: - self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=ep_name) - 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: return False - + if nma_api == None: nma_api = sickbeard.NMA_API - + 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) - + + 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() keys = nma_api.split(',') p.addkey(keys) - - if len(keys) > 1: batch = True - + + if len(keys) > 1: + batch = True + response = p.push(title, event, message, priority=nma_priority, batch_mode=batch) - + if not response[nma_api][u'code'] == u'200': - logger.log(u'Could not send notification to NotifyMyAndroid', logger.ERROR) + logger.log(u"NMA: Could not send notification to NotifyMyAndroid", logger.ERROR) return False else: return True - -notifier = NMA_Notifier \ No newline at end of file + +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.NMA_NOTIFY_ONSNATCH: + self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name) + + def notify_download(self, ep_name): + if sickbeard.NMA_NOTIFY_ONDOWNLOAD: + self._sendNMA(nma_api=None, nma_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=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): + pass + +notifier = NMA_Notifier diff --git a/sickbeard/notifiers/nmj.py b/sickbeard/notifiers/nmj.py index 4a05815592..2171103310 100644 --- a/sickbeard/notifiers/nmj.py +++ b/sickbeard/notifiers/nmj.py @@ -16,7 +16,8 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -import urllib, urllib2 +import urllib +import urllib2 import sickbeard import telnetlib import re @@ -32,23 +33,23 @@ class NMJNotifier: def notify_settings(self, host): """ - Retrieves the settings from a NMJ/Popcorn hour - + Retrieves the settings from a NMJ/Popcorn Hour + host: The hostname/IP of the Popcorn Hour server - + Returns: True if the settings were retrieved successfully, False otherwise """ - + # establish a terminal session to the PC terminal = False try: terminal = telnetlib.Telnet(host) except Exception: - logger.log(u"Warning: unable to get a telnet session to %s" % (host), logger.ERROR) + logger.log(u"NMJ: Warning: unable to get a telnet session to %s" % (host), logger.ERROR) return False # tell the terminal to output the necessary info to the screen so we can search it later - logger.log(u"Connected to %s via telnet" % (host), logger.DEBUG) + logger.log(u"NMJ: Connected to %s via telnet" % (host), logger.DEBUG) terminal.read_until("sh-3.00# ") terminal.write("cat /tmp/source\n") terminal.write("cat /tmp/netshare\n") @@ -63,56 +64,46 @@ def notify_settings(self, host): if match: database = match.group(1) device = match.group(2) - logger.log(u"Found NMJ database %s on device %s" % (database, device), logger.DEBUG) + logger.log(u"NMJ: Found NMJ database %s on device %s" % (database, device), logger.DEBUG) sickbeard.NMJ_DATABASE = database else: - logger.log(u"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.ERROR) return False - + # if the device is a remote host then try to parse the mounting URL and save it to the config if device.startswith("NETWORK_SHARE/"): match = re.search(".*(?=\r\n?%s)" % (re.escape(device[14:])), tnoutput) if match: mount = match.group().replace("127.0.0.1", host) - logger.log(u"Found mounting url on the Popcorn Hour in configuration: %s" % (mount), logger.DEBUG) + 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"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.DEBUG) return False return True - - def notify_snatch(self, ep_name): - return False - #Not implemented: Start the scanner when snatched does not make any sense - - def notify_download(self, ep_name): - if sickbeard.USE_NMJ: - self._notifyNMJ() - - def test_notify(self, host, database, mount): - return self._sendNMJ(host, database, mount) def _sendNMJ(self, host, database, mount=None): """ Sends a NMJ update command to the specified machine - + host: The hostname/IP to send the request to (no port) - database: The database to send the requst to + database: The database to send the request to mount: The mount URL to use (optional) - + Returns: True if the request succeeded, False otherwise """ - + # 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"Try to mount network drive via url: %s" % (mount), logger.DEBUG) + 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"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e)) + logger.log(u"NMJ: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) return False # build up the request URL and parameters @@ -127,12 +118,13 @@ def _sendNMJ(self, host, database, mount=None): # send the request to the server try: + # TODO: Use our getURL from helper? req = urllib2.Request(updateUrl) - logger.log(u"Sending NMJ scan update command via url: %s" % (updateUrl), logger.DEBUG) + 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"Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) + logger.log(u"NMJ: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) return False # try to parse the resulting XML @@ -140,28 +132,28 @@ def _sendNMJ(self, host, database, mount=None): et = etree.fromstring(response) result = et.findtext("returnValue") except SyntaxError, e: - logger.log(u"Unable to parse XML returned from the Popcorn Hour: %s" % (e), logger.ERROR) + logger.log(u"NMJ: Unable to parse XML returned from the Popcorn Hour: %s" % (e), logger.ERROR) return False - + # if the result was a number then consider that an error if int(result) > 0: - logger.log(u"Popcorn Hour returned an errorcode: %s" % (result)) + logger.log(u"NMJ: Popcorn Hour returned an errorcode: %s" % (result)) return False else: - logger.log(u"NMJ started background scan") + logger.log(u"NMJ: Started background scan.") return True def _notifyNMJ(self, host=None, database=None, mount=None, force=False): """ Sends a NMJ update command based on the SB config settings - + host: The host to send the command to (optional, defaults to the host in the config) database: The database to use (optional, defaults to the database in the config) mount: The mount URL (optional, defaults to the mount URL in the config) force: If True then the notification will be sent even if NMJ is disabled in the config """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_NMJ and not force: - logger.log("Notification for NMJ scan update not enabled, skipping this notification", logger.DEBUG) return False # fill in omitted parameters @@ -172,8 +164,27 @@ def _notifyNMJ(self, host=None, database=None, mount=None, force=False): if not mount: mount = sickbeard.NMJ_MOUNT - logger.log(u"Sending scan command for NMJ ", logger.DEBUG) + logger.log(u"NMJ: Sending scan command.", logger.DEBUG) return self._sendNMJ(host, database, mount) +############################################################################## +# Public functions +############################################################################## + + 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() + + def test_notify(self, host, database, mount): + return self._sendNMJ(host, database, mount) + + def update_library(self, showName=None): + if sickbeard.USE_NMJ: + self._notifyNMJ() + notifier = NMJNotifier diff --git a/sickbeard/notifiers/nmjv2.py b/sickbeard/notifiers/nmjv2.py index c5f096f54a..679381ae39 100644 --- a/sickbeard/notifiers/nmjv2.py +++ b/sickbeard/notifiers/nmjv2.py @@ -17,11 +17,9 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -import urllib, urllib2,xml.dom.minidom +import urllib2 from xml.dom.minidom import parseString import sickbeard -import telnetlib -import re import time from sickbeard import logger @@ -33,140 +31,154 @@ class NMJv2Notifier: - - def notify_snatch(self, ep_name): - return False - #Not implemented: Start the scanner when snatched does not make any sense - - def notify_download(self, ep_name): - self._notifyNMJ() - - def test_notify(self, host): - return self._sendNMJ(host) def notify_settings(self, host, dbloc, instance): """ - Retrieves the NMJv2 database location from Popcorn hour - + Retrieves the NMJv2 database location from Popcorn Hour + host: The hostname/IP of the Popcorn Hour server dbloc: 'local' for PCH internal harddrive. 'network' for PCH network shares instance: Allows for selection of different DB in case of multiple databases - + Returns: True if the settings were retrieved successfully, False otherwise """ 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" + 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() + # TODO: convert to etree? xml = parseString(response1) - time.sleep (300.0 / 1000.0) + time.sleep(0.5) for node in xml.getElementsByTagName('path'): - xmlTag=node.toxml(); - xmlData=xmlTag.replace('','').replace('','').replace('[=]','') - url_db = "http://" + host + ":8008/metadata_database?arg0=check_database&arg1="+ xmlData + xmlTag = node.toxml() + xmlData = xmlTag.replace('', '').replace('', '').replace('[=]', '') + url_db = "http://" + host + ":8008/metadata_database?arg0=check_database&arg1=" + xmlData reqdb = urllib2.Request(url_db) handledb = urllib2.urlopen(reqdb) responsedb = handledb.read() xmldb = parseString(responsedb) - returnvalue=xmldb.getElementsByTagName('returnValue')[0].toxml().replace('','').replace('','') - if returnvalue=="0": - DB_path=xmldb.getElementsByTagName('database_path')[0].toxml().replace('','').replace('','').replace('[=]','') - if dbloc=="local" and DB_path.find("localhost")>-1: - sickbeard.NMJv2_HOST=host - sickbeard.NMJv2_DATABASE=DB_path + returnvalue = xmldb.getElementsByTagName('returnValue')[0].toxml().replace('', '').replace('', '') + if returnvalue == "0": + DB_path = xmldb.getElementsByTagName('database_path')[0].toxml().replace('', '').replace('', '').replace('[=]', '') + if dbloc == "local" and DB_path.find("localhost") > -1: + sickbeard.NMJv2_HOST = host + sickbeard.NMJv2_DATABASE = DB_path return True - if dbloc=="network" and DB_path.find("://")>-1: - sickbeard.NMJv2_HOST=host - sickbeard.NMJv2_DATABASE=DB_path + if dbloc == "network" and DB_path.find("://") > -1: + sickbeard.NMJv2_HOST = host + sickbeard.NMJv2_DATABASE = DB_path return True - except IOError, e: - logger.log(u"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e)) + logger.log(u"NMJv2: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) return False + return False def _sendNMJ(self, host): """ Sends a NMJ update command to the specified machine - + host: The hostname/IP to send the request to (no port) - database: The database to send the requst to + database: The database to send the request to mount: The mount URL to use (optional) - + Returns: True if the request succeeded, False otherwise """ - + #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"NMJ scan update command send to host: %s" % (host)) - url_updatedb = "http://" + host + ":8008/metadata_database?arg0=scanner_start&arg1="+ sickbeard.NMJv2_DATABASE +"&arg2=background&arg3=" + 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)) + 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) prereq = urllib2.Request(url_scandir) req = urllib2.Request(url_updatedb) handle1 = urllib2.urlopen(prereq) response1 = handle1.read() - time.sleep (300.0 / 1000.0) + time.sleep(0.5) handle2 = urllib2.urlopen(req) response2 = handle2.read() except IOError, e: - logger.log(u"Warning: Couldn't contact popcorn hour on host %s: %s" % (host, e)) + logger.log(u"NMJv2: Warning: Couldn't contact Popcorn Hour on host %s: %s" % (host, e)) return False - try: + + try: et = etree.fromstring(response1) result1 = et.findtext("returnValue") except SyntaxError, e: - logger.log(u"Unable to parse XML returned from the Popcorn Hour: update_scandir, %s" % (e), logger.ERROR) - return False - try: + logger.log(u"NMJv2: Unable to parse XML returned from the Popcorn Hour: update_scandir, %s" % (e), logger.ERROR) + return False + + try: et = etree.fromstring(response2) result2 = et.findtext("returnValue") except SyntaxError, e: - logger.log(u"Unable to parse XML returned from the Popcorn Hour: scanner_start, %s" % (e), logger.ERROR) + logger.log(u"NMJv2: Unable to parse XML returned from the Popcorn Hour: scanner_start, %s" % (e), logger.ERROR) return False - + # if the result was a number then consider that an error - error_codes=["8","11","22","49","50","51","60"] - error_messages=["Invalid parameter(s)/argument(s)", + error_codes = ["8", "11", "22", "49", "50", "51", "60"] + error_messages = ["Invalid parameter(s)/argument(s)", "Invalid database path", "Insufficient size", "Database write error", "Database read error", "Open fifo pipe failed", "Read only file system"] + if int(result1) > 0: - index=error_codes.index(result1) - logger.log(u"Popcorn Hour returned an error: %s" % (error_messages[index])) + index = error_codes.index(result1) + logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index])) return False else: if int(result2) > 0: - index=error_codes.index(result2) - logger.log(u"Popcorn Hour returned an error: %s" % (error_messages[index])) + index = error_codes.index(result2) + logger.log(u"NMJv2: Popcorn Hour returned an error: %s" % (error_messages[index])) return False else: - logger.log(u"NMJv2 started background scan") + logger.log(u"NMJv2: Started background scan.") return True def _notifyNMJ(self, host=None, force=False): """ Sends a NMJ update command based on the SB config settings - + host: The host to send the command to (optional, defaults to the host in the config) database: The database to use (optional, defaults to the database in the config) mount: The mount URL (optional, defaults to the mount URL in the config) force: If True then the notification will be sent even if NMJ is disabled in the config """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_NMJv2 and not force: - logger.log("Notification for NMJ scan update not enabled, skipping this notification", logger.DEBUG) return False # fill in omitted parameters if not host: host = sickbeard.NMJv2_HOST - logger.log(u"Sending scan command for NMJ ", logger.DEBUG) + logger.log(u"NMJv2: Sending scan command.", logger.DEBUG) return self._sendNMJ(host) +############################################################################## +# Public functions +############################################################################## + + 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() + + def test_notify(self, host): + return self._sendNMJ(host) + + def update_library(self, showName=None): + if sickbeard.USE_NMJv2: + self._notifyNMJ() + notifier = NMJv2Notifier diff --git a/sickbeard/notifiers/plex.py b/sickbeard/notifiers/plex.py index 4d1b4362ef..39c3764472 100644 --- a/sickbeard/notifiers/plex.py +++ b/sickbeard/notifiers/plex.py @@ -27,8 +27,10 @@ from sickbeard.exceptions import ex from sickbeard.encodingKludge import fixStupidEncodings -# TODO: switch over to using ElementTree -from xml.dom import minidom +try: + import xml.etree.cElementTree as etree +except ImportError: + import elementtree.ElementTree as etree class PLEXNotifier: @@ -54,7 +56,7 @@ def _send_to_plex(self, command, host, username=None, password=None): password = sickbeard.PLEX_PASSWORD if not host: - logger.log(u"No Plex host specified, check your settings", logger.DEBUG) + logger.log(u"PLEX: No Plex host specified, check your settings", logger.DEBUG) return False for key in command: @@ -62,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 encoded API command: " + enc_command, logger.DEBUG) + logger.log(u"PLEX: Plex encoded API command: " + enc_command, logger.DEBUG) url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command) try: @@ -72,24 +74,25 @@ 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"Contacting Plex (with auth header) via url: " + url, logger.DEBUG) + logger.log(u"PLEX: Contacting Plex (with auth header) via url: " + url, logger.DEBUG) else: - logger.log(u"Contacting Plex via url: " + url, logger.DEBUG) + logger.log(u"PLEX: Contacting Plex 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 HTTP response: " + result.replace('\n', ''), logger.DEBUG) + logger.log(u"PLEX: Plex HTTP response: " + result.replace('\n', ''), logger.DEBUG) # could return result response = re.compile('

  • (.+\w)').findall(result) return 'OK' except (urllib2.URLError, IOError), e: - logger.log(u"Warning: Couldn't contact Plex at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) + logger.log(u"PLEX: Warning: Couldn't contact Plex at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) return False - def _notify_pmc(self, message, title="Sick Beard", host=None, username=None, password=None, force=False): + def _notify(self, message, title="Sick Beard", host=None, username=None, password=None, force=False): """Internal wrapper for the notify_snatch and notify_download functions Args: @@ -105,6 +108,9 @@ def _notify_pmc(self, message, title="Sick Beard", host=None, username=None, pas The result will either be 'OK' or False, this is used to be parsed by the calling function. """ + # suppress notifications if the notifier is disabled but the notify options are checked + if not sickbeard.USE_PLEX and not force: + return False # fill in omitted parameters if not host: @@ -114,14 +120,9 @@ def _notify_pmc(self, message, title="Sick Beard", host=None, username=None, pas if not password: password = sickbeard.PLEX_PASSWORD - # suppress notifications if the notifier is disabled but the notify options are checked - if not sickbeard.USE_PLEX and not force: - logger.log("Notification for Plex not enabled, skipping this notification", logger.DEBUG) - return False - result = '' for curHost in [x.strip() for x in host.split(",")]: - logger.log(u"Sending Plex notification to '" + curHost + "' - " + message, logger.MESSAGE) + logger.log(u"PLEX: Sending Plex 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) @@ -136,14 +137,14 @@ def _notify_pmc(self, message, title="Sick Beard", host=None, username=None, pas def notify_snatch(self, ep_name): if sickbeard.PLEX_NOTIFY_ONSNATCH: - self._notify_pmc(ep_name, common.notifyStrings[common.NOTIFY_SNATCH]) + self._notify(ep_name, common.notifyStrings[common.NOTIFY_SNATCH]) def notify_download(self, ep_name): if sickbeard.PLEX_NOTIFY_ONDOWNLOAD: - self._notify_pmc(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD]) + self._notify(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD]) def test_notify(self, host, username, password): - return self._notify_pmc("Testing Plex notifications from Sick Beard", "Test Notification", host, username, password, force=True) + return self._notify("Testing Plex notifications from Sick Beard", "Test Notification", host, username, password, force=True) def update_library(self): """Handles updating the Plex Media Server host via HTTP API @@ -157,30 +158,32 @@ def update_library(self): if sickbeard.USE_PLEX and sickbeard.PLEX_UPDATE_LIBRARY: if not sickbeard.PLEX_SERVER_HOST: - logger.log(u"No Plex Server host specified, check your settings", logger.DEBUG) + logger.log(u"PLEX: No Plex Server host specified, check your settings", logger.DEBUG) return False - logger.log(u"Updating library for the Plex Media Server host: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE) + 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: - xml_sections = minidom.parse(urllib.urlopen(url)) + # TODO: Use our getURL from helper? + xml_tree = etree.parse(urllib.urlopen(url)) + media_container = xml_tree.getroot() except IOError, e: - logger.log(u"Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR) + logger.log(u"PLEX: Error while trying to contact Plex Media Server: " + ex(e), logger.ERROR) return False - sections = xml_sections.getElementsByTagName('Directory') + sections = media_container.findall('.//Directory') if not sections: - logger.log(u"Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE) + logger.log(u"PLEX: Plex Media Server not running on: " + sickbeard.PLEX_SERVER_HOST, logger.MESSAGE) return False - for s in sections: - if s.getAttribute('type') == "show": - url = "http://%s/library/sections/%s/refresh" % (sickbeard.PLEX_SERVER_HOST, s.getAttribute('key')) + for section in sections: + if section.attrib['type'] == "show": + url = "http://%s/library/sections/%s/refresh" % (sickbeard.PLEX_SERVER_HOST, section.attrib['key']) try: urllib.urlopen(url) except Exception, e: - logger.log(u"Error updating library section for Plex Media Server: " + ex(e), logger.ERROR) + logger.log(u"PLEX: Error updating library section for Plex Media Server: " + ex(e), logger.ERROR) return False return True diff --git a/sickbeard/notifiers/prowl.py b/sickbeard/notifiers/prowl.py index 709ea95ca5..74cf9657ae 100644 --- a/sickbeard/notifiers/prowl.py +++ b/sickbeard/notifiers/prowl.py @@ -16,81 +16,89 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -from httplib import HTTPSConnection, HTTPException +from httplib import HTTPSConnection from urllib import urlencode -try: - # this only exists in 2.6 - from ssl import SSLError -except ImportError: - # make a fake one since I don't know what it is supposed to be in 2.5 - class SSLError(Exception): - pass - import sickbeard -from sickbeard import logger, common +from sickbeard.exceptions import ex +from sickbeard import common +from sickbeard import logger -class ProwlNotifier: - def test_notify(self, prowl_api, prowl_priority): - return self._sendProwl(prowl_api, prowl_priority, event="Test", message="Testing Prowl settings from Sick Beard", force=True) +class ProwlNotifier: - def notify_snatch(self, ep_name): - if sickbeard.PROWL_NOTIFY_ONSNATCH: - self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name) + def _notify(self, prowl_api=None, prowl_priority=None, event=None, message=None, force=False): - def notify_download(self, ep_name): - if sickbeard.PROWL_NOTIFY_ONDOWNLOAD: - self._sendProwl(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=ep_name) - - def _sendProwl(self, prowl_api=None, prowl_priority=None, event=None, message=None, force=False): - + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_PROWL and not force: - return False - - if prowl_api == None: + return False + + # fill in omitted parameters + if not prowl_api: prowl_api = sickbeard.PROWL_API - - if prowl_priority == None: + if not prowl_priority: prowl_priority = sickbeard.PROWL_PRIORITY - - + title = "Sick Beard" - - 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) - - http_handler = HTTPSConnection("api.prowlapp.com") - - data = {'apikey': prowl_api, - 'application': title, - 'event': event, - 'description': message.encode('utf-8'), - 'priority': prowl_priority } + + # 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) try: + + http_handler = HTTPSConnection("api.prowlapp.com") + + data = {'apikey': prowl_api, + 'application': title, + 'event': event, + 'description': message.encode('utf-8'), + 'priority': prowl_priority + } + http_handler.request("POST", - "/publicapi/add", - headers = {'Content-type': "application/x-www-form-urlencoded"}, - body = urlencode(data)) - except (SSLError, HTTPException): - logger.log(u"Prowl notification failed.", logger.ERROR) + "/publicapi/add", + headers={'Content-type': "application/x-www-form-urlencoded"}, + body=urlencode(data) + ) + + response = http_handler.getresponse() + request_status = response.status + + except Exception, e: + logger.log(u"PROWL: Notification failed: " + ex(e), logger.ERROR) return False - response = http_handler.getresponse() - request_status = response.status if request_status == 200: - logger.log(u"Prowl notifications sent.", logger.DEBUG) - return True - elif request_status == 401: - logger.log(u"Prowl auth failed: %s" % response.reason, logger.ERROR) - return False + logger.log(u"PROWL: Notifications sent.", logger.DEBUG) + return True + elif request_status == 401: + logger.log(u"PROWL: Auth failed: %s" % response.reason, logger.ERROR) + return False else: - logger.log(u"Prowl notification failed.", logger.ERROR) - return False - + logger.log(u"PROWL: Notification failed.", logger.ERROR) + return False + +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.PROWL_NOTIFY_ONSNATCH: + self._notify(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH], message=ep_name) + + def notify_download(self, ep_name): + if sickbeard.PROWL_NOTIFY_ONDOWNLOAD: + self._notify(prowl_api=None, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=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): + pass + notifier = ProwlNotifier diff --git a/sickbeard/notifiers/pushover.py b/sickbeard/notifiers/pushover.py index 4e6cd5d6ba..386a194d7f 100644 --- a/sickbeard/notifiers/pushover.py +++ b/sickbeard/notifiers/pushover.py @@ -18,7 +18,8 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . -import urllib, urllib2 +import urllib +import urllib2 import time import sickbeard @@ -30,25 +31,23 @@ API_URL = "https://api.pushover.net/1/messages.json" API_KEY = "OKCXmkvHN1syU2e8xvpefTnyvVWGv5" -class PushoverNotifier: - def test_notify(self, userKey=None): - return self._sendPushover("This is a test notification from SickBeard", 'Test', userKey ) +class PushoverNotifier: - def _sendPushover(self, msg, title, userKey=None ): + def _sendPushover(self, msg, title, userKey=None): """ Sends a pushover notification to the address provided - + msg: The message to send (unicode) title: The title of the message userKey: The pushover user id to send the message to (or to subscribe with) - + returns: True if the message succeeded, False otherwise """ if not userKey: userKey = sickbeard.PUSHOVER_USERKEY - + # build up the URL and parameters msg = msg.strip() curUrl = API_URL @@ -59,78 +58,86 @@ def _sendPushover(self, msg, title, userKey=None ): 'user': userKey, 'message': msg.encode('utf-8'), 'timestamp': int(time.time()) - }) - + }) # send the request to pushover try: + # TODO: Use our getURL from helper? req = urllib2.Request(curUrl) handle = urllib2.urlopen(req, data) handle.close() - + 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("Pushover notification failed." + ex(e), logger.ERROR) + logger.log(u"PUSHOVER: Notification failed." + ex(e), logger.ERROR) return False else: - logger.log("Pushover notification failed. Error code: " + str(e.code), logger.WARNING) + logger.log(u"PUSHOVER: Notification failed. Error code: " + str(e.code), logger.WARNING) # HTTP status 404 if the provided email address isn't a Pushover user. if e.code == 404: - logger.log("Username is wrong/not a pushover email. Pushover will send an email to it", logger.WARNING) + logger.log(u"PUSHOVER: Username is wrong/not a Pushover email. Pushover will send an email to it", logger.WARNING) return False - + # For HTTP status code 401's, it is because you are passing in either an invalid token, or the user has not added your service. elif e.code == 401: - + #HTTP status 401 if the user doesn't have the service added subscribeNote = self._sendPushover(msg, title, userKey ) if subscribeNote: - logger.log("Subscription send", logger.DEBUG) + logger.log(u"PUSHOVER: Subscription sent", logger.DEBUG) return True else: - logger.log("Subscription could not be send", logger.ERROR) + logger.log(u"PUSHOVER: Subscription could not be sent", logger.ERROR) return 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("Wrong data sent to pushover", logger.ERROR) + logger.log(u"PUSHOVER: Wrong data sent to Pushover", logger.ERROR) return False - logger.log("Pushover notification successful.", logger.DEBUG) + logger.log(u"PUSHOVER: Notification successful.", logger.DEBUG) return True - def notify_snatch(self, ep_name, title=notifyStrings[NOTIFY_SNATCH]): - if sickbeard.PUSHOVER_NOTIFY_ONSNATCH: - self._notifyPushover(title, ep_name) - - - def notify_download(self, ep_name, title=notifyStrings[NOTIFY_DOWNLOAD]): - if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD: - self._notifyPushover(title, ep_name) - - def _notifyPushover(self, title, message, userKey=None ): + def _notify(self, title, message, userKey=None ): """ Sends a pushover notification based on the provided info or SB config title: The title of the notification to send message: The message string to send - userKey: The userKey to send the notification to + userKey: The userKey to send the notification to """ + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_PUSHOVER: - logger.log("Notification for Pushover not enabled, skipping this notification", logger.DEBUG) return False - # if no userKey was given then use the one from the config + # fill in omitted parameters if not userKey: userKey = sickbeard.PUSHOVER_USERKEY - logger.log("Sending notification for " + message, logger.DEBUG) + logger.log(u"PUSHOVER: Sending notification for " + message, logger.DEBUG) - # self._sendPushover(message, title, userKey) self._sendPushover(message, title) return True +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.PUSHOVER_NOTIFY_ONSNATCH: + self._notify(notifyStrings[NOTIFY_SNATCH], ep_name) + + def notify_download(self, ep_name): + if sickbeard.PUSHOVER_NOTIFY_ONDOWNLOAD: + 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) + + def update_library(self, showName=None): + pass + notifier = PushoverNotifier diff --git a/sickbeard/notifiers/pytivo.py b/sickbeard/notifiers/pytivo.py index 3f4aa30b48..6172fa2f3b 100644 --- a/sickbeard/notifiers/pytivo.py +++ b/sickbeard/notifiers/pytivo.py @@ -25,8 +25,13 @@ from sickbeard import logger from sickbeard import encodingKludge as ek + class pyTivoNotifier: +############################################################################## +# Public functions +############################################################################## + def notify_snatch(self, ep_name): pass @@ -35,65 +40,56 @@ def notify_download(self, ep_name): def update_library(self, ep_obj): - # Values from config - if not sickbeard.USE_PYTIVO: return False - + host = sickbeard.PYTIVO_HOST shareName = sickbeard.PYTIVO_SHARE_NAME tsn = sickbeard.PYTIVO_TIVO_NAME - + # There are two more values required, the container and file. - # + # # container: The share name, show name and season # # file: The file name - # - # Some slicing and dicing of variables is required to get at these values. # - # There might be better ways to arrive at the values, but this is the best I have been able to - # come up with. - # - - + # Some slicing and dicing of variables is required to get at these values. + # Calculated values - showPath = ep_obj.show.location showName = ep_obj.show.name - rootShowAndSeason = ek.ek(os.path.dirname, ep_obj.location) + rootShowAndSeason = ek.ek(os.path.dirname, ep_obj.location) absPath = ep_obj.location - + # Some show names have colons in them which are illegal in a path location, so strip them out. # (Are there other characters?) - showName = showName.replace(":","") - + showName = showName.replace(":", "") + root = showPath.replace(showName, "") showAndSeason = rootShowAndSeason.replace(root, "") - + container = shareName + "/" + showAndSeason - file = "/" + absPath.replace(root, "") - + mediaFile = "/" + absPath.replace(root, "") + # Finally create the url and make request - requestUrl = "http://" + host + "/TiVoConnect?" + urlencode( {'Command':'Push', 'Container':container, 'File':file, 'tsn':tsn} ) - - logger.log(u"pyTivo notification: Requesting " + requestUrl) - + requestUrl = "http://" + host + "/TiVoConnect?" + urlencode( {'Command': 'Push', 'Container': container, 'File': mediaFile, 'tsn': tsn}) + + logger.log(u"PYTIVO: Requesting " + requestUrl) + + # TODO: Use our getURL from helper? request = Request( requestUrl ) try: - response = urlopen(request) #@UnusedVariable + response = urlopen(request) # @UnusedVariable except URLError, e: if hasattr(e, 'reason'): - logger.log(u"pyTivo notification: Error, failed to reach a server") - logger.log(u"'Error reason: " + e.reason) + logger.log(u"PYTIVO: Error, failed to reach a server - " + str(e.reason)) return False elif hasattr(e, 'code'): - logger.log(u"pyTivo notification: Error, the server couldn't fulfill the request") - logger.log(u"Error code: " + e.code) + logger.log(u"PYTIVO: Error, the server couldn't fulfill the request - " + str(e.code)) return False else: - logger.log(u"pyTivo notification: Successfully requested transfer of file") + logger.log(u"PYTIVO: Successfully requested transfer of file") return True notifier = pyTivoNotifier diff --git a/sickbeard/notifiers/synoindex.py b/sickbeard/notifiers/synoindex.py index 2eaeedbfb7..3edcefd2dd 100755 --- a/sickbeard/notifiers/synoindex.py +++ b/sickbeard/notifiers/synoindex.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . - - import os import subprocess @@ -27,13 +25,8 @@ from sickbeard import encodingKludge as ek from sickbeard.exceptions import ex -class synoIndexNotifier: - - def notify_snatch(self, ep_name): - pass - def notify_download(self, ep_name): - pass +class synoIndexNotifier: def moveFolder(self, old_path, new_path): self.moveObject(old_path, new_path) @@ -44,14 +37,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"Executing command "+str(synoindex_cmd)) - logger.log(u"Absolute path to command: "+ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG) + logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd)) + 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"Script result: "+str(out), logger.DEBUG) + out, err = p.communicate() # @UnusedVariable + logger.log(u"SYNOINDEX: Script result: " + str(out), logger.DEBUG) except OSError, e: - logger.log(u"Unable to run synoindex: "+ex(e)) + logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e)) def deleteFolder(self, cur_path): self.makeObject('-D', cur_path) @@ -68,13 +61,23 @@ 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"Executing command "+str(synoindex_cmd)) - logger.log(u"Absolute path to command: "+ek.ek(os.path.abspath, synoindex_cmd[0]), logger.DEBUG) + logger.log(u"SYNOINDEX: Executing command " + str(synoindex_cmd)) + 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"Script result: "+str(out), logger.DEBUG) + out, err = p.communicate() # @UnusedVariable + logger.log(u"SYNOINDEX: Script result: " + str(out), logger.DEBUG) except OSError, e: - logger.log(u"Unable to run synoindex: "+ex(e)) + logger.log(u"SYNOINDEX: Unable to run synoindex: " + ex(e)) + +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + pass + + def notify_download(self, ep_name): + pass notifier = synoIndexNotifier diff --git a/sickbeard/notifiers/trakt.py b/sickbeard/notifiers/trakt.py index d5bcb65150..45cbed60f7 100644 --- a/sickbeard/notifiers/trakt.py +++ b/sickbeard/notifiers/trakt.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU General Public License # along with Sick Beard. If not, see . - import urllib2 from hashlib import sha1 @@ -30,126 +29,115 @@ from sickbeard import logger -class TraktNotifier: - """ - A "notifier" for trakt.tv which keeps track of what has and hasn't been added to your library. - """ - - def notify_snatch(self, ep_name): - pass - - def notify_download(self, ep_name): - pass - - def update_library(self, ep_obj): - """ - Sends a request to trakt indicating that the given episode is part of our library. - - ep_obj: The TVEpisode object to add to trakt - """ - - if sickbeard.USE_TRAKT: - method = "show/episode/library/" - method += "%API%" - - # URL parameters - data = { - 'tvdb_id': ep_obj.show.tvdbid, - 'title': ep_obj.show.name, - 'year': ep_obj.show.startyear, - 'episodes': [ { - 'season': ep_obj.season, - 'episode': ep_obj.episode - } ] - } - - if data is not None: - self._notifyTrakt(method, None, None, None, data) - def test_notify(self, api, username, password): - """ - Sends a test notification to trakt with the given authentication info and returns a boolean - representing success. - - api: The api string to use - username: The username to use - password: The password to use - - Returns: True if the request succeeded, False otherwise - """ - - method = "account/test/" - method += "%API%" - return self._notifyTrakt(method, api, username, password, {}) - - def _username(self): - return sickbeard.TRAKT_USERNAME - - def _password(self): - return sickbeard.TRAKT_PASSWORD - - def _api(self): - return sickbeard.TRAKT_API - - def _use_me(self): - return sickbeard.USE_TRAKT +class TraktNotifier: - def _notifyTrakt(self, method, api, username, password, data = {}): + def _notifyTrakt(self, method, api, username, password, data={}): """ A generic method for communicating with trakt. Uses the method and data provided along with the auth info to send the command. - + method: The URL to use at trakt, relative, no leading slash. api: The API string to provide to trakt username: The username to use when logging in password: The unencrypted password to use when logging in - + Returns: A boolean representing success """ - logger.log("trakt_notifier: Call method " + method, logger.DEBUG) + logger.log(u"TRAKT: Calling method " + method, logger.DEBUG) # if the API isn't given then use the config API if not api: - api = self._api() + api = sickbeard.TRAKT_API # if the username isn't given then use the config username if not username: - username = self._username() - + username = sickbeard.TRAKT_USERNAME + # if the password isn't given then use the config password if not password: - password = self._password() + password = sickbeard.TRAKT_PASSWORD password = sha1(password).hexdigest() - # replace the API string with what we found - method = method.replace("%API%", api) + # append apikey to method + method += api data["username"] = username data["password"] = password # take the URL params and make a json object out of them - encoded_data = json.dumps(data); + encoded_data = json.dumps(data) # request the URL from trakt and parse the result as json try: - logger.log("trakt_notifier: Calling method http://api.trakt.tv/" + method + ", with data" + encoded_data, logger.DEBUG) + 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() resp = json.loads(resp) - + if ("error" in resp): raise Exception(resp["error"]) except (IOError): - logger.log("trakt_notifier: Failed calling method", logger.ERROR) + logger.log(u"TRAKT: Failed calling method", logger.ERROR) return False if (resp["status"] == "success"): - logger.log("trakt_notifier: Succeeded calling method. Result: " + resp["message"], logger.DEBUG) + logger.log(u"TRAKT: Succeeded calling method. Result: " + resp["message"], logger.DEBUG) return True - logger.log("trakt_notifier: Failed calling method", logger.ERROR) + logger.log(u"TRAKT: Failed calling method", logger.ERROR) return False +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + pass + + def notify_download(self, ep_name): + pass + + def test_notify(self, api, username, password): + """ + Sends a test notification to trakt with the given authentication info and returns a boolean + representing success. + + api: The api string to use + username: The username to use + password: The password to use + + Returns: True if the request succeeded, False otherwise + """ + + method = "account/test/" + return self._notifyTrakt(method, api, username, password, {}) + + def update_library(self, ep_obj): + """ + Sends a request to trakt indicating that the given episode is part of our library. + + ep_obj: The TVEpisode object to add to trakt + """ + + if sickbeard.USE_TRAKT: + method = "show/episode/library/" + + # URL parameters + data = { + 'tvdb_id': ep_obj.show.tvdbid, + 'title': ep_obj.show.name, + 'year': ep_obj.show.startyear, + 'episodes': [ { + 'season': ep_obj.season, + 'episode': ep_obj.episode + } ] + } + + if data is not None: + self._notifyTrakt(method, None, None, None, data) + notifier = TraktNotifier diff --git a/sickbeard/notifiers/tweet.py b/sickbeard/notifiers/tweet.py index c56288038b..68f2ea2d4d 100644 --- a/sickbeard/notifiers/tweet.py +++ b/sickbeard/notifiers/tweet.py @@ -23,114 +23,120 @@ # parse_qsl moved to urlparse module in v2.6 try: - from urlparse import parse_qsl #@UnusedImport + from urlparse import parse_qsl # @UnusedImport except: - from cgi import parse_qsl #@Reimport + from cgi import parse_qsl # @Reimport import lib.oauth2 as oauth import lib.pythontwitter as twitter + class TwitterNotifier: consumer_key = "vHHtcB6WzpWDG6KYlBMr8g" consumer_secret = "zMqq5CB3f8cWKiRO2KzWPTlBanYmV0VYxSXZ0Pxds0E" - - REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token' - ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token' - AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authorize' - SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate' - - def notify_snatch(self, ep_name): - if sickbeard.TWITTER_NOTIFY_ONSNATCH: - self._notifyTwitter(common.notifyStrings[common.NOTIFY_SNATCH]+': '+ep_name) - - def notify_download(self, ep_name): - if sickbeard.TWITTER_NOTIFY_ONDOWNLOAD: - self._notifyTwitter(common.notifyStrings[common.NOTIFY_DOWNLOAD]+': '+ep_name) - def test_notify(self): - return self._notifyTwitter("This is a test notification from Sick Beard", force=True) + REQUEST_TOKEN_URL = "https://api.twitter.com/oauth/request_token" + ACCESS_TOKEN_URL = "https://api.twitter.com/oauth/access_token" + AUTHORIZATION_URL = "https://api.twitter.com/oauth/authorize" + SIGNIN_URL = "https://api.twitter.com/oauth/authenticate" def _get_authorization(self): - - signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable - oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) - oauth_client = oauth.Client(oauth_consumer) - - logger.log('Requesting temp token from Twitter') - + + signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() # @UnusedVariable + 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') + resp, content = oauth_client.request(self.REQUEST_TOKEN_URL, 'GET') - + if resp['status'] != '200': - logger.log('Invalid respond from Twitter requesting temp token: %s' % resp['status']) + logger.log(u"TWITTER: Invalid respond from Twitter requesting temp token: %s" % resp['status']) else: request_token = dict(parse_qsl(content)) - + sickbeard.TWITTER_USERNAME = request_token['oauth_token'] sickbeard.TWITTER_PASSWORD = request_token['oauth_token_secret'] - - return self.AUTHORIZATION_URL+"?oauth_token="+ request_token['oauth_token'] - + + return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token'] + def _get_credentials(self, key): request_token = {} - + request_token['oauth_token'] = sickbeard.TWITTER_USERNAME request_token['oauth_token_secret'] = sickbeard.TWITTER_PASSWORD request_token['oauth_callback_confirmed'] = 'true' - + token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret']) token.set_verifier(key) - - logger.log('Generating and signing request for an access token using key '+key) - - signature_method_hmac_sha1 = oauth.SignatureMethod_HMAC_SHA1() #@UnusedVariable - oauth_consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret) - logger.log('oauth_consumer: '+str(oauth_consumer)) - oauth_client = oauth.Client(oauth_consumer, token) - logger.log('oauth_client: '+str(oauth_client)) + + logger.log(u"TWITTER: Generating and signing request for an access token using key " + key) + + 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)) + oauth_client = oauth.Client(oauth_consumer, token) + logger.log(u"TWITTER: oauth_client: " + str(oauth_client)) resp, content = oauth_client.request(self.ACCESS_TOKEN_URL, method='POST', body='oauth_verifier=%s' % key) - logger.log('resp, content: '+str(resp)+','+str(content)) - - access_token = dict(parse_qsl(content)) - logger.log('access_token: '+str(access_token)) - - logger.log('resp[status] = '+str(resp['status'])) + logger.log(u"TWITTER: resp, content: " + str(resp) + "," + str(content)) + + access_token = dict(parse_qsl(content)) + logger.log(u"TWITTER: access_token: " + str(access_token)) + + logger.log(u"TWITTER: resp[status] = " + str(resp['status'])) if resp['status'] != '200': - logger.log('The request for a token with did not succeed: '+str(resp['status']), logger.ERROR) + logger.log(u"TWITTER: The request for a token with did not succeed: " + str(resp['status']), logger.ERROR) return False else: - logger.log('Your Twitter Access Token key: %s' % access_token['oauth_token']) - logger.log('Access Token secret: %s' % access_token['oauth_token_secret']) + 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']) sickbeard.TWITTER_USERNAME = access_token['oauth_token'] sickbeard.TWITTER_PASSWORD = access_token['oauth_token_secret'] return True - - + def _send_tweet(self, message=None): - - username=self.consumer_key - password=self.consumer_secret - access_token_key=sickbeard.TWITTER_USERNAME - access_token_secret=sickbeard.TWITTER_PASSWORD - - logger.log(u"Sending tweet: "+message) - + + username = self.consumer_key + password = self.consumer_secret + access_token_key = sickbeard.TWITTER_USERNAME + access_token_secret = sickbeard.TWITTER_PASSWORD + + logger.log(u"TWITTER: Sending tweet: " + message) + api = twitter.Api(username, password, access_token_key, access_token_secret) - + try: api.PostUpdate(message) except Exception, e: - logger.log(u"Error Sending Tweet: "+ex(e), logger.ERROR) + logger.log(u"TWITTER: Error Sending Tweet: " + ex(e), logger.ERROR) return False - + return True - - def _notifyTwitter(self, message='', force=False): - prefix = sickbeard.TWITTER_PREFIX - + + def _notify(self, message='', force=False): + # suppress notifications if the notifier is disabled but the notify options are checked if not sickbeard.USE_TWITTER and not force: return False - - return self._send_tweet(prefix+": "+message) -notifier = TwitterNotifier \ No newline at end of file + return self._send_tweet(sickbeard.TWITTER_PREFIX + ": " + message) + +############################################################################## +# Public functions +############################################################################## + + def notify_snatch(self, ep_name): + if sickbeard.TWITTER_NOTIFY_ONSNATCH: + self._notify(common.notifyStrings[common.NOTIFY_SNATCH] + ': ' + ep_name) + + def notify_download(self, ep_name): + if sickbeard.TWITTER_NOTIFY_ONDOWNLOAD: + self._notify(common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + 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): + pass + +notifier = TwitterNotifier diff --git a/sickbeard/notifiers/xbmc.py b/sickbeard/notifiers/xbmc.py index 5366f0b848..b63853c545 100644 --- a/sickbeard/notifiers/xbmc.py +++ b/sickbeard/notifiers/xbmc.py @@ -42,7 +42,7 @@ class XBMCNotifier: - sb_logo_url = 'http://www.sickbeard.com/xbmc-notify.png' + sb_logo_url = "http://www.sickbeard.com/notify.png" def _get_xbmc_version(self, host, username, password): """Returns XBMC JSON-RPC API version (odd # = dev, even # = stable) @@ -93,7 +93,7 @@ def _get_xbmc_version(self, host, username, password): else: return False - def _notify_xbmc(self, message, title="Sick Beard", host=None, username=None, password=None, force=False): + def _notify(self, message, title="Sick Beard", host=None, username=None, password=None, force=False): """Internal wrapper for the notify_snatch and notify_download functions Detects JSON-RPC version then branches the logic for either the JSON-RPC or legacy HTTP API methods. @@ -104,7 +104,7 @@ def _notify_xbmc(self, message, title="Sick Beard", host=None, username=None, pa host: XBMC webserver host:port username: XBMC webserver username password: XBMC webserver 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 @@ -112,6 +112,10 @@ def _notify_xbmc(self, message, title="Sick Beard", host=None, username=None, pa """ + # suppress notifications if the notifier is disabled but the notify options are checked + if not sickbeard.USE_XBMC and not force: + return False + # fill in omitted parameters if not host: host = sickbeard.XBMC_HOST @@ -120,31 +124,26 @@ def _notify_xbmc(self, message, title="Sick Beard", host=None, username=None, pa if not password: password = sickbeard.XBMC_PASSWORD - # suppress notifications if the notifier is disabled but the notify options are checked - if not sickbeard.USE_XBMC and not force: - logger.log("Notification for XBMC not enabled, skipping this notification", logger.DEBUG) - return False - result = '' for curHost in [x.strip() for x in host.split(",")]: - logger.log(u"Sending XBMC notification to '" + curHost + "' - " + message, logger.MESSAGE) + logger.log(u"XBMC: Sending XBMC notification to '" + curHost + "' - " + message, logger.MESSAGE) xbmcapi = self._get_xbmc_version(curHost, username, password) if xbmcapi: if (xbmcapi <= 4): - logger.log(u"Detected XBMC version <= 11, using XBMC HTTP API", logger.DEBUG) + logger.log(u"XBMC: Detected XBMC version <= 11, using XBMC HTTP API", logger.DEBUG) command = {'command': 'ExecBuiltIn', 'parameter': 'Notification(' + title.encode("utf-8") + ',' + message.encode("utf-8") + ')'} notifyResult = self._send_to_xbmc(command, curHost, username, password) if notifyResult: result += curHost + ':' + str(notifyResult) else: - logger.log(u"Detected XBMC version >= 12, using XBMC JSON API", logger.DEBUG) + logger.log(u"XBMC: Detected XBMC version >= 12, using XBMC JSON API", logger.DEBUG) command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification","params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % (title.encode("utf-8"), message.encode("utf-8"), self.sb_logo_url) notifyResult = self._send_to_xbmc_json(command, curHost, username, password) if notifyResult: result += curHost + ':' + notifyResult["result"].decode(sickbeard.SYS_ENCODING) else: - logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) + logger.log(u"XBMC: Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) result += curHost + ':False' return result @@ -174,7 +173,7 @@ def _send_to_xbmc(self, command, host=None, username=None, password=None): password = sickbeard.XBMC_PASSWORD if not host: - logger.log(u'No XBMC host passed, aborting update', logger.DEBUG) + logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) return False for key in command: @@ -182,7 +181,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 encoded API command: " + enc_command, logger.DEBUG) + logger.log(u"XBMC: XBMC encoded API command: " + enc_command, logger.DEBUG) url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command) try: @@ -192,19 +191,16 @@ def _send_to_xbmc(self, command, host=None, username=None, password=None): base64string = base64.encodestring('%s:%s' % (username, password))[:-1] authheader = "Basic %s" % base64string req.add_header("Authorization", authheader) - logger.log(u"Contacting XBMC (with auth header) via url: " + fixStupidEncodings(url), logger.DEBUG) - else: - logger.log(u"Contacting XBMC via url: " + fixStupidEncodings(url), logger.DEBUG) response = urllib2.urlopen(req) result = response.read().decode(sickbeard.SYS_ENCODING) response.close() - logger.log(u"XBMC HTTP response: " + result.replace('\n', ''), logger.DEBUG) + logger.log(u"XBMC: XBMC HTTP response: " + result.replace('\n', ''), logger.DEBUG) return result except (urllib2.URLError, IOError), e: - logger.log(u"Warning: Couldn't contact XBMC HTTP at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) + logger.log(u"XBMC: Warning: Couldn't contact XBMC HTTP at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) return False def _update_library(self, host=None, showName=None): @@ -223,14 +219,14 @@ def _update_library(self, host=None, showName=None): """ if not host: - logger.log(u'No XBMC host passed, aborting update', logger.DEBUG) + logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) return False - logger.log(u"Updating XBMC library via HTTP method for host: " + host, logger.DEBUG) + 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"Updating library in XBMC via HTTP method for show " + showName, logger.DEBUG) + logger.log(u"XBMC: Updating library in XBMC via HTTP method for show " + showName, logger.DEBUG) pathSql = 'select path.strPath from path, tvshow, tvshowlinkpath where ' \ 'tvshow.c00 = "%s" and tvshowlinkpath.idShow = tvshow.idShow ' \ @@ -252,42 +248,42 @@ def _update_library(self, host=None, showName=None): request = self._send_to_xbmc(resetCommand, host) if not sqlXML: - logger.log(u"Invalid response for " + showName + " on " + host, logger.DEBUG) + logger.log(u"XBMC: Invalid response for " + showName + " on " + host, logger.DEBUG) return False encSqlXML = urllib.quote(sqlXML, ':\\/<>') try: et = etree.fromstring(encSqlXML) except SyntaxError, e: - logger.log(u"Unable to parse XML returned from XBMC: " + ex(e), logger.ERROR) + logger.log(u"XBMC: Unable to parse XML returned from XBMC: " + ex(e), logger.ERROR) return False paths = et.findall('.//field') if not paths: - logger.log(u"No valid paths found for " + showName + " on " + host, logger.DEBUG) + logger.log(u"XBMC: No valid paths found for " + showName + " on " + host, logger.DEBUG) return False 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 Updating " + showName + " on " + host + " at " + unEncPath, logger.DEBUG) + logger.log(u"XBMC: 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"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.ERROR) 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"Doing Full Library XBMC update on host: " + host, logger.DEBUG) + logger.log(u"XBMC: Doing Full Library XBMC update on host: " + host, logger.DEBUG) updateCommand = {'command': 'ExecBuiltIn', 'parameter': 'XBMC.updatelibrary(video)'} request = self._send_to_xbmc(updateCommand, host) if not request: - logger.log(u"XBMC Full Library update failed on: " + host, logger.ERROR) + logger.log(u"XBMC: XBMC Full Library update failed on: " + host, logger.ERROR) return False return True @@ -317,11 +313,11 @@ def _send_to_xbmc_json(self, command, host=None, username=None, password=None): password = sickbeard.XBMC_PASSWORD if not host: - logger.log(u'No XBMC host passed, aborting update', logger.DEBUG) + logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) return False command = command.encode('utf-8') - logger.log(u"XBMC JSON command: " + command, logger.DEBUG) + logger.log(u"XBMC: XBMC JSON command: " + command, logger.DEBUG) url = 'http://%s/jsonrpc' % (host) try: @@ -332,28 +328,25 @@ def _send_to_xbmc_json(self, command, host=None, username=None, password=None): base64string = base64.encodestring('%s:%s' % (username, password))[:-1] authheader = "Basic %s" % base64string req.add_header("Authorization", authheader) - logger.log(u"Contacting XBMC (with auth header) via url: " + fixStupidEncodings(url), logger.DEBUG) - else: - logger.log(u"Contacting XBMC via url: " + fixStupidEncodings(url), logger.DEBUG) try: response = urllib2.urlopen(req) except urllib2.URLError, e: - logger.log(u"Error while trying to retrieve XBMC API version for " + host + ": " + ex(e), logger.WARNING) + logger.log(u"XBMC: Error while trying to retrieve XBMC API version for " + host + ": " + ex(e), logger.WARNING) return False # parse the json result try: result = json.load(response) response.close() - logger.log(u"XBMC JSON response: " + str(result), logger.DEBUG) - return result # need to return response for parsing + logger.log(u"XBMC: XBMC JSON response: " + str(result), logger.DEBUG) + return result # need to return response for parsing except ValueError, e: - logger.log(u"Unable to decode JSON: " + response, logger.WARNING) + logger.log(u"XBMC: Unable to decode JSON: " + response, logger.WARNING) return False except IOError, e: - logger.log(u"Warning: Couldn't contact XBMC JSON API at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) + logger.log(u"XBMC: Warning: Couldn't contact XBMC JSON API at " + fixStupidEncodings(url) + " " + ex(e), logger.WARNING) return False def _update_library_json(self, host=None, showName=None): @@ -372,15 +365,15 @@ def _update_library_json(self, host=None, showName=None): """ if not host: - logger.log(u'No XBMC host passed, aborting update', logger.DEBUG) + logger.log(u"XBMC: No XBMC host passed, aborting update", logger.DEBUG) return False - logger.log(u"Updating XBMC library via JSON method for host: " + host, logger.MESSAGE) + 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"Updating library in XBMC via JSON method for show " + showName, logger.DEBUG) + logger.log(u"XBMC: Updating library in XBMC via JSON method for show " + showName, logger.DEBUG) # get tvshowid by showName showsCommand = '{"jsonrpc":"2.0","method":"VideoLibrary.GetTVShows","id":1}' @@ -389,20 +382,20 @@ def _update_library_json(self, host=None, showName=None): if showsResponse and "result" in showsResponse and "tvshows" in showsResponse["result"]: shows = showsResponse["result"]["tvshows"] else: - logger.log(u'No tvshows in XBMC TV show list', logger.DEBUG) + logger.log(u"XBMC: No tvshows in XBMC TV show list", logger.DEBUG) return False for show in shows: if (show["label"] == showName): tvshowid = show["tvshowid"] - break # exit out of loop otherwise the label and showname will not match up + break # exit out of loop otherwise the label and showname will not match up # this can be big, so free some memory del shows # we didn't find the show (exact match), thus revert to just doing a full update if enabled if (tvshowid == -1): - logger.log(u'Exact show name not matched in XBMC TV show list', logger.DEBUG) + logger.log(u"XBMC: Exact show name not matched in XBMC TV show list", logger.DEBUG) return False # lookup tv-show path @@ -410,33 +403,33 @@ def _update_library_json(self, host=None, showName=None): pathResponse = self._send_to_xbmc_json(pathCommand, host) path = pathResponse["result"]["tvshowdetails"]["file"] - logger.log(u"Received Show: " + show["label"] + " with ID: " + str(tvshowid) + " Path: " + path, logger.DEBUG) + logger.log(u"XBMC: Received Show: " + show["label"] + " with ID: " + str(tvshowid) + " Path: " + path, logger.DEBUG) if (len(path) < 1): - logger.log(u"No valid path found for " + showName + " with ID: " + str(tvshowid) + " on " + host, logger.WARNING) + logger.log(u"XBMC: No valid path found for " + showName + " with ID: " + str(tvshowid) + " on " + host, logger.WARNING) return False - logger.log(u"XBMC Updating " + showName + " on " + host + " at " + path, logger.DEBUG) + logger.log(u"XBMC: 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"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.ERROR) return False # catch if there was an error in the returned request for r in request: if 'error' in r: - logger.log(u"Error while attempting to update show directory for " + showName + " on " + host + " at " + path, logger.ERROR) + logger.log(u"XBMC: Error while attempting to update show directory for " + showName + " on " + host + " at " + path, logger.ERROR) return False # do a full update if requested else: - logger.log(u"Doing Full Library XBMC update on host: " + host, logger.MESSAGE) + logger.log(u"XBMC: Doing Full Library XBMC update on 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 Full Library update failed on: " + host, logger.ERROR) + logger.log(u"XBMC: XBMC Full Library update failed on: " + host, logger.ERROR) return False return True @@ -447,22 +440,22 @@ def _update_library_json(self, host=None, showName=None): def notify_snatch(self, ep_name): if sickbeard.XBMC_NOTIFY_ONSNATCH: - self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_SNATCH]) + self._notify(ep_name, common.notifyStrings[common.NOTIFY_SNATCH]) def notify_download(self, ep_name): if sickbeard.XBMC_NOTIFY_ONDOWNLOAD: - self._notify_xbmc(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD]) + self._notify(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD]) def test_notify(self, host, username, password): - return self._notify_xbmc("Testing XBMC notifications from Sick Beard", "Test Notification", host, username, password, force=True) + return self._notify("Testing XBMC notifications from Sick Beard", "Test Notification", host, username, password, force=True) def update_library(self, showName=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. - Do the ability of accepting a list of hosts deliminated by comma, we split off the first host to send the update to. + Do the ability of accepting a list of hosts delimited by comma, we split off the first host to send the update to. This is a workaround for SQL backend users as updating multiple clients causes duplicate entries. - Future plan is to revist how we store the host/ip/username/pw/options so that it may be more flexible. + Future plan is to revisit how we store the host/ip/username/pw/options so that it may be more flexible. Args: showName: Name of a TV show to specifically target the library update for @@ -474,7 +467,7 @@ def update_library(self, showName=None): if sickbeard.USE_XBMC and sickbeard.XBMC_UPDATE_LIBRARY: if not sickbeard.XBMC_HOST: - logger.log(u"No XBMC hosts specified, check your settings", logger.DEBUG) + logger.log(u"XBMC: No XBMC hosts specified, check your settings", logger.DEBUG) return False if sickbeard.XBMC_UPDATE_ONLYFIRST: @@ -485,7 +478,7 @@ def update_library(self, showName=None): result = 0 for curHost in [x.strip() for x in host.split(",")]: - logger.log(u"Sending request to update library for XBMC host: '" + curHost + "'", logger.MESSAGE) + logger.log(u"XBMC: Sending request to update library for XBMC host: '" + curHost + "'", logger.MESSAGE) xbmcapi = self._get_xbmc_version(curHost, sickbeard.XBMC_USERNAME, sickbeard.XBMC_PASSWORD) if xbmcapi: @@ -493,20 +486,20 @@ def update_library(self, showName=None): # 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 single show update failed", logger.WARNING) + logger.log(u"XBMC: XBMC single show update failed", logger.WARNING) if sickbeard.XBMC_UPDATE_FULL: - logger.log(u"Falling back to XBMC full update") + logger.log(u"XBMC: Falling back to XBMC full update") 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 single show update failed", logger.WARNING) + logger.log(u"XBMC: XBMC single show update failed", logger.WARNING) if sickbeard.XBMC_UPDATE_FULL: - logger.log(u"Falling back to XBMC full update") + logger.log(u"XBMC: Falling back to XBMC full update") self._update_library_json(curHost) else: - logger.log(u"Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) + logger.log(u"XBMC: Failed to detect XBMC version for '" + curHost + "', check configuration and try again.", logger.ERROR) result = result + 1 # needed for the 'update xbmc' submenu command diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index 77ab261db1..678a388a4b 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -1919,7 +1919,7 @@ def testTwitter(self): if result: return "Tweet successful, check your twitter to make sure it worked" else: - return "Error sending tweet" + return "Error sending Tweet" @cherrypy.expose def testXBMC(self, host=None, username=None, password=None): @@ -1971,9 +1971,9 @@ def testNMJ(self, host=None, database=None, mount=None): host = config.clean_host(host) result = notifiers.nmj_notifier.test_notify(urllib.unquote_plus(host), database, mount) if result: - return "Successfully started the scan update" + return "Successfully started the scan update for NMJ" else: - return "Test failed to start the scan update" + return "Failed to start the scan update for NMJ" @cherrypy.expose def settingsNMJ(self, host=None): @@ -1993,9 +1993,9 @@ def testNMJv2(self, host=None): host = config.clean_host(host) result = notifiers.nmjv2_notifier.test_notify(urllib.unquote_plus(host)) if result: - return "Test notice sent successfully to " + urllib.unquote_plus(host) + return "Successfully started the scan update for NMJv2" else: - return "Test notice failed to " + urllib.unquote_plus(host) + return "Failed to start the scan update for NMJv2" @cherrypy.expose def settingsNMJv2(self, host=None, dbloc=None, instance=None): @@ -2004,9 +2004,9 @@ def settingsNMJv2(self, host=None, dbloc=None, instance=None): host = config.clean_host(host) result = notifiers.nmjv2_notifier.notify_settings(urllib.unquote_plus(host), dbloc, instance) if result: - return '{"message": "NMJ Database found at: %(host)s", "database": "%(database)s"}' % {"host": host, "database": sickbeard.NMJv2_DATABASE} + return '{"message": "NMJv2 Database found at: %(host)s", "database": "%(database)s"}' % {"host": host, "database": sickbeard.NMJv2_DATABASE} else: - return '{"message": "Unable to find NMJ Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {"dbloc": dbloc} + return '{"message": "Unable to find NMJv2 Database at location: %(dbloc)s. Is the right location selected and PCH running?", "database": ""}' % {"dbloc": dbloc} @cherrypy.expose def testTrakt(self, api=None, username=None, password=None): From 2732fb03e36abdcfdcf098dfce475c4b6f55a2d0 Mon Sep 17 00:00:00 2001 From: Jonathon Saine Date: Tue, 1 Apr 2014 09:09:11 -0500 Subject: [PATCH 04/39] Add **Always On** option for XBMC Control logging errors when XBMC is offline or not --- data/interfaces/default/config_notifications.tmpl | 7 +++++++ sickbeard/__init__.py | 5 ++++- sickbeard/notifiers/xbmc.py | 6 ++++-- sickbeard/webserve.py | 3 ++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/data/interfaces/default/config_notifications.tmpl b/data/interfaces/default/config_notifications.tmpl index 81e956ebfa..bd7d201fb1 100644 --- a/data/interfaces/default/config_notifications.tmpl +++ b/data/interfaces/default/config_notifications.tmpl @@ -38,6 +38,13 @@
  • +
    + + +
    @@ -172,12 +173,12 @@
    Click below to test.
    - +
    @@ -424,7 +425,7 @@