diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 6ca29f6..776976d 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -1,10 +1,7 @@ from base64 import b32decode, b16encode from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import toSafeString from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.environment import Env -import os import random import re @@ -23,34 +20,18 @@ class Downloader(Plugin): def __init__(self): addEvent('download', self.download) - addEvent('download.status', self.getDownloadStatus) - addEvent('download.remove', self.remove) + addEvent('download.status', self.getAllDownloadStatus) + addEvent('download.remove_failed', self.removeFailed) def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getDownloadStatus(self, data = {}, movie = {}): + def getAllDownloadStatus(self): return False - def remove(self, name = {}, nzo_id = {}): + def removeFailed(self, name = {}, nzo_id = {}): return False - def createNzbName(self, data, movie): - tag = self.cpTag(movie) - return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag) - - def createFileName(self, data, filedata, movie): - name = os.path.join(self.createNzbName(data, movie)) - if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata: - return '%s.%s' % (name, 'rar') - return '%s.%s' % (name, data.get('type')) - - def cpTag(self, movie): - if Env.setting('enabled', 'renamer'): - return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' - - return '' - def isCorrectType(self, item_type): is_correct = item_type in self.type diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index f05f0d5..eaf91b5 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -1,6 +1,6 @@ from couchpotato.core.downloaders.base import Downloader from couchpotato.core.helpers.encoding import tryUrlencode -from couchpotato.core.helpers.variable import cleanHost +from couchpotato.core.helpers.variable import cleanHost, mergeDicts from couchpotato.core.logger import CPLog import json import traceback @@ -63,86 +63,92 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getDownloadStatus(self, data = {}, movie = {}): - if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): - return + def getAllDownloadStatus(self): + if self.isDisabled(manual = False): + return False - log.info('Checking SABnzbd download status.') + log.debug('Checking SABnzbd download status.') # Go through Queue - params = { - 'apikey': self.conf('api_key'), - 'mode': 'queue', - 'output': 'json' - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - sab = self.urlopen(url, timeout = 60, show_error = False) + queue = self.call({ + 'mode': 'queue', + }) except: - log.error('Failed checking status: %s', traceback.format_exc()) + log.error('Failed getting queue: %s', traceback.format_exc()) return False + # Go through history items try: - queue = json.loads(sab) + history = self.call({ + 'mode': 'history', + 'limit': 15, + }) except: - log.debug("Result text from SAB: " + sab[:40]) - log.error('Failed parsing json status: %s', traceback.format_exc()) + log.error('Failed getting history json: %s', traceback.format_exc()) return False - # Go through history items - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'limit': 15, - 'output': 'json' - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + statuses = [] - try: - sab = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error('Failed getting history: %s', traceback.format_exc()) - return False + # Get busy releases + for item in queue.get('slots', []): + statuses.append({ + 'id': item['nzo_id'], + 'name': item['filename'], + 'status': 'busy', + 'original_status': item['status'], + 'timeleft': item['timeleft'] if not queue['paused'] else -1, + }) - try: - history = json.loads(sab) - except: - log.debug("Result text from SAB: " + sab[:40]) - log.error('Failed parsing history json: %s', traceback.format_exc()) - return False + # Get old releases + for item in history.get('slots', []): + + status = 'busy' + if item['status'] == 'Failed' or (item['status'] == 'Completed' and item['fail_message'].strip()): + status = 'failed' + elif item['status'] == 'Completed': + status = 'completed' - return queue, history + statuses.append({ + 'id': item['nzo_id'], + 'name': item['name'], + 'status': status, + 'original_status': item['status'], + 'timeleft': 0, + }) - def remove(self, name = {}, nzo_id = {}): - # Delete failed download - if self.conf('delete_failed', default = True): + return statuses - log.info('%s failed downloading, deleting...', name) - params = { - 'apikey': self.conf('api_key'), + def removeFailed(self, item): + + if not self.conf('delete_failed', default = True): + return False + + log.info('%s failed downloading, deleting...', item['name']) + + try: + self.call({ 'mode': 'history', 'name': 'delete', 'del_files': '1', - 'value': nzo_id - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + 'value': item['id'] + }, use_json = False) + except: + log.error('Failed deleting: %s', traceback.format_exc()) + return False - try: - sab = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error('Failed deleting: %s', traceback.format_exc()) - return False + return True - result = sab.strip() - if not result: - log.error("SABnzbd didn't return anything.") + def call(self, params, use_json = True): + + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(mergeDicts(params, { + 'apikey': self.conf('api_key'), + 'output': 'json' + })) + + data = self.urlopen(url, timeout = 60, show_error = False) + if use_json: + return json.loads(data)[params['mode']] + else: + return data - log.debug("Result text from SAB: " + result[:40]) - if result == "ok": - log.info('SabNZBd deleted failed release %s successfully.', name) - elif result == "Missing authentication": - log.error("Incorrect username/password or API?.") - else: - log.error("Unknown error: " + result[:40]) - return diff --git a/couchpotato/core/helpers/variable.py b/couchpotato/core/helpers/variable.py index 5312177..2578d2c 100644 --- a/couchpotato/core/helpers/variable.py +++ b/couchpotato/core/helpers/variable.py @@ -118,8 +118,16 @@ def getTitle(library_dict): try: return library_dict['titles'][0]['title'] except: - log.error('Could not get title for %s', library_dict['identifier']) - return None + try: + for title in library_dict.titles: + if title.default: + return title.title + except: + log.error('Could not get title for %s', library_dict.identifier) + return None + + log.error('Could not get title for %s', library_dict['identifier']) + return None except: log.error('Could not get title for library item: %s', library_dict) return None diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 40189ef..8a1e076 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -1,7 +1,8 @@ from StringIO import StringIO from couchpotato import addView from couchpotato.core.event import fireEvent, addEvent -from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss +from couchpotato.core.helpers.encoding import tryUrlencode, simplifyString, ss, \ + toSafeString from couchpotato.core.helpers.variable import getExt from couchpotato.core.logger import CPLog from couchpotato.environment import Env @@ -245,6 +246,22 @@ class Plugin(object): Env.get('cache').set(cache_key, value, timeout) return value + def createNzbName(self, data, movie): + tag = self.cpTag(movie) + return '%s%s' % (toSafeString(data.get('name')[:127 - len(tag)]), tag) + + def createFileName(self, data, filedata, movie): + name = os.path.join(self.createNzbName(data, movie)) + if data.get('type') == 'nzb' and 'DOCTYPE nzb' not in filedata and '' not in filedata: + return '%s.%s' % (name, 'rar') + return '%s.%s' % (name, data.get('type')) + + def cpTag(self, movie): + if Env.setting('enabled', 'renamer'): + return '.cp(' + movie['library'].get('identifier') + ')' if movie['library'].get('identifier') else '' + + return '' + def isDisabled(self): return not self.isEnabled() diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 2f57911..1b72229 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -20,6 +20,7 @@ log = CPLog(__name__) class Renamer(Plugin): renaming_started = False + checking_snatched = False def __init__(self): @@ -33,6 +34,7 @@ class Renamer(Plugin): addEvent('app.load', self.scan) fireEvent('schedule.interval', 'renamer.check_snatched', self.checkSnatched, minutes = self.conf('run_every')) + fireEvent('schedule.interval', 'renamer.check_snatched_forced', self.scan, hours = 2) def scanView(self): @@ -495,6 +497,11 @@ class Renamer(Plugin): loge('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) def checkSnatched(self): + if self.checking_snatched: + log.debug('Already checking snatched') + + self.checking_snatched = True + snatched_status = fireEvent('status.get', 'snatched', single = True) ignored_status = fireEvent('status.get', 'ignored', single = True) failed_status = fireEvent('status.get', 'failed', single = True) @@ -504,81 +511,75 @@ class Renamer(Plugin): db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')).all() + scan_required = False + if rels: + self.checking_snatched = True log.debug('Checking status snatched releases...') # get queue and history (once) from SABnzbd - queue, history = fireEvent('download.status', data = {}, movie = {}, single = True) + statuses = fireEvent('download.status', merge = True) + if not statuses: + log.debug('Download status functionality is not implemented for active downloaders.') + scan_required = True - scan_required = False - - for rel in rels: - - # Get current selected title - default_title = '' - for title in rel.movie.library.titles: - if title.default: default_title = title.title - - # Check if movie has already completed and is manage tab (legacy db correction) - if rel.movie.status_id == done_status.get('id'): - log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title) - rel.status_id = ignored_status.get('id') - db.commit() - continue + try: + for rel in rels: + rel_dict = rel.to_dict({'info': {}}) - item = {} - for info in rel.info: - item[info.identifier] = info.value + # Get current selected title + default_title = getTitle(rel.movie.library) - movie_dict = fireEvent('movie.get', rel.movie_id, single = True) + # Check if movie has already completed and is manage tab (legacy db correction) + if rel.movie.status_id == done_status.get('id'): + log.debug('Found a completed movie with a snatched release : %s. Setting release status to ignored...' , default_title) + rel.status_id = ignored_status.get('id') + db.commit() + continue - # check status - nzbname = self.createNzbName(item, movie_dict) - try: - for slot in queue['queue']['slots']: - log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) - if slot['filename'] == nzbname: - downloadstatus =['status'].lower() - except: - log.debug('No items in queue: %s', (traceback.format_exc())) - try: - for slot in history['history']['slots']: - log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) - if slot['name'] == nzbname: - # Note: if post process even if failed is on in SabNZBd, it will complete with a fail message - if slot['status'] == 'Failed' or (slot['status'] == 'Completed' and slot['fail_message'].strip()): - - # Delete failed download - rel_remove = fireEvent('download.remove', name = slot['name'], nzo_id = slot['nzo_id'], single = True) - downloadstatus = 'failed' - else: - downloadstatus = slot['status'].lower() - except: - log.debug('No items in history: %s', (traceback.format_exc())) + movie_dict = fireEvent('movie.get', rel.movie_id, single = True) - if not downloadstatus: - log.debug('Download status functionality is not implemented for active downloaders.') - scan_required = True - else: - log.debug('Download status: %s' , downloadstatus) + # check status + nzbname = self.createNzbName(rel_dict['info'], movie_dict) - if downloadstatus == 'failed': - if self.conf('next_on_failed'): - fireEvent('searcher.try_next_release', movie_id = rel.movie_id) - else: - rel.status_id = failed_status.get('id') - db.commit() + found = False + for item in statuses: + if item['name'] == nzbname: + + timeleft = 'N/A' if item['timeleft'] == -1 else item['timeleft'] + log.debug('Found %s: %s, time to go: %s', (item['name'], item['status'].upper(), timeleft)) - log.info('Download of %s failed.', item['name']) + if item['status'] == 'busy': + pass + elif item['status'] == 'failed': + if item['delete']: + fireEvent('download.remove_failed', item, single = True) - elif downloadstatus == 'completed': - log.info('Download of %s completed!', item['name']) - scan_required = True + if self.conf('next_on_failed'): + fireEvent('searcher.try_next_release', movie_id = rel.movie_id) + else: + rel.status_id = failed_status.get('id') + db.commit() + elif item['status'] == 'completed': + log.info('Download of %s completed!', item['name']) + scan_required = True - elif downloadstatus == 'not_found': - log.info('%s not found in downloaders', item['name']) - rel.status_id = ignored_status.get('id') - db.commit() + found = True + break + + if not found: + log.info('%s not found in downloaders', nzbname) + rel.status_id = ignored_status.get('id') + db.commit() + + if self.conf('next_on_failed'): + fireEvent('searcher.try_next_release', movie_id = rel.movie_id) + + except: + log.error('Failed checking for release in downloader: %s', traceback.format_exc()) - # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if scan_required: fireEvent('renamer.scan') + + self.checking_snatched = False + + return True