From 4223ed4b5aa97ee5aa0871e51a61af3e6c234b57 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 18:59:04 +0200 Subject: [PATCH] Put the renamer as a function of a download status checker in the searcher class The changed functionality is as follows: - the renamer is not on scheduled interval anymore - the download status checker checks the status of all snatched releases every x minutes - if a release has downloaded it fires up the renamer (if enabled) - if it failed, it sets the release to ignored and snatches the next best release With these additions the renamer wont scan your hd anymore when it is not required, and will retry failed downloads with new releases. To do: - the only downloader implemented is SabNZBd, for the others it defaults to the old behavior when releases are snatched (I think!?) - a button to scan manually: Items added to the renamer folder are only picked up after a download completed --- couchpotato/core/downloaders/base.py | 6 +- .../core/downloaders/sabnzbd/__init__.py | 7 -- couchpotato/core/downloaders/sabnzbd/main.py | 109 ++++++++++++------ couchpotato/core/plugins/renamer/__init__.py | 9 -- couchpotato/core/plugins/renamer/main.py | 57 +-------- couchpotato/core/plugins/searcher/__init__.py | 16 +++ couchpotato/core/plugins/searcher/main.py | 76 ++++++++++++ 7 files changed, 172 insertions(+), 108 deletions(-) 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