From 6225ed92b1a9512b57e44c48de7727fef53b4d9d Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 29 Jul 2012 18:56:05 +0200 Subject: [PATCH 01/11] Added failed download handling for sabnzbd. It will check for failed downloads from the sabnzbd api and then set the failed release to ignore and try the search again. --- couchpotato/core/downloaders/base.py | 5 ++- couchpotato/core/downloaders/sabnzbd/main.py | 43 ++++++++++++++++++ couchpotato/core/plugins/renamer/main.py | 46 +++++++++++++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 74e04de6..e0dad913 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -7,17 +7,20 @@ import os log = CPLog(__name__) - class Downloader(Plugin): type = [] def __init__(self): addEvent('download', self.download) + addEvent('getdownloadfailed', self.getdownloadfailed) def download(self, data = {}): pass + def getdownloadfailed(self, data = {}): + pass + def createNzbName(self, data, movie): return '%s%s' % (toSafeString(data.get('name')), self.cpTag(movie)) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 64b24509..8983c1ad 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -3,6 +3,8 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback +import urllib2 +import json log = CPLog(__name__) @@ -61,3 +63,44 @@ class Sabnzbd(Downloader): else: log.error("Unknown error: " + result[:40]) return False + + def getdownloadfailed(self, data = {}, movie = {}, manual = False): + if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): + return + + log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) + + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'ouput': 'json' + } + url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) + log.debug('Opening: %s', url) + history = json.load(urllib2.urlopen(url)) + + 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': + log.debug('%s failed downloading, deleting...', slot['name']) + + # Delete failed download + params = { + 'apikey': self.conf('api_key'), + 'mode': 'history', + 'name': 'delete', + 'value' : slot['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()) + + # Return failed + return True + + return False diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 4c6e6b89..c8a76944 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import jsonified from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Library, File, Profile +from couchpotato.core.settings.model import Library, File, Profile, Release as Relea from couchpotato.environment import Env import os import re @@ -48,6 +48,8 @@ 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.') @@ -471,3 +473,45 @@ class Renamer(Plugin): os.rmdir(folder) 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')) + + log.debug('Checking snatched releases... %s', 'ops') + + for rel in rels: + log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + item = {} + for info in rel.info: + item[info.identifier] = info.value + + log.debug('Checking status snatched release: %s' , item.get('name')) + + mymovie = rel.movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) + + log.debug('Checking status snatched release: %s' , mymovie['library'].get('identifier')) + + # check status + downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mymovie) + + if downloadfailed: + log.debug('Download of %s failed', item['name']) + + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() + + # search/download again + log.info('Download of %s failed, trying next release...', item['name']) + fireEvent('searcher.single', rel.movie) + + return From 684ef44f841a9f39b07e9a724c550e688bf89a19 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 29 Jul 2012 20:09:11 +0200 Subject: [PATCH 02/11] Fixed typo and added some error handling --- couchpotato/core/downloaders/sabnzbd/main.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 8983c1ad..45b4e25b 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -4,6 +4,7 @@ from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback import urllib2 +import requests import json log = CPLog(__name__) @@ -73,11 +74,16 @@ class Sabnzbd(Downloader): params = { 'apikey': self.conf('api_key'), 'mode': 'history', - 'ouput': 'json' + 'output': 'json' } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) log.debug('Opening: %s', url) - history = json.load(urllib2.urlopen(url)) + + try: + history = json.load(urllib2.urlopen(url)) + except: + log.error(traceback.format_exc()) + return False nzbname = self.createNzbName(data, movie) From 2884488338d5bd98a98b431c80eb17eb509802de Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Sun, 29 Jul 2012 22:02:51 +0200 Subject: [PATCH 03/11] Removed double logging, bug fixes --- couchpotato/core/plugins/renamer/main.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index c8a76944..f0887acf 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -481,37 +481,32 @@ class Renamer(Plugin): db = get_session() rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) - log.debug('Checking snatched releases... %s', 'ops') - for rel in rels: log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + item = {} for info in rel.info: item[info.identifier] = info.value log.debug('Checking status snatched release: %s' , item.get('name')) - mymovie = rel.movie.to_dict({ + mov = rel.movie.to_dict({ 'profile': {'types': {'quality': {}}}, 'releases': {'status': {}, 'quality': {}}, 'library': {'titles': {}, 'files':{}}, 'files': {} }) - log.debug('Checking status snatched release: %s' , mymovie['library'].get('identifier')) - # check status - downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mymovie) + downloadfailed = fireEvent('getdownloadfailed', data = item, movie = mov) if downloadfailed: - log.debug('Download of %s failed', item['name']) - # if failed set status to ignored rel.status_id = ignored_status.get('id') db.commit() # search/download again log.info('Download of %s failed, trying next release...', item['name']) - fireEvent('searcher.single', rel.movie) + fireEvent('searcher.single', mov) return From b262ed59a8d6f07a882da79c09bc049e4e5dd265 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Mon, 30 Jul 2012 19:15:25 +0200 Subject: [PATCH 04/11] Fixed sab api bug --- couchpotato/core/downloaders/sabnzbd/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 45b4e25b..10180991 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -98,7 +98,8 @@ class Sabnzbd(Downloader): 'apikey': self.conf('api_key'), 'mode': 'history', 'name': 'delete', - 'value' : slot['id'] + 'del_files': '1', + 'value': slot['nzo_id'] } url = cleanHost(self.conf('host')) + "api?" + tryUrlencode(params) try: From 4aae3c45eadc76ee276a305d26e9e862ace2eff2 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 02:38:00 +0200 Subject: [PATCH 05/11] Bug fixes, it should actually restart the download now --- couchpotato/core/downloaders/base.py | 4 ++-- couchpotato/core/downloaders/sabnzbd/main.py | 4 ++-- couchpotato/core/plugins/renamer/main.py | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index e0dad913..07536a49 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -15,10 +15,10 @@ class Downloader(Plugin): addEvent('download', self.download) addEvent('getdownloadfailed', self.getdownloadfailed) - def download(self, data = {}): + def download(self, data = {}, movie = {}, manual = False, filedata = None): pass - def getdownloadfailed(self, data = {}): + def getdownloadfailed(self, data = {}, movie = {}): pass def createNzbName(self, data, movie): diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 10180991..010c1be2 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -65,8 +65,8 @@ class Sabnzbd(Downloader): log.error("Unknown error: " + result[:40]) return False - def getdownloadfailed(self, data = {}, movie = {}, manual = False): - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): + def getdownloadfailed(self, data = {}, movie = {}): + if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index f0887acf..8855df4d 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -482,7 +482,13 @@ class Renamer(Plugin): rels = db.query(Relea).filter_by(status_id = snatched_status.get('id')) for rel in rels: - log.debug('Checking snatched release: %s' , rel.movie.library.titles[0].title) + + # 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: @@ -506,6 +512,18 @@ class Renamer(Plugin): 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) From db2c9e51565cd71605ba8484edf35c6f68c53ccf Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 03:09:34 +0200 Subject: [PATCH 06/11] Added config options --- .../core/downloaders/sabnzbd/__init__.py | 16 ++++++++- couchpotato/core/downloaders/sabnzbd/main.py | 36 ++++++++++--------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index ac0ce05c..938c6748 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -35,11 +35,25 @@ config = [{ }, { 'name': 'manual', - 'default': 0, + 'default': False, 'type': 'bool', '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 next the next best release for a movie after a download failed.', + }, + { + 'name': 'delete failed', + 'default': True, + 'type': 'bool', + 'advanced': True, + 'description': 'Delete a release after it\'s download failed.', + }, ], } ], diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 010c1be2..bb7a2eb8 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -3,8 +3,6 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog import traceback -import urllib2 -import requests import json log = CPLog(__name__) @@ -69,6 +67,9 @@ class Sabnzbd(Downloader): if self.isDisabled(manual = True) or not self.isCorrectType(data.get('type')): return + if not self.conf('download failed', default = True): + return False + log.info('Checking download status of "%s" at SABnzbd.', data.get('name')) params = { @@ -80,7 +81,7 @@ class Sabnzbd(Downloader): log.debug('Opening: %s', url) try: - history = json.load(urllib2.urlopen(url)) + history = json.load(self.urlopen(url)) except: log.error(traceback.format_exc()) return False @@ -91,23 +92,24 @@ class Sabnzbd(Downloader): 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': - log.debug('%s failed downloading, deleting...', slot['name']) # Delete failed download - 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()) + 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()) - # Return failed + # Return download failed return True return False From 100b563e107ec789de93c2ba417eca18da59dfa8 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Wed, 1 Aug 2012 03:11:19 +0200 Subject: [PATCH 07/11] typo --- couchpotato/core/downloaders/sabnzbd/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd/__init__.py b/couchpotato/core/downloaders/sabnzbd/__init__.py index 938c6748..822ba61f 100644 --- a/couchpotato/core/downloaders/sabnzbd/__init__.py +++ b/couchpotato/core/downloaders/sabnzbd/__init__.py @@ -45,7 +45,7 @@ config = [{ 'default': True, 'type': 'bool', 'advanced': True, - 'description': 'Try next the next best release for a movie after a download failed.', + 'description': 'Try the next best release for a movie after a download failed.', }, { 'name': 'delete failed', From 4223ed4b5aa97ee5aa0871e51a61af3e6c234b57 Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 18:59:04 +0200 Subject: [PATCH 08/11] 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 From 928c440f90b77a793ad64368afcb8cd31a3a883f Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 19:01:19 +0200 Subject: [PATCH 09/11] renamer cleanup --- couchpotato/core/plugins/renamer/main.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index 4c80fb08..a7744993 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -6,7 +6,7 @@ from couchpotato.core.helpers.request import jsonified from couchpotato.core.helpers.variable import getExt, mergeDicts, getTitle from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin -from couchpotato.core.settings.model import Library, File, Profile, Release as Relea +from couchpotato.core.settings.model import Library, File, Profile from couchpotato.environment import Env import os import re @@ -471,5 +471,3 @@ class Renamer(Plugin): os.rmdir(folder) except: log.error('Couldn\'t remove empty directory %s: %s', (folder, traceback.format_exc())) - - From 0193f7ad8116f681d4666247a34f546f564bff5d Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 20:05:31 +0200 Subject: [PATCH 10/11] scan only once when multiple releases are snatched --- couchpotato/core/plugins/searcher/main.py | 68 ++++++++++++----------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index b1b285ef..2d2fdac6 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -450,7 +450,8 @@ class Searcher(Plugin): db = get_session() rels = db.query(Release).filter_by(status_id = snatched_status.get('id')) - log.info('Checking snatched releases...') + log.info('Checking status snatched releases...') + scanrequired = False for rel in rels: @@ -474,44 +475,47 @@ class Searcher(Plugin): # check status downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) - log.debug('Download staus: %s' , downloadstatus[0]) + if downloadstatus == None: # Downloader not compatible with download status + scanrequired = True - if downloadstatus[0] == 'Failed': - # if failed set status to ignored - rel.status_id = ignored_status.get('id') - db.commit() + else: + log.debug('Download staus: %s' , downloadstatus[0]) - # search/download again - # if downloaded manually: # this is currently not stored... - # log.info('Download of %s failed...', item['name']) - # return + if downloadstatus[0] == 'Failed': + # if failed set status to ignored + rel.status_id = ignored_status.get('id') + db.commit() - if self.conf('failed download', default = True): + # 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 - 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']) + if self.conf('failed download', default = True): - elif downloadstatus[0] == 'Completed': - log.info('Download of %s completed!', item['name']) - fireEvent('renamer.scan') + #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] == 'Not found': - log.info('%s not found in SabNZBd', item['name']) - rel.status_id = ignored_status.get('id') - db.commit() + elif downloadstatus[0] == 'Completed': + log.info('Download of %s completed!', item['name']) + scanrequired = True - elif downloadstatus[0] == None: # Downloader not compatible with download status or - fireEvent('renamer.scan') + elif downloadstatus[0] == 'Not found': + log.info('%s not found in downloaders', item['name']) + rel.status_id = ignored_status.get('id') + db.commit() - # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + if scanrequired: + fireEvent('renamer.scan') return From 71c181379a9008f14b3c416494d34ea71b05d86a Mon Sep 17 00:00:00 2001 From: mano3m <-> Date: Thu, 2 Aug 2012 20:56:05 +0200 Subject: [PATCH 11/11] Fixed behavior for not compatible downloaders + typo's --- couchpotato/core/plugins/searcher/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index 2d2fdac6..fbfe0fa9 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -475,11 +475,12 @@ class Searcher(Plugin): # check status downloadstatus = fireEvent('getdownloadstatus', data = item, movie = movie) - if downloadstatus == None: # Downloader not compatible with download status + if not downloadstatus: # Downloader not compatible with download status + log.debug('Download status functionality is not implemented for active downloaders.') scanrequired = True else: - log.debug('Download staus: %s' , downloadstatus[0]) + log.debug('Download status: %s' , downloadstatus[0]) if downloadstatus[0] == 'Failed': # if failed set status to ignored @@ -514,7 +515,7 @@ class Searcher(Plugin): rel.status_id = ignored_status.get('id') db.commit() - # Note that Queued, Downloading, Paused, Repairn and Unpackimg are also available as status + # Note that Queued, Downloading, Paused, Repair and Unpackimg are also available as status for SabNZBd if scanrequired: fireEvent('renamer.scan')