diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 7a695e21..377e6062 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -33,17 +33,36 @@ class Downloader(Provider): ] def __init__(self): - addEvent('download', self.download) - addEvent('download.status', self.getAllDownloadStatus) - addEvent('download.remove_failed', self.removeFailed) + addEvent('download', self._download) + addEvent('download.enabled', self._isEnabled) + addEvent('download.enabled_types', self.getEnabledDownloadType) + addEvent('download.status', self._getAllDownloadStatus) + addEvent('download.remove_failed', self._removeFailed) - def download(self, data = {}, movie = {}, manual = False, filedata = None): - pass + def getEnabledDownloadType(self): + for download_type in self.type: + if self.isEnabled(manual = True, data = {'type': download_type}): + return self.type - def getAllDownloadStatus(self): + return [] + + def _download(self, data = {}, movie = {}, manual = False, filedata = None): + if self.isDisabled(manual, data): + return + return self.download(data = data, movie = movie, filedata = filedata) + + def _getAllDownloadStatus(self): + if self.isDisabled(manual = True, data = {}): + return + + return self.getAllDownloadStatus() + + def _removeFailed(self, item): + if self.conf('delete_failed', default = True): + return self.removeFailed(self, item) return False - def removeFailed(self, name = {}, nzo_id = {}): + def removeFailed(self, item): return False def isCorrectType(self, item_type): @@ -77,9 +96,14 @@ class Downloader(Provider): log.error('Failed converting magnet url to torrent: %s', (torrent_hash)) return False - def isDisabled(self, manual): - return not self.isEnabled(manual) + def isDisabled(self, manual, data): + return not self.isEnabled(manual, data) - def isEnabled(self, manual): + def _isEnabled(self, manual, data = {}): + if not self.isEnabled(manual, data): + return + return True + + def isEnabled(self, manual, data = {}): d_manual = self.conf('manual', default = False) - return super(Downloader, self).isEnabled() and ((d_manual and manual) or (d_manual is False)) + return super(Downloader, self).isEnabled() and ((d_manual and manual) or (d_manual is False)) and self.isCorrectType(data.get('type')) diff --git a/couchpotato/core/downloaders/blackhole/main.py b/couchpotato/core/downloaders/blackhole/main.py index ca38fc49..402e607a 100644 --- a/couchpotato/core/downloaders/blackhole/main.py +++ b/couchpotato/core/downloaders/blackhole/main.py @@ -10,11 +10,7 @@ class Blackhole(Downloader): type = ['nzb', 'torrent', 'torrent_magnet'] - def download(self, data = {}, movie = {}, manual = False, filedata = None): - if self.isDisabled(manual) or \ - (not self.isCorrectType(data.get('type')) or \ - (not self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])): - return + def download(self, data = {}, movie = {}, filedata = None): directory = self.conf('directory') if not directory or not os.path.isdir(directory): @@ -52,4 +48,17 @@ class Blackhole(Downloader): except: log.info('Failed to download file %s: %s', (data.get('name'), traceback.format_exc())) return False + return False + + def getEnabledDownloadType(self): + if self.conf('use_for') == 'both': + return super(Blackhole, self).getEnabledDownloadType() + elif self.conf('use_for') == 'torrent': + return ['torrent', 'torrent_magnet'] + else: + return ['nzb'] + + def isEnabled(self, manual, data = {}): + return super(Blackhole, self).isEnabled(manual, data) and \ + ((self.conf('use_for') in ['both', 'torrent' if 'torrent' in data.get('type') else data.get('type')])) diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py index 75a48130..c3b9703e 100644 --- a/couchpotato/core/downloaders/nzbget/main.py +++ b/couchpotato/core/downloaders/nzbget/main.py @@ -15,10 +15,7 @@ class NZBGet(Downloader): url = 'http://nzbget:%(password)s@%(host)s/xmlrpc' - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data = {}, movie = {}, filedata = None): if not filedata: log.error('Unable to get NZB file: %s', traceback.format_exc()) diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py index 32b1e6e8..1462c678 100644 --- a/couchpotato/core/downloaders/nzbvortex/main.py +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -22,10 +22,7 @@ class NZBVortex(Downloader): api_level = None session_id = None - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')) or not self.getApiLevel(): - return + def download(self, data = {}, movie = {}, filedata = None): # Send the nzb try: @@ -39,9 +36,6 @@ class NZBVortex(Downloader): def getAllDownloadStatus(self): - if self.isDisabled(manual = True): - return False - raw_statuses = self.call('nzb') statuses = [] @@ -66,9 +60,6 @@ class NZBVortex(Downloader): def removeFailed(self, item): - if not self.conf('delete_failed', default = True): - return False - log.info('%s failed downloading, deleting...', item['name']) try: @@ -153,6 +144,9 @@ class NZBVortex(Downloader): return self.api_level + def isEnabled(self, manual, data): + return super(NZBVortex, self).isEnabled(manual, data) and self.getApiLevel() + class HTTPSConnection(httplib.HTTPSConnection): def __init__(self, *args, **kwargs): diff --git a/couchpotato/core/downloaders/pneumatic/main.py b/couchpotato/core/downloaders/pneumatic/main.py index 57e47e60..5e2b7854 100644 --- a/couchpotato/core/downloaders/pneumatic/main.py +++ b/couchpotato/core/downloaders/pneumatic/main.py @@ -11,9 +11,7 @@ class Pneumatic(Downloader): type = ['nzb'] strm_syntax = 'plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb=%s&nzbname=%s' - def download(self, data = {}, movie = {}, manual = False, filedata = None): - if self.isDisabled(manual) or (not self.isCorrectType(data.get('type'))): - return + def download(self, data = {}, movie = {}, filedata = None): directory = self.conf('directory') if not directory or not os.path.isdir(directory): diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index e3848e40..91302780 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -12,10 +12,7 @@ class Sabnzbd(Downloader): type = ['nzb'] - def download(self, data = {}, movie = {}, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data = {}, movie = {}, filedata = None): log.info('Sending "%s" to SABnzbd.', data.get('name')) @@ -65,8 +62,6 @@ class Sabnzbd(Downloader): return False def getAllDownloadStatus(self): - if self.isDisabled(manual = True): - return False log.debug('Checking SABnzbd download status.') @@ -122,9 +117,6 @@ class Sabnzbd(Downloader): def removeFailed(self, item): - if not self.conf('delete_failed', default = True): - return False - log.info('%s failed downloading, deleting...', item['name']) try: diff --git a/couchpotato/core/downloaders/synology/main.py b/couchpotato/core/downloaders/synology/main.py index 02d91d56..6e405980 100644 --- a/couchpotato/core/downloaders/synology/main.py +++ b/couchpotato/core/downloaders/synology/main.py @@ -14,10 +14,7 @@ class Synology(Downloader): type = ['torrent_magnet'] log = CPLog(__name__) - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.error('Sending "%s" (%s) to Synology.', (data.get('name'), data.get('type'))) diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 22e01c2f..63c9de9d 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -16,10 +16,7 @@ class Transmission(Downloader): type = ['torrent', 'torrent_magnet'] log = CPLog(__name__) - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.debug('Sending "%s" (%s) to Transmission.', (data.get('name'), data.get('type'))) diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index 7e703d0c..e1e67de1 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -20,10 +20,7 @@ class uTorrent(Downloader): type = ['torrent', 'torrent_magnet'] utorrent_api = None - def download(self, data, movie, manual = False, filedata = None): - - if self.isDisabled(manual) or not self.isCorrectType(data.get('type')): - return + def download(self, data, movie, filedata = None): log.debug('Sending "%s" (%s) to uTorrent.', (data.get('name'), data.get('type'))) diff --git a/couchpotato/core/event.py b/couchpotato/core/event.py index 8365ae57..aa05ce0f 100644 --- a/couchpotato/core/event.py +++ b/couchpotato/core/event.py @@ -115,7 +115,8 @@ def fireEvent(name, *args, **kwargs): elif isinstance(results[0], list): merged = [] for result in results: - merged += result + if result not in merged: + merged += result results = merged diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index d5e1f672..eb953cd1 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -91,31 +91,44 @@ class Searcher(Plugin): 'to_go': len(movies), } - for movie in movies: - movie_dict = movie.to_dict({ - 'profile': {'types': {'quality': {}}}, - 'releases': {'status': {}, 'quality': {}}, - 'library': {'titles': {}, 'files':{}}, - 'files': {} - }) + try: + search_types = self.getSearchTypes() - try: - self.single(movie_dict) - except IndexError: - log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) - fireEvent('library.update', movie_dict['library']['identifier'], force = True) - except: - log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) + for movie in movies: + movie_dict = movie.to_dict({ + 'profile': {'types': {'quality': {}}}, + 'releases': {'status': {}, 'quality': {}}, + 'library': {'titles': {}, 'files':{}}, + 'files': {} + }) - self.in_progress['to_go'] -= 1 + try: + self.single(movie_dict, search_types) + except IndexError: + log.error('Forcing library update for %s, if you see this often, please report: %s', (movie_dict['library']['identifier'], traceback.format_exc())) + fireEvent('library.update', movie_dict['library']['identifier'], force = True) + except: + log.error('Search failed for %s: %s', (movie_dict['library']['identifier'], traceback.format_exc())) - # Break if CP wants to shut down - if self.shuttingDown(): - break + self.in_progress['to_go'] -= 1 + + # Break if CP wants to shut down + if self.shuttingDown(): + break + + except SearchSetupError: + pass self.in_progress = False - def single(self, movie): + def single(self, movie, search_types = None): + + # Find out search type + try: + if not search_types: + search_types = self.getSearchTypes() + except SearchSetupError: + return done_status = fireEvent('status.get', 'done', single = True) @@ -140,6 +153,7 @@ class Searcher(Plugin): fireEvent('notify.frontend', type = 'searcher.started.%s' % movie['id'], data = True, message = 'Searching for "%s"' % default_title) + ret = False for quality_type in movie['profile']['types']: if not self.couldBeReleased(quality_type['quality']['identifier'], release_dates, pre_releases): @@ -159,7 +173,11 @@ class Searcher(Plugin): log.info('Search for %s in %s', (default_title, quality_type['quality']['label'])) quality = fireEvent('quality.single', identifier = quality_type['quality']['identifier'], single = True) - results = fireEvent('yarr.search', movie, quality, merge = True) + results = [] + for search_type in search_types: + type_results = fireEvent('%s.search' % search_type, movie, quality, merge = True) + if type_results: + results += type_results sorted_results = sorted(results, key = lambda k: k['score'], reverse = True) if len(sorted_results) == 0: @@ -230,6 +248,12 @@ class Searcher(Plugin): break elif downloaded != 'try_next': break + + # Remove releases that aren't found anymore + for release in movie.get('releases', []): + if release.get('identifier') not in found_releases: + fireEvent('release.delete', release.get('id'), single = True) + else: log.info('Better quality (%s) already available or snatched for %s', (quality_type['quality']['label'], default_title)) fireEvent('movie.restatus', movie['id']) @@ -239,72 +263,93 @@ class Searcher(Plugin): if self.shuttingDown() or ret: break - # Remove releases that aren't found anymore - for release in movie.get('releases', []): - if release.get('identifier') not in found_releases: - fireEvent('release.delete', release.get('id'), single = True) - fireEvent('notify.frontend', type = 'searcher.ended.%s' % movie['id'], data = True) return ret def download(self, data, movie, manual = False): - snatched_status = fireEvent('status.get', 'snatched', single = True) + # Test to see if any downloaders are enabled for this type + downloader_enabled = fireEvent('download.enabled', manual, data, single = True) - # Download movie to temp - filedata = None - if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))): - filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id')) - if filedata == 'try_next': - return filedata + if downloader_enabled: - successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) + snatched_status = fireEvent('status.get', 'snatched', single = True) - if successful: + # Download movie to temp + filedata = None + if data.get('download') and (ismethod(data.get('download')) or isfunction(data.get('download'))): + filedata = data.get('download')(url = data.get('url'), nzb_id = data.get('id')) + if filedata == 'try_next': + return filedata - try: - # Mark release as snatched - db = get_session() - rls = db.query(Release).filter_by(identifier = md5(data['url'])).first() - if rls: - rls.status_id = snatched_status.get('id') - db.commit() + successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) - log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) - snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie) - log.info(snatch_message) - fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict()) + if successful: - # If renamer isn't used, mark movie done - if not Env.setting('enabled', 'renamer'): - active_status = fireEvent('status.get', 'active', single = True) - done_status = fireEvent('status.get', 'done', single = True) - try: - if movie['status_id'] == active_status.get('id'): - for profile_type in movie['profile']['types']: - if rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']: - log.info('Renamer disabled, marking movie as finished: %s', log_movie) + try: + # Mark release as snatched + db = get_session() + rls = db.query(Release).filter_by(identifier = md5(data['url'])).first() + if rls: + rls.status_id = snatched_status.get('id') + db.commit() - # Mark release done - rls.status_id = done_status.get('id') - db.commit() + log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) + snatch_message = 'Snatched "%s": %s' % (data.get('name'), log_movie) + log.info(snatch_message) + fireEvent('movie.snatched', message = snatch_message, data = rls.to_dict()) - # Mark movie done - mvie = db.query(Movie).filter_by(id = movie['id']).first() - mvie.status_id = done_status.get('id') - db.commit() - except: - log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc()) + # If renamer isn't used, mark movie done + if not Env.setting('enabled', 'renamer'): + active_status = fireEvent('status.get', 'active', single = True) + done_status = fireEvent('status.get', 'done', single = True) + try: + if movie['status_id'] == active_status.get('id'): + for profile_type in movie['profile']['types']: + if rls and profile_type['quality_id'] == rls.quality.id and profile_type['finish']: + log.info('Renamer disabled, marking movie as finished: %s', log_movie) - except: - log.error('Failed marking movie finished: %s', traceback.format_exc()) + # Mark release done + rls.status_id = done_status.get('id') + db.commit() - return True + # Mark movie done + mvie = db.query(Movie).filter_by(id = movie['id']).first() + mvie.status_id = done_status.get('id') + db.commit() + except: + log.error('Failed marking movie finished, renamer disabled: %s', traceback.format_exc()) + + except: + log.error('Failed marking movie finished: %s', traceback.format_exc()) + + return True + + log.info('Tried to download, but none of the "%s" downloaders are enabled', (data.get('type', ''))) - log.info('Tried to download, but none of the downloaders are enabled') return False + def getSearchTypes(self): + + download_types = fireEvent('download.enabled_types', merge = True) + provider_types = fireEvent('provider.enabled_types', merge = True) + + if download_types and len(list(set(provider_types) & set(download_types))) == 0: + log.error('There aren\'t any providers enabled for your downloader (%s). Check your settings.', ','.join(download_types)) + raise NoProviders + + for useless_provider in list(set(provider_types) - set(download_types)): + log.debug('Provider for "%s" enabled, but no downloader.', useless_provider) + + search_types = download_types + + if len(search_types) == 0: + log.error('There aren\'t any downloaders enabled. Please pick one in settings.') + raise NoDownloaders + + return search_types + def correctMovie(self, nzb = {}, movie = {}, quality = {}, **kwargs): imdb_results = kwargs.get('imdb_results', False) @@ -548,3 +593,12 @@ class Searcher(Plugin): except: log.error('Failed searching for next release: %s', traceback.format_exc()) return False + +class SearchSetupError(Exception): + pass + +class NoDownloaders(SearchSetupError): + pass + +class NoProviders(SearchSetupError): + pass diff --git a/couchpotato/core/providers/base.py b/couchpotato/core/providers/base.py index 9c143d81..a07282af 100644 --- a/couchpotato/core/providers/base.py +++ b/couchpotato/core/providers/base.py @@ -84,8 +84,16 @@ class YarrProvider(Provider): login_opener = None def __init__(self): + addEvent('provider.enabled_types', self.getEnabledProviderType) addEvent('provider.belongs_to', self.belongsTo) addEvent('yarr.search', self.search) + addEvent('%s.search' % self.type, self.search) + + def getEnabledProviderType(self): + if self.isEnabled(): + return self.type + else: + return [] def login(self): diff --git a/couchpotato/core/providers/nzb/newznab/main.py b/couchpotato/core/providers/nzb/newznab/main.py index e6529733..f80e07cc 100644 --- a/couchpotato/core/providers/nzb/newznab/main.py +++ b/couchpotato/core/providers/nzb/newznab/main.py @@ -109,10 +109,18 @@ class Newznab(NZBProvider, RSS): return cleanHost(host) + 'api?t=' + type - def isDisabled(self, host): + def isDisabled(self, host = None): return not self.isEnabled(host) - def isEnabled(self, host): + def isEnabled(self, host = None): + + # Return true if at least one is enabled and no host is given + if host is None: + for host in self.getHosts(): + if self.isEnabled(host): + return True + return False + return NZBProvider.isEnabled(self) and host['host'] and host['api_key'] and int(host['use']) def getApiExt(self, host):