diff --git a/couchpotato/core/_base/clientscript/main.py b/couchpotato/core/_base/clientscript/main.py index 248d2bc5..c1be7e73 100644 --- a/couchpotato/core/_base/clientscript/main.py +++ b/couchpotato/core/_base/clientscript/main.py @@ -49,6 +49,7 @@ class ClientScript(Plugin): 'scripts/page/settings.js', 'scripts/page/about.js', 'scripts/page/manage.js', + 'scripts/misc/downloaders.js', ], } diff --git a/couchpotato/core/downloaders/base.py b/couchpotato/core/downloaders/base.py index 71da65ee..bb57cca7 100644 --- a/couchpotato/core/downloaders/base.py +++ b/couchpotato/core/downloaders/base.py @@ -1,4 +1,5 @@ from base64 import b32decode, b16encode +from couchpotato.api import addApiView from couchpotato.core.event import addEvent from couchpotato.core.helpers.variable import mergeDicts from couchpotato.core.logger import CPLog @@ -14,6 +15,7 @@ class Downloader(Provider): protocol = [] http_time_between_calls = 0 status_support = True + testable = False torrent_sources = [ 'http://torrage.com/torrent/%s.torrent', @@ -42,6 +44,8 @@ class Downloader(Provider): addEvent('download.remove_failed', self._removeFailed) addEvent('download.pause', self._pause) addEvent('download.process_complete', self._processComplete) + addApiView('download.%s.is_testable' % self.getName().lower(), self.isTestable) + addApiView('download.%s.test' % self.getName().lower(), self._test) def getEnabledProtocol(self): for download_protocol in self.protocol: @@ -158,6 +162,18 @@ class Downloader(Provider): (d_manual and manual or d_manual is False) and \ (not data or self.isCorrectProtocol(data.get('protocol'))) + def isTestable(self): + return {'success': self.testable} + + def _test(self): + t = self.test() + if isinstance(t,tuple): + return {'success': t[0], 'msg': t[1] } + return {'success': t } + + def test(self): + return False + def _pause(self, release_download, pause = True): if self.isDisabled(manual = True, data = {}): return diff --git a/couchpotato/core/downloaders/deluge/main.py b/couchpotato/core/downloaders/deluge/main.py index c5f80167..3d72be73 100644 --- a/couchpotato/core/downloaders/deluge/main.py +++ b/couchpotato/core/downloaders/deluge/main.py @@ -19,19 +19,25 @@ class Deluge(Downloader): protocol = ['torrent', 'torrent_magnet'] log = CPLog(__name__) drpc = None + testable = True - def connect(self): + def connect(self, reconnect = False): # Load host from config and split out port. host = cleanHost(self.conf('host'), protocol = False).split(':') if not isInt(host[1]): log.error('Config properties are not filled in correctly, port is missing.') return False - if not self.drpc: + if not self.drpc or reconnect: self.drpc = DelugeRPC(host[0], port = host[1], username = self.conf('username'), password = self.conf('password')) return self.drpc + def test(self): + if self.connect(True) and self.drpc.test(): + return True + return False + def download(self, data = None, media = None, filedata = None): if not media: media = {} if not data: data = {} @@ -178,6 +184,13 @@ class DelugeRPC(object): self.client = DelugeClient() self.client.connect(self.host, int(self.port), self.username, self.password) + def test(self): + try: + self.connect() + except: + return False + return True + def add_torrent_magnet(self, torrent, options): torrent_id = False try: diff --git a/couchpotato/core/downloaders/nzbget/main.py b/couchpotato/core/downloaders/nzbget/main.py index a690572c..8b9c88d1 100644 --- a/couchpotato/core/downloaders/nzbget/main.py +++ b/couchpotato/core/downloaders/nzbget/main.py @@ -18,6 +18,28 @@ class NZBGet(Downloader): protocol = ['nzb'] rpc = 'xmlrpc' + testable = True + + def test(self): + url = cleanHost(host = self.conf('host'), ssl = self.conf('ssl'), username = self.conf('username'), password = self.conf('password')) + self.rpc + rpc = xmlrpclib.ServerProxy(url) + + try: + if rpc.writelog('INFO', 'CouchPotato connected to test connection'): + log.debug('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 as e: + if e.errcode == 401: + log.error('Password is incorrect.') + else: + log.error('Protocol Error: %s', e) + return False + + return True def download(self, data = None, media = None, filedata = None): if not media: media = {} diff --git a/couchpotato/core/downloaders/nzbvortex/main.py b/couchpotato/core/downloaders/nzbvortex/main.py index 236e624c..e02f7c1e 100644 --- a/couchpotato/core/downloaders/nzbvortex/main.py +++ b/couchpotato/core/downloaders/nzbvortex/main.py @@ -24,6 +24,15 @@ class NZBVortex(Downloader): protocol = ['nzb'] api_level = None session_id = None + testable = True + + def test(self): + try: + login_result = self.login() + except: + return False + + return login_result def download(self, data = None, media = None, filedata = None): if not media: media = {} diff --git a/couchpotato/core/downloaders/rtorrent/main.py b/couchpotato/core/downloaders/rtorrent/main.py index 75b6a249..fed2bfe2 100755 --- a/couchpotato/core/downloaders/rtorrent/main.py +++ b/couchpotato/core/downloaders/rtorrent/main.py @@ -19,6 +19,8 @@ class rTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] rt = None + testable = True + error_msg = '' # Migration url to host options def __init__(self): @@ -49,7 +51,7 @@ class rTorrent(Downloader): def connect(self): # Already connected? - if self.rt is not None: + if not reconnect and self.rt is not None: return self.rt url = cleanHost(self.conf('host'), protocol = True, ssl = self.conf('ssl')) @@ -68,9 +70,25 @@ class rTorrent(Downloader): else: self.rt = RTorrent(url) + self.error_msg = '' + try: + self.rt._verify_conn() + except AssertionError as e: + self.error_msg = e.message + self.rt = None + return self.rt - def updateProviderGroup(self, name, data): + def test(self): + if self.connect(True): + return True + + if self.error_msg: + return False, 'Connection failed: ' + self.error_msg + + return False + + def _update_provider_group(self, name, data): if data.get('seed_time'): log.info('seeding time ignored, not supported') diff --git a/couchpotato/core/downloaders/sabnzbd/main.py b/couchpotato/core/downloaders/sabnzbd/main.py index 72c23708..9eea8c9a 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -15,6 +15,27 @@ log = CPLog(__name__) class Sabnzbd(Downloader): protocol = ['nzb'] + testable = True + + def test(self): + try: + sab_data = self.call({ + 'mode': 'version', + }) + v = sab_data.split('.') + if int(v[0]) == 0 and int(v[1]) < 7: + return False, 'Your Sabnzbd client is too old, please update to newest version.' + + # the version check will work even with wrong api key, so we need the next check as well + sab_data = self.call({ + 'mode': 'qstatus', + }) + if not sab_data: + return False + except: + return False + + return True def download(self, data = None, media = None, filedata = None): if not media: media = {} diff --git a/couchpotato/core/downloaders/synology/main.py b/couchpotato/core/downloaders/synology/main.py index f964f37f..7fb0ee57 100644 --- a/couchpotato/core/downloaders/synology/main.py +++ b/couchpotato/core/downloaders/synology/main.py @@ -13,6 +13,17 @@ class Synology(Downloader): protocol = ['nzb', 'torrent', 'torrent_magnet'] status_support = False + testable = True + + def test(self): + host = cleanHost(self.conf('host'), protocol = False).split(':') + try: + srpc = SynologyRPC(host[0], host[1], self.conf('username'), self.conf('password')) + test_result = srpc.test() + except: + return False + + return test_result def download(self, data = None, media = None, filedata = None): if not media: media = {} @@ -147,3 +158,6 @@ class SynologyRPC(object): self._logout() return result + + def test(self): + return bool(self._login()) diff --git a/couchpotato/core/downloaders/transmission/main.py b/couchpotato/core/downloaders/transmission/main.py index 2daeab46..d64c270e 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -18,19 +18,25 @@ class Transmission(Downloader): protocol = ['torrent', 'torrent_magnet'] log = CPLog(__name__) trpc = None + testable = True - def connect(self): + def connect(self, reconnect = False): # Load host from config and split out port. host = cleanHost(self.conf('host'), protocol = False).split(':') if not isInt(host[1]): log.error('Config properties are not filled in correctly, port is missing.') return False - if not self.trpc: + if not self.trpc or reconnect: self.trpc = TransmissionRPC(host[0], port = host[1], rpc_url = self.conf('rpc_url').strip('/ '), username = self.conf('username'), password = self.conf('password')) return self.trpc + def test(self): + if self.connect(True) and self.trpc.get_session(): + return True + return False + def download(self, data = None, media = None, filedata = None): if not media: media = {} if not data: data = {} diff --git a/couchpotato/core/downloaders/utorrent/main.py b/couchpotato/core/downloaders/utorrent/main.py index e0d6a921..e1a111e4 100644 --- a/couchpotato/core/downloaders/utorrent/main.py +++ b/couchpotato/core/downloaders/utorrent/main.py @@ -1,5 +1,6 @@ from base64 import b16encode, b32decode from bencode import bencode as benc, bdecode +from couchpotato.api import addApiView from couchpotato.core.downloaders.base import Downloader, ReleaseDownloadList from couchpotato.core.helpers.encoding import isInt, ss, sp from couchpotato.core.helpers.variable import tryInt, tryFloat, cleanHost @@ -24,6 +25,7 @@ class uTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] utorrent_api = None + testable = True status_flags = { 'STARTED' : 1, 'CHECKING' : 2, @@ -46,6 +48,17 @@ class uTorrent(Downloader): return self.utorrent_api + def test(self): + if self.connect(): + build_version = self.utorrent_api.get_build() + if not build_version: + return False + if build_version < 25406: # This build corresponds to version 3.0.0 stable + return False, 'Your uTorrent client is too old, please update to newest version.' + return True + + return False + def download(self, data = None, media = None, filedata = None): if not media: media = {} if not data: data = {} @@ -322,3 +335,10 @@ class uTorrentAPI(object): def get_files(self, hash): action = 'action=getfiles&hash=%s' % hash return self._request(action) + + def get_build(self): + data = self._request('') + if not data: + return False + response = json.loads(data) + return int(response.get('build')) diff --git a/couchpotato/static/scripts/misc/downloaders.js b/couchpotato/static/scripts/misc/downloaders.js new file mode 100644 index 00000000..8d03c241 --- /dev/null +++ b/couchpotato/static/scripts/misc/downloaders.js @@ -0,0 +1,82 @@ +var DownloadersBase = new Class({ + + Implements: [Events], + + initialize: function(){ + var self = this; + + // Add test buttons to settings page + App.addEvent('load', self.addTestButtons.bind(self)); + + }, + + // Downloaders setting tests + addTestButtons: function(){ + var self = this; + + var setting_page = App.getPage('Settings'); + setting_page.addEvent('create', function(){ + Object.each(setting_page.tabs.downloaders.groups, self.addTestButton.bind(self)) + }) + + }, + + addTestButton: function(fieldset, plugin_name){ + var self = this, + button_name = self.testButtonName(fieldset); + + if(button_name.contains('Downloaders')) return; + + Api.request('download.'+plugin_name+'.is_testable', { + 'onComplete': function(json){ + if(json.success){ + // Only add test button if downloader is testable + new Element('.ctrlHolder.test_button').adopt( + new Element('a.button', { + 'text': button_name, + 'events': { + 'click': function(){ + var button = fieldset.getElement('.test_button .button'); + button.set('text', 'Connecting...'); + + Api.request('download.'+plugin_name+'.test', { + 'onComplete': function(json){ + + button.set('text', button_name); + + if(json.success){ + var message = new Element('span.success', { + 'text': 'Connection successful' + }).inject(button, 'after') + } + else { + var msg_text = 'Connection failed. Check logs for details.'; + if(json.hasOwnProperty('msg')) msg_text = json.msg; + var message = new Element('span.failed', { + 'text': msg_text + }).inject(button, 'after') + } + + (function(){ + message.destroy(); + }).delay(3000) + } + }); + } + } + }) + ).inject(fieldset); + } + } + }); + + }, + + testButtonName: function(fieldset){ + var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf("= MIN_RTORRENT_VERSION