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..fa274922 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,15 @@ 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): + return {'success': self.test()} + + 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 53b87d91..b94da29a 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 205ceb1b..048aa44d 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 f934de5f..a35cb006 100755 --- a/couchpotato/core/downloaders/rtorrent/main.py +++ b/couchpotato/core/downloaders/rtorrent/main.py @@ -18,6 +18,7 @@ class rTorrent(Downloader): protocol = ['torrent', 'torrent_magnet'] rt = None + testable = True # Migration url to host options def __init__(self): @@ -37,9 +38,9 @@ class rTorrent(Downloader): self.deleteConf('url') - def connect(self): + def connect(self, reconnect = False): # 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')) + '/' + self.conf('rpc_url').strip('/ ') + '/' @@ -53,8 +54,16 @@ class rTorrent(Downloader): else: self.rt = RTorrent(url) + if not self.rt.test_connection(): + self.rt = None + return self.rt + def test(self): + if not self.connect(True): + return False + return True + 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..fd23211e 100644 --- a/couchpotato/core/downloaders/sabnzbd/main.py +++ b/couchpotato/core/downloaders/sabnzbd/main.py @@ -15,6 +15,17 @@ log = CPLog(__name__) class Sabnzbd(Downloader): protocol = ['nzb'] + testable = True + + def test(self): + try: + sab_data = self.call({ + 'mode': 'version', + }) + except: + return False + + return bool(sab_data) 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 26a4558d..2ffbb8d8 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 d3f17598..5e952306 100644 --- a/couchpotato/core/downloaders/transmission/main.py +++ b/couchpotato/core/downloaders/transmission/main.py @@ -18,6 +18,7 @@ class Transmission(Downloader): protocol = ['torrent', 'torrent_magnet'] log = CPLog(__name__) trpc = None + testable = True def connect(self): # Load host from config and split out port. @@ -26,11 +27,16 @@ class Transmission(Downloader): log.error('Config properties are not filled in correctly, port is missing.') return False - if not self.trpc: + if not (self.trpc and self.trpc.get_session()): 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() 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 e527230c..6e3e4df3 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,11 @@ class uTorrent(Downloader): return self.utorrent_api + def test(self): + if self.connect() and self.utorrent_api.get_status(): + 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/static/scripts/misc/downloaders.js b/couchpotato/static/scripts/misc/downloaders.js new file mode 100644 index 00000000..eee45b40 --- /dev/null +++ b/couchpotato/static/scripts/misc/downloaders.js @@ -0,0 +1,80 @@ +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 message = new Element('span.failed', { + 'text': 'Connection failed. Check logs for details.' + }).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