From b0d556c8eb18a15565ababb435e9e61dbca1f36d Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 24 Mar 2013 19:54:11 +0100 Subject: [PATCH] Try and find release status by ID instead of name. closes #1511 Thanks to @mano3m --- couchpotato/core/downloaders/base.py | 38 +++++++ .../core/downloaders/nzbget/__init__.py | 6 + couchpotato/core/downloaders/nzbget/main.py | 106 +++++++++++++++++- .../core/downloaders/nzbvortex/main.py | 8 +- couchpotato/core/downloaders/sabnzbd/main.py | 44 ++++---- .../core/downloaders/transmission/main.py | 2 +- couchpotato/core/downloaders/utorrent/main.py | 6 +- couchpotato/core/plugins/renamer/main.py | 10 +- couchpotato/core/plugins/searcher/main.py | 17 ++- 9 files changed, 200 insertions(+), 37 deletions(-) diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 70500dc0..69449249 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -1,5 +1,6 @@ from base64 import b32decode, b16encode from couchpotato.core.event import addEvent +from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog from couchpotato.core.providers.base import Provider import random @@ -103,6 +104,12 @@ class Downloader(Provider): log.error('Failed converting magnet url to torrent: %s', (torrent_hash)) return False + def downloadReturnId(self, download_id): + return { + 'downloader': self.getName(), + 'id': download_id + } + def isDisabled(self, manual, data): return not self.isEnabled(manual, data) @@ -116,3 +123,34 @@ class Downloader(Provider): return super(Downloader, self).isEnabled() and \ ((d_manual and manual) or (d_manual is False)) and \ (not data or self.isCorrectType(data.get('type'))) + + +class StatusList(list): + + provider = None + + def __init__(self, provider, **kwargs): + + self.provider = provider + self.kwargs = kwargs + + super(StatusList, self).__init__() + + def extend(self, results): + for r in results: + self.append(r) + + def append(self, result): + new_result = self.fillResult(result) + super(StatusList, self).append(new_result) + + def fillResult(self, result): + + defaults = { + 'id': 0, + 'status': 'busy', + 'downloader': self.provider.getName(), + } + + return mergeDicts(defaults, result) + diff --git a/couchpotato/core/downloaders/nzbget/__init__.py b/couchpotato/core/downloaders/nzbget/__init__.py index 403a7e7d..5349914a 100644 --- a/couchpotato/core/downloaders/nzbget/__init__.py +++ b/couchpotato/core/downloaders/nzbget/__init__.py @@ -48,6 +48,12 @@ config = [{ 'advanced': True, 'description': 'Disable this downloader for automated searches, but use it when I manually send a release.', }, + { + 'name': 'delete_failed', + 'default': True, + 'type': 'bool', + 'description': 'Delete a release after the download has failed.', + }, ], } ], diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py index 82d8a363..b767ef75 100644 --- a/couchpotato/core/downloaders/nzbget/main.py +++ b/couchpotato/core/downloaders/nzbget/main.py @@ -1,9 +1,11 @@ from base64 import standard_b64encode -from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog +from datetime import timedelta import re +import shutil import socket import traceback import xmlrpclib @@ -50,7 +52,107 @@ class NZBGet(Downloader): if xml_response: log.info('NZB sent successfully to NZBGet') - return True + groups = rpc.listgroups() + nzb_id = [item['NZBID'] for item in groups if item['NZBFilename'] == nzb_name][0] + return self.downloadReturnId(nzb_id) else: log.error('NZBGet could not add %s to the queue.', nzb_name) return False + + def getAllDownloadStatus(self): + + log.debug('Checking NZBGet download status.') + + url = self.url % {'host': self.conf('host'), 'password': self.conf('password')} + + rpc = xmlrpclib.ServerProxy(url) + try: + if rpc.writelog('INFO', 'CouchPotato connected to check status'): + log.info('Successfully connected to NZBGet') + else: + log.info('Successfully connected to NZBGet, but unable to send a message') + except socket.error: + log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.') + return False + except xmlrpclib.ProtocolError, e: + if e.errcode == 401: + log.error('Password is incorrect.') + else: + log.error('Protocol Error: %s', e) + return False + + # Get NZBGet data + try: + status = rpc.status() + groups = rpc.listgroups() + queue = rpc.postqueue(0) + history = rpc.history() + except: + log.error('Failed getting data: %s', traceback.format_exc(1)) + return False + + statuses = StatusList(self) + + for item in groups: + log.debug('Found %s in NZBGet download queue', item['NZBFilename']) + statuses.append({ + 'id': item['NZBID'], + 'name': item['NZBFilename'], + 'original_status': 'DOWNLOADING' if item['ActiveDownloads'] > 0 else 'QUEUED', + # Seems to have no native API function for time left. This will return the time left after NZBGet started downloading this item + 'timeleft': str(timedelta(seconds = item['RemainingSizeMB'] / status['DownloadRate'] * 2 ^ 20)) if item['ActiveDownloads'] > 0 and not (status['DownloadPaused'] or status['Download2Paused']) else -1, + }) + + for item in queue: + log.debug('Found %s in NZBGet postprocessing queue', item['NZBFilename']) + statuses.append({ + 'id': item['NZBID'], + 'name': item['NZBFilename'], + 'original_status': item['Stage'], + 'timeleft': str(timedelta(seconds = 0)) if not status['PostPaused'] else -1, + }) + + for item in history: + log.debug('Found %s in NZBGet history. ParStatus: %s, ScriptStatus: %s, Log: %s', (item['NZBFilename'] , item['ParStatus'], item['ScriptStatus'] , item['Log'])) + statuses.append({ + 'id': item['NZBID'], + 'name': item['NZBFilename'], + 'status': 'completed' if item['ParStatus'] == 'SUCCESS' and item['ScriptStatus'] == 'SUCCESS' else 'failed', + 'original_status': item['ParStatus'] + ', ' + item['ScriptStatus'], + 'timeleft': str(timedelta(seconds = 0)), + }) + + return statuses + + def removeFailed(self, item): + + log.info('%s failed downloading, deleting...', item['name']) + + url = self.url % {'host': self.conf('host'), 'password': self.conf('password')} + + rpc = xmlrpclib.ServerProxy(url) + try: + if rpc.writelog('INFO', 'CouchPotato connected to delete some history'): + log.info('Successfully connected to NZBGet') + else: + log.info('Successfully connected to NZBGet, but unable to send a message') + except socket.error: + log.error('NZBGet is not responding. Please ensure that NZBGet is running and host setting is correct.') + return False + except xmlrpclib.ProtocolError, e: + if e.errcode == 401: + log.error('Password is incorrect.') + else: + log.error('Protocol Error: %s', e) + return False + + try: + history = rpc.history() + if rpc.editqueue('HistoryDelete', 0, "", [int(item['id'][len('NZBGet_'):])]): + path = [hist['DestDir'] for hist in history if hist['NZBID'] == item['id']][0] + shutil.rmtree(path, True) + except: + log.error('Failed deleting: %s', traceback.format_exc(0)) + return False + + return True diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py index 1462c678..e6cbd027 100644 --- a/couchpotato/core/downloaders/nzbvortex/main.py +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -1,5 +1,5 @@ from base64 import b64encode -from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.helpers.encoding import tryUrlencode, ss from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog @@ -29,7 +29,9 @@ class NZBVortex(Downloader): nzb_filename = self.createFileName(data, filedata, movie) self.call('nzb/add', params = {'file': (ss(nzb_filename), filedata)}, multipart = True) - return True + raw_statuses = self.call('nzb') + nzb_id = [item['id'] for item in raw_statuses.get('nzbs', []) if item['name'] == nzb_filename][0] + return self.downloadReturnId(nzb_id) except: log.error('Something went wrong sending the NZB file: %s', traceback.format_exc()) return False @@ -38,7 +40,7 @@ class NZBVortex(Downloader): raw_statuses = self.call('nzb') - statuses = [] + statuses = StatusList(self) for item in raw_statuses.get('nzbs', []): # Check status diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index a287f119..8b374698 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -1,4 +1,4 @@ -from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.helpers.encoding import tryUrlencode, ss from couchpotato.core.helpers.variable import cleanHost, mergeDicts from couchpotato.core.logger import CPLog @@ -17,8 +17,7 @@ class Sabnzbd(Downloader): log.info('Sending "%s" to SABnzbd.', data.get('name')) - params = { - 'apikey': self.conf('api_key'), + req_params = { 'cat': self.conf('category'), 'mode': 'addurl', 'nzbname': self.createNzbName(data, movie), @@ -31,17 +30,15 @@ class Sabnzbd(Downloader): # If it's a .rar, it adds the .rar extension, otherwise it stays .nzb nzb_filename = self.createFileName(data, filedata, movie) - params['mode'] = 'addfile' + req_params['mode'] = 'addfile' else: - params['name'] = data.get('url') - - url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(params) + req_params['name'] = data.get('url') try: - if params.get('mode') is 'addfile': - sab = self.urlopen(url, timeout = 60, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) + if req_params.get('mode') is 'addfile': + sab_data = self.call(req_params, params = {'nzbfile': (ss(nzb_filename), filedata)}, multipart = True) else: - sab = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) + sab_data = self.call(req_params) except URLError: log.error('Failed sending release, probably wrong HOST: %s', traceback.format_exc(0)) return False @@ -49,17 +46,19 @@ class Sabnzbd(Downloader): log.error('Failed sending release, use API key, NOT the NZB key: %s', traceback.format_exc(0)) return False - result = sab.strip() - if not result: - log.error('SABnzbd didn\'t return anything.') + if sab_data.get('error'): + log.error('Error getting data from SABNZBd: %s', sab_data.get('error')) return False - log.debug('Result text from SAB: %s', result[:40]) - if result[:2] == 'ok': + log.debug('Result from SAB: %s', sab_data) + if sab_data.get('status'): log.info('NZB sent to SAB successfully.') - return True + if filedata: + return self.downloadReturnId(sab_data.get('nzo_ids')[0]) + else: + return True else: - log.error(result[:40]) + log.error(sab_data) return False def getAllDownloadStatus(self): @@ -85,14 +84,13 @@ class Sabnzbd(Downloader): log.error('Failed getting history json: %s', traceback.format_exc(1)) return False - statuses = [] + statuses = StatusList(self) # 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, }) @@ -133,21 +131,21 @@ class Sabnzbd(Downloader): return True - def call(self, params, use_json = True): + def call(self, request_params, use_json = True, **kwargs): - url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(params, { + url = cleanHost(self.conf('host')) + 'api?' + tryUrlencode(mergeDicts(request_params, { 'apikey': self.conf('api_key'), 'output': 'json' })) - data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}) + data = self.urlopen(url, timeout = 60, show_error = False, headers = {'User-Agent': Env.getIdentifier()}, **kwargs) if use_json: d = json.loads(data) if d.get('error'): log.error('Error getting data from SABNZBd: %s', d.get('error')) return {} - return d[params['mode']] + return d.get(request_params['mode']) or d else: return data diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 5c13af6e..6c4607fb 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -62,7 +62,7 @@ class Transmission(Downloader): if torrent_params: trpc.set_torrent(remote_torrent['torrent-added']['hashString'], torrent_params) - return True + return self.downloadReturnId(remote_torrent['torrent-added']['hashString']) except Exception, err: log.error('Failed to change settings for transfer: %s', err) return False diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index 5953b117..c64db135 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -1,6 +1,6 @@ from base64 import b16encode, b32decode from bencode import bencode, bdecode -from couchpotato.core.downloaders.base import Downloader +from couchpotato.core.downloaders.base import Downloader, StatusList from couchpotato.core.helpers.encoding import isInt, ss from couchpotato.core.logger import CPLog from hashlib import sha1 @@ -66,7 +66,7 @@ class uTorrent(Downloader): self.utorrent_api.set_torrent(torrent_hash, torrent_params) if self.conf('paused', default = 0): self.utorrent_api.pause_torrent(torrent_hash) - return True + return self.downloadReturnId(torrent_hash) except Exception, err: log.error('Failed to send torrent to uTorrent: %s', err) return False @@ -103,7 +103,7 @@ class uTorrent(Downloader): log.debug('Nothing in queue') return False - statuses = [] + statuses = StatusList(self) # Get torrents for item in queue.get('torrents', []): diff --git a/couchpotato/core/plugins/renamer/main.py b/couchpotato/core/plugins/renamer/main.py index b7062157..aba20724 100644 --- a/couchpotato/core/plugins/renamer/main.py +++ b/couchpotato/core/plugins/renamer/main.py @@ -571,8 +571,16 @@ class Renamer(Plugin): found = False for item in statuses: - if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']: + found_release = False + if rel_dict['info'].get('download_id'): + if item['id'] == rel_dict['info']['download_id'] and item['downloader'] == rel_dict['info']['download_downloader']: + log.debug('Found release by id: %s', item['id']) + found_release = True + else: + if item['name'] == nzbname or rel_dict['info']['name'] in item['name'] or getImdb(item['name']) == movie_dict['library']['identifier']: + found_release = True + if found_release: 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)) diff --git a/couchpotato/core/plugins/searcher/main.py b/couchpotato/core/plugins/searcher/main.py index b4c4937a..a34b0b47 100644 --- a/couchpotato/core/plugins/searcher/main.py +++ b/couchpotato/core/plugins/searcher/main.py @@ -285,10 +285,10 @@ class Searcher(Plugin): if filedata == 'try_next': return filedata - successful = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) - - if successful: + download_result = fireEvent('download', data = data, movie = movie, manual = manual, filedata = filedata, single = True) + log.debug('Downloader result: %s', download_result) + if download_result: try: # Mark release as snatched db = get_session() @@ -298,6 +298,15 @@ class Searcher(Plugin): done_status = fireEvent('status.get', 'done', single = True) rls.status_id = done_status.get('id') if not renamer_enabled else snatched_status.get('id') + + # Save download-id info if returned + if isinstance(download_result, dict): + for key in download_result: + rls_info = ReleaseInfo( + identifier = 'download_%s' % key, + value = toUnicode(download_result.get(key)) + ) + rls.info.append(rls_info) db.commit() log_movie = '%s (%s) in %s' % (getTitle(movie['library']), movie['library']['year'], rls.quality.label) @@ -333,7 +342,7 @@ class Searcher(Plugin): 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 "%s" downloaders are enabled or gave an error', (data.get('type', ''))) return False