From a6c32a7e30111524c9e3511cac548f6177a7350d Mon Sep 17 00:00:00 2001 From: Dean Gardiner Date: Mon, 29 Jul 2013 21:07:35 +1200 Subject: [PATCH 1/4] Fixed Plex notifications Conflicts: couchpotato/core/notifications/plex/main.py --- .../core/notifications/plex/__init__.py | 10 +- couchpotato/core/notifications/plex/main.py | 202 +++++++++++++----- 2 files changed, 152 insertions(+), 60 deletions(-) mode change 100644 => 100755 couchpotato/core/notifications/plex/__init__.py mode change 100644 => 100755 couchpotato/core/notifications/plex/main.py diff --git a/couchpotato/core/notifications/plex/__init__.py b/couchpotato/core/notifications/plex/__init__.py old mode 100644 new mode 100755 index c00ea6d4..e2984ad9 --- a/couchpotato/core/notifications/plex/__init__.py +++ b/couchpotato/core/notifications/plex/__init__.py @@ -17,10 +17,14 @@ config = [{ 'type': 'enabler', }, { - 'name': 'host', + 'name': 'media_server', 'default': 'localhost', - 'description': 'Default should be on localhost', - 'advanced': True, + 'description': 'Media server hostname' + }, + { + 'name': 'clients', + 'default': '', + 'description': 'Comma separated list of client names\'s (computer names).' }, { 'name': 'on_snatch', diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py old mode 100644 new mode 100755 index 02c9b30a..f9746a09 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -1,77 +1,179 @@ +from datetime import datetime +import json from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import tryUrlencode -from couchpotato.core.helpers.variable import cleanHost, splitString +from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification from urllib2 import URLError -from urlparse import urlparse from xml.dom import minidom import traceback +import requests + +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree log = CPLog(__name__) class Plex(Notification): + client_update_time = 5 * 60 def __init__(self): super(Plex, self).__init__() + self.clients = {} + self.clients_updated = None addEvent('renamer.after', self.addToLibrary) - def addToLibrary(self, message = None, group = {}): + def updateClients(self, force=False): + if not self.conf('media_server'): + log.warning("Plex media server hostname is required") + return + + since_update = ((datetime.now() - self.clients_updated).total_seconds())\ + if self.clients_updated is not None else None + + if force or self.clients_updated is None or since_update > self.client_update_time: + self.clients = {} + + client_result = etree.fromstring(self.urlopen('http://%s:32400/clients' % self.conf('media_server'))) + + hosts = [x.strip().lower() for x in self.conf('clients').split(',')] + + for server in client_result.findall('Server'): + if server.get('name').lower() in hosts: + hosts.remove(server.get('name').lower()) + protocol = server.get('protocol', 'xbmchttp') + + if protocol in ['xbmcjson', 'xbmchttp']: + self.clients[server.get('name')] = { + 'name': server.get('name'), + 'address': server.get('address'), + 'port': server.get('port'), + 'protocol': protocol + } + + if len(hosts) > 0: + log.warning('unable to find some plex hosts: %s', ', '.join(hosts)) + + log.info('found hosts: %s', ', '.join(self.clients.keys())) + + self.clients_updated = datetime.now() + + + def addToLibrary(self, message=None, group={}): if self.isDisabled(): return log.info('Sending notification to Plex') - hosts = self.getHosts(port = 32400) - for host in hosts: + source_type = ['movie'] + base_url = 'http://%s:32400/library/sections' % self.conf('media_server') + refresh_url = '%s/%%s/refresh' % base_url - source_type = ['movie'] - base_url = '%s/library/sections' % host - refresh_url = '%s/%%s/refresh' % base_url + try: + sections_xml = self.urlopen(base_url) + xml_sections = minidom.parseString(sections_xml) + sections = xml_sections.getElementsByTagName('Directory') - try: - sections_xml = self.urlopen(base_url) - xml_sections = minidom.parseString(sections_xml) - sections = xml_sections.getElementsByTagName('Directory') + for s in sections: + if s.getAttribute('type') in source_type: + url = refresh_url % s.getAttribute('key') + x = self.urlopen(url) - for s in sections: - if s.getAttribute('type') in source_type: - url = refresh_url % s.getAttribute('key') - x = self.urlopen(url) - - except: - log.error('Plex library update failed for %s, Media Server not running: %s', (host, traceback.format_exc(1))) - return False + except: + log.error('Plex library update failed for %s, Media Server not running: %s', + (self.conf('media_server'), traceback.format_exc(1))) + return False return True - def notify(self, message = '', data = {}, listener = None): + def send_http(self, command, client): + url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % ( + client['address'], + client['port'], + tryUrlencode(command) + ) - hosts = self.getHosts(port = 3000) - successful = 0 - for host in hosts: - if self.send({'command': 'ExecBuiltIn', 'parameter': 'Notification(CouchPotato, %s)' % message}, host): - successful += 1 - - return successful == len(hosts) - - def send(self, command, host): - - url = '%s/xbmcCmds/xbmcHttp/?%s' % (host, tryUrlencode(command)) headers = {} try: - self.urlopen(url, headers = headers, show_error = False) - except URLError: - log.error("Couldn't sent command to Plex, probably just running Media Server") - return False - except: - log.error("Couldn't sent command to Plex: %s", traceback.format_exc()) + self.urlopen(url, headers=headers, timeout=3, show_error=False) + except Exception, err: + log.error("Couldn't sent command to Plex: %s", err) return False - log.info('Plex notification to %s successful.', host) return True + def notify_http(self, message='', data={}, listener=None): + total = 0 + successful = 0 + + data = { + 'command': 'ExecBuiltIn', + 'parameter': 'Notification(CouchPotato, %s)' % message + } + + for name, client in self.clients.items(): + if client['protocol'] == 'xbmchttp': + total += 1 + if self.send_http(data, client): + successful += 1 + + return successful == total + + def send_json(self, method, params, client): + log.debug('send_json("%s", %s, %s)', (method, params, client)) + url = 'http://%s:%s/jsonrpc' % ( + client['address'], + client['port'] + ) + + headers = { + 'Content-Type': 'application/json' + } + + request = { + 'id':1, + 'jsonrpc': '2.0', + 'method': method, + 'params': params + } + + try: + requests.post(url, headers=headers, timeout=3, data=json.dumps(request)) + except Exception, err: + log.error("Couldn't sent command to Plex: %s", err) + return False + + return True + + def notify_json(self, message='', data={}, listener=None): + total = 0 + successful = 0 + + params = { + 'title': 'CouchPotato', + 'message': message + } + + for name, client in self.clients.items(): + if client['protocol'] == 'xbmcjson': + total += 1 + if self.send_json('GUI.ShowNotification', params, client): + successful += 1 + + return successful == total + + def notify(self, message='', data={}, listener=None, forceUpdate=True): + self.updateClients(forceUpdate) + + http_result = self.notify_http(message, data, listener) + json_result = self.notify_json(message, data, listener) + + return http_result and json_result + def test(self, **kwargs): test_type = self.testNotifyName() @@ -79,27 +181,13 @@ class Plex(Notification): log.info('Sending test to %s', test_type) success = self.notify( - message = self.test_message, - data = {}, - listener = 'test' + message=self.test_message, + data={}, + listener='test', + forceUpdate=True ) success2 = self.addToLibrary() return { 'success': success or success2 } - - def getHosts(self, port = None): - - raw_hosts = splitString(self.conf('host')) - hosts = [] - - for h in raw_hosts: - h = cleanHost(h) - p = urlparse(h) - h = h.rstrip('/') - if port and not p.port: - h += ':%s' % port - hosts.append(h) - - return hosts From c92aa91aa7e2624f36171943220d271d3d4ab13f Mon Sep 17 00:00:00 2001 From: Dean Gardiner Date: Mon, 29 Jul 2013 21:07:50 +1200 Subject: [PATCH 2/4] Corrected notify() force parameter default. --- couchpotato/core/notifications/plex/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index f9746a09..962e0b87 100755 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -166,8 +166,8 @@ class Plex(Notification): return successful == total - def notify(self, message='', data={}, listener=None, forceUpdate=True): - self.updateClients(forceUpdate) + def notify(self, message='', data={}, listener=None, force=False): + self.updateClients(force) http_result = self.notify_http(message, data, listener) json_result = self.notify_json(message, data, listener) From b824ef93bded98b09cfa41fa72374504c0753bfa Mon Sep 17 00:00:00 2001 From: Dean Gardiner Date: Sun, 4 Aug 2013 15:39:02 +1200 Subject: [PATCH 3/4] Fix plex notifications test method. --- couchpotato/core/notifications/plex/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index 962e0b87..09fd6ff1 100755 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -184,7 +184,7 @@ class Plex(Notification): message=self.test_message, data={}, listener='test', - forceUpdate=True + force=True ) success2 = self.addToLibrary() From 9b5166826f804ee0218c273de29cacf13b06ad55 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 24 Sep 2013 22:37:40 +0200 Subject: [PATCH 4/4] Cleanup Plex notification --- .../core/notifications/plex/__init__.py | 7 +- couchpotato/core/notifications/plex/main.py | 75 +++++++++++-------- 2 files changed, 49 insertions(+), 33 deletions(-) diff --git a/couchpotato/core/notifications/plex/__init__.py b/couchpotato/core/notifications/plex/__init__.py index e2984ad9..70c0a3e5 100755 --- a/couchpotato/core/notifications/plex/__init__.py +++ b/couchpotato/core/notifications/plex/__init__.py @@ -17,14 +17,15 @@ config = [{ 'type': 'enabler', }, { - 'name': 'media_server', + 'name': 'host', + 'label': 'Media Server', 'default': 'localhost', - 'description': 'Media server hostname' + 'description': 'Hostname/IP, default localhost' }, { 'name': 'clients', 'default': '', - 'description': 'Comma separated list of client names\'s (computer names).' + 'description': 'Comma separated list of client names\'s (computer names). Top right when you start Plex' }, { 'name': 'on_snatch', diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index 09fd6ff1..5558289d 100755 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -1,14 +1,14 @@ -from datetime import datetime -import json from couchpotato.core.event import addEvent from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.helpers.variable import cleanHost from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification -from urllib2 import URLError +from datetime import datetime +from urlparse import urlparse from xml.dom import minidom -import traceback +import json import requests +import traceback try: import xml.etree.cElementTree as etree @@ -19,15 +19,19 @@ log = CPLog(__name__) class Plex(Notification): + client_update_time = 5 * 60 + http_time_between_calls = 0 def __init__(self): super(Plex, self).__init__() + self.clients = {} self.clients_updated = None + addEvent('renamer.after', self.addToLibrary) - def updateClients(self, force=False): + def updateClients(self, force = False): if not self.conf('media_server'): log.warning("Plex media server hostname is required") return @@ -38,13 +42,14 @@ class Plex(Notification): if force or self.clients_updated is None or since_update > self.client_update_time: self.clients = {} - client_result = etree.fromstring(self.urlopen('http://%s:32400/clients' % self.conf('media_server'))) + data = self.urlopen('%s/clients' % self.createHost(self.conf('media_server'), port = 32400)) + client_result = etree.fromstring(data) - hosts = [x.strip().lower() for x in self.conf('clients').split(',')] + clients = [x.strip().lower() for x in self.conf('clients').split(',')] for server in client_result.findall('Server'): - if server.get('name').lower() in hosts: - hosts.remove(server.get('name').lower()) + if server.get('name').lower() in clients: + clients.remove(server.get('name').lower()) protocol = server.get('protocol', 'xbmchttp') if protocol in ['xbmcjson', 'xbmchttp']: @@ -55,21 +60,21 @@ class Plex(Notification): 'protocol': protocol } - if len(hosts) > 0: - log.warning('unable to find some plex hosts: %s', ', '.join(hosts)) + if len(clients) > 0: + log.info2('Unable to find plex clients: %s', ', '.join(clients)) - log.info('found hosts: %s', ', '.join(self.clients.keys())) + log.info2('Found hosts: %s', ', '.join(self.clients.keys())) self.clients_updated = datetime.now() - def addToLibrary(self, message=None, group={}): + def addToLibrary(self, message = None, group = {}): if self.isDisabled(): return log.info('Sending notification to Plex') source_type = ['movie'] - base_url = 'http://%s:32400/library/sections' % self.conf('media_server') + base_url = '%s/library/sections' % self.createHost(self.conf('media_server'), port = 32400) refresh_url = '%s/%%s/refresh' % base_url try: @@ -89,7 +94,7 @@ class Plex(Notification): return True - def send_http(self, command, client): + def sendHTTP(self, command, client): url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % ( client['address'], client['port'], @@ -99,14 +104,14 @@ class Plex(Notification): headers = {} try: - self.urlopen(url, headers=headers, timeout=3, show_error=False) + self.urlopen(url, headers = headers, timeout = 3, show_error = False) except Exception, err: log.error("Couldn't sent command to Plex: %s", err) return False return True - def notify_http(self, message='', data={}, listener=None): + def notifyHTTP(self, message = '', data = {}, listener = None): total = 0 successful = 0 @@ -118,13 +123,13 @@ class Plex(Notification): for name, client in self.clients.items(): if client['protocol'] == 'xbmchttp': total += 1 - if self.send_http(data, client): + if self.sendHTTP(data, client): successful += 1 return successful == total - def send_json(self, method, params, client): - log.debug('send_json("%s", %s, %s)', (method, params, client)) + def sendJSON(self, method, params, client): + log.debug('sendJSON("%s", %s, %s)', (method, params, client)) url = 'http://%s:%s/jsonrpc' % ( client['address'], client['port'] @@ -142,14 +147,14 @@ class Plex(Notification): } try: - requests.post(url, headers=headers, timeout=3, data=json.dumps(request)) + requests.post(url, headers = headers, timeout = 3, data = json.dumps(request)) except Exception, err: log.error("Couldn't sent command to Plex: %s", err) return False return True - def notify_json(self, message='', data={}, listener=None): + def notifyJSON(self, message = '', data = {}, listener = None): total = 0 successful = 0 @@ -161,16 +166,16 @@ class Plex(Notification): for name, client in self.clients.items(): if client['protocol'] == 'xbmcjson': total += 1 - if self.send_json('GUI.ShowNotification', params, client): + if self.sendJSON('GUI.ShowNotification', params, client): successful += 1 return successful == total - def notify(self, message='', data={}, listener=None, force=False): + def notify(self, message = '', data = {}, listener = None, force = False): self.updateClients(force) - http_result = self.notify_http(message, data, listener) - json_result = self.notify_json(message, data, listener) + http_result = self.notifyHTTP(message, data, listener) + json_result = self.notifyJSON(message, data, listener) return http_result and json_result @@ -181,13 +186,23 @@ class Plex(Notification): log.info('Sending test to %s', test_type) success = self.notify( - message=self.test_message, - data={}, - listener='test', - force=True + message = self.test_message, + data = {}, + listener = 'test', + force = True ) success2 = self.addToLibrary() return { 'success': success or success2 } + + def createHost(self, host, port = None): + + h = cleanHost(host) + p = urlparse(h) + h = h.rstrip('/') + if port and not p.port: + h += ':%s' % port + + return h