diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 07536a49..1be9dccc 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -13,16 +13,16 @@ class Downloader(Plugin): def __init__(self): addEvent('download', self.download) - addEvent('getdownloadfailed', self.getdownloadfailed) + addEvent('getdownloadstatus', self.getdownloadstatus) def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getdownloadfailed(self, data = {}, movie = {}): + def getdownloadstatus(self, data = {}, movie = {}): pass def createNzbName(self, data, movie): - return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie)) + return '%s%s' % (toSafeString(data.get('name')[:40]), self.cpTag(movie)) def createFileName(self, data, filedata, movie): name = os.path.join(self.createNzbName(data, movie)) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 822ba61f..1baaea0d 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -40,13 +40,6 @@ config = [{ 'advanced': True, 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, - { - 'name': 'download failed', - 'default': True, - 'type': 'bool', - 'advanced': True, - 'description': 'Try the next best release for a movie after a download failed.', - }, { 'name': 'delete failed', 'default': True, diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index bb7a2eb8..4144b279 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -40,14 +40,14 @@ class Sabnzbd(Downloader): try: if params.get('mode') is 'addfile': - data = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False) + sab = self.urlopen(url, timeout = 60, params = {"nzbfile": (nzb_filename, filedata)}, multipart = True, show_error = False) else: - data = self.urlopen(url, timeout = 60, show_error = False) + sab = self.urlopen(url, timeout = 60, show_error = False) except: log.error(traceback.format_exc()) return False - result = data.strip() + result = sab.strip() if not result: log.error("SABnzbd didn't return anything.") return False @@ -63,53 +63,96 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getdownloadfailed(self, data = {}, movie = {}): + def getdownloadstatus(self, data = {}, movie = {}): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return - if not self.conf('download failed', default = True): - return False + nzbname = self.createNzbName(data, movie) + log.info('Checking download status of "%s" at SABnzbd.', nzbname) - log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) + # 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) + except: + log.error(traceback.format_exc()) + return + try: + history = json.loads(sab) + except: + log.debug("Result text from SAB: " + sab[:40]) + log.error(traceback.format_exc()) + return + + for slot in history['queue']['slots']: + if slot['cat'] == self.conf('category'): + log.debug('Found %s in SabNZBd queue, which is %s, with %s left', (slot['filename'], slot['status'], slot['timeleft'])) + if slot['filename'] == nzbname: + return slot['status'] + + # Go through history items params = { 'apikey': self.conf('api_key'), 'mode': 'history', 'output': 'json' } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - log.debug('Opening: %s', url) try: - history = json.load(self.urlopen(url)) + sab = self.urlopen(url, timeout = 60, show_error = False) except: log.error(traceback.format_exc()) - return False + return + try: + history = json.loads(sab) + except: + log.debug("Result text from SAB: " + sab[:40]) + log.error(traceback.format_exc()) + return - nzbname = self.createNzbName(data, movie) - - # Go through history items for slot in history['history']['slots']: - log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) - if slot['name'] == nzbname and slot['status'] == 'Failed': + if slot['category'] == self.conf('category'): + log.debug('Found %s in SabNZBd history, which has %s', (slot['name'], slot['status'])) + if slot['name'] == nzbname: + if slot['status'] == 'Failed' or 'fail' in slot['fail_message'].lower(): - # Delete failed download - if self.conf('delete failed', default = True): - log.info('%s failed downloading, deleting...', slot['name']) - params = { - 'apikey': self.conf('api_key'), - 'mode': 'history', - 'name': 'delete', - 'del_files': '1', - 'value': slot['nzo_id'] - } - url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) - try: - data = self.urlopen(url, timeout = 60, show_error = False) - except: - log.error(traceback.format_exc()) + # Delete failed download + if self.conf('delete failed', default = True): + log.info('%s failed downloading, deleting...', slot['name']) + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'del_files': '1', + 'value': slot['nzo_id'] + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + try: + sab = self.urlopen(url, timeout = 60, show_error = False) + except: + log.error(traceback.format_exc()) + return False - # Return download failed - return True + result = sab.strip() + if not result: + log.error("SABnzbd didn't return anything.") - return False + log.debug("Result text from SAB: " + result[:40]) + if result == "ok": + log.info('SabNZBd deleted failed release %s successfully.', slot['name']) + elif result == "Missing authentication": + log.error("Incorrect username/password.") + else: + log.error("Unknown error: " + result[:40]) + + return 'Failed' + else: + return slot['status'] + + return 'Not found' diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py index 59439544..21076d68 100644 --- a/couchpotato/core/plugins/renamer/__init__.py +++ b/couchpotato/core/plugins/renamer/__init__.py @@ -86,15 +86,6 @@ config = [{ 'label': 'Separator', 'description': 'Replace all the spaces with a character. Example: ".", "-" (without quotes). Leave empty to use spaces.', }, - { - 'advanced': True, - 'name': 'run_every', - 'label': 'Run every', - 'default': 1, - 'type': 'int', - 'unit': 'min(s)', - 'description': 'Search for new movies inside the folder every X minutes.', - }, ], }, { 'tab': 'renamer', diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 8855df4d..4c80fb08 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -29,7 +29,7 @@ class Renamer(Plugin): addEvent('renamer.scan', self.scan) addEvent('app.load', self.scan) - fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) + #fireEvent('schedule.interval', 'renamer.scan', self.scan, minutes = self.conf('run_every')) def scanView(self): @@ -48,8 +48,6 @@ class Renamer(Plugin): log.info('Renamer is disabled to avoid infinite looping of the same error.') return - self.checkSnatchedStatusses() - # Check to see if the "to" folder is inside the "from" folder. if not os.path.isdir(self.conf('from')) or not os.path.isdir(self.conf('to')): log.debug('"To" and "From" have to exist.') @@ -474,57 +472,4 @@ class Renamer(Plugin): except: log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) - def checkSnatchedStatusses(self): - snatched_status = fireEvent('status.get', 'snatched', single = True) - ignored_status = fireEvent('status.get', 'ignored', single = True) - db = get_session() - rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) - - for rel in rels: - - # Get current selected title - default_title = '' - for title in rel.movie.library.titles: - if title.default: default_title = title.title - - log.debug('Checking snatched movie: %s' , default_title) - - item = {} - for info in rel.info: - item[info.identifier] = info.value - - log.debug('Checking status snatched release: %s' , item.get('name')) - - mov = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - - # check status - downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mov) - - if downloadfailed: - # if failed set status to ignored - rel.status_id = ignored_status.get('id') - db.commit() - - # search/download again - # if downloaded manually: # this is currently not stored... - # log.info('Download of %s failed...', item['name']) - # return - - #update movie to reflect release status update - mov = rel.movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) - - log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', mov) - - return diff --git a/couchpotato/core/plugins/searcher/__init__.py b/couchpotato/core/plugins/searcher/__init__.py index f499e2bd..2d82b61c 100644 --- a/couchpotato/core/plugins/searcher/__init__.py +++ b/couchpotato/core/plugins/searcher/__init__.py @@ -39,6 +39,22 @@ config = [{ 'type': 'dropdown', 'values': [('usenet & torrents', 'both'), ('usenet', 'nzb'), ('torrents', 'torrent')], }, + { + 'advanced': True, + 'name': 'run_every', + 'label': 'Run every', + 'default': 1, + 'type': 'int', + 'unit': 'min(s)', + 'description': 'Detect movie status every X minutes. Will start the renamer if movie is completed or handle failed download if these options are enabled', + }, + { + 'name': 'failed download', + 'default': True, + 'type': 'bool', + 'advanced': True, + 'description': 'Try the next best release for a movie after a download failed.', + }, ], }, { 'tab': 'searcher', diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 03eb6cfb..b1b285ef 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -25,9 +25,12 @@ class Searcher(Plugin): addEvent('searcher.single', self.single) addEvent('searcher.correct_movie', self.correctMovie) addEvent('searcher.download', self.download) + addEvent('searcher.checksnatched', self.checksnatched) # Schedule cronjob fireEvent('schedule.cron', 'searcher.all', self.all_movies, day = self.conf('cron_day'), hour = self.conf('cron_hour'), minute = self.conf('cron_minute')) + fireEvent('schedule.interval', 'searcher.checksnatched', self.checksnatched, minutes = self.conf('run_every')) + def all_movies(self): @@ -439,3 +442,76 @@ class Searcher(Plugin): return False + + def checksnatched(self): + snatched_status = fireEvent('status.get', 'snatched', single = True) + ignored_status = fireEvent('status.get', 'ignored', single = True) + + db = get_session() + rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) + + log.info('Checking snatched releases...') + + for rel in rels: + + # Get current selected title + default_title = '' + for title in rel.movie.library.titles: + if title.default: default_title = title.title + + log.debug('Checking snatched movie: %s' , default_title) + + item = {} + for info in rel.info: + item[info.identifier] = info.value + + movie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + + # check status + downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) + log.debug('Download staus: %s' , downloadstatus[0]) + + if downloadstatus[0] == 'Failed': + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() + + # search/download again + # if downloaded manually: # this is currently not stored... + # log.info('Download of %s failed...', item['name']) + # return + + if self.conf('failed download', default = True): + + #update movie to reflect release status update + movie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + log.info('Download of %s failed, trying next release...', item['name']) + fireEvent('searcher.single', movie) + else: + log.info('Download of %s failed.', item['name']) + + elif downloadstatus[0] == 'Completed': + log.info('Download of %s completed!', item['name']) + fireEvent('renamer.scan') + + elif downloadstatus[0] == 'Not found': + log.info('%s not found in SabNZBd', item['name']) + rel.status_id = ignored_status.get('id') + db.commit() + + elif downloadstatus[0] == None: # Downloader not compatible with download status or + fireEvent('renamer.scan') + + # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + + return