diff --git a/couchpotato/core/notifications/plex/__init__.py b/couchpotato/core/notifications/plex/__init__.py old mode 100644 new mode 100755 index c00ea6d4..70c0a3e5 --- a/couchpotato/core/notifications/plex/__init__.py +++ b/couchpotato/core/notifications/plex/__init__.py @@ -18,9 +18,14 @@ config = [{ }, { 'name': 'host', + 'label': 'Media Server', 'default': 'localhost', - 'description': 'Default should be on localhost', - 'advanced': True, + 'description': 'Hostname/IP, default localhost' + }, + { + 'name': 'clients', + 'default': '', + '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 old mode 100644 new mode 100755 index f6088f5b..5558289d --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -1,79 +1,184 @@ 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 datetime import datetime from urlparse import urlparse from xml.dom import minidom +import json +import requests import traceback +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 + http_time_between_calls = 0 + def __init__(self): super(Plex, self).__init__() + + self.clients = {} + self.clients_updated = None + addEvent('renamer.after', self.addToLibrary) - def addToLibrary(self, message = None, group = None): + 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 = {} + + data = self.urlopen('%s/clients' % self.createHost(self.conf('media_server'), port = 32400)) + client_result = etree.fromstring(data) + + clients = [x.strip().lower() for x in self.conf('clients').split(',')] + + for server in client_result.findall('Server'): + if server.get('name').lower() in clients: + clients.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(clients) > 0: + log.info2('Unable to find plex clients: %s', ', '.join(clients)) + + log.info2('Found hosts: %s', ', '.join(self.clients.keys())) + + self.clients_updated = datetime.now() + + + def addToLibrary(self, message = None, group = {}): if self.isDisabled(): return - if not group: group = {} log.info('Sending notification to Plex') - hosts = self.getHosts(port = 32400) - for host in hosts: + source_type = ['movie'] + base_url = '%s/library/sections' % self.createHost(self.conf('media_server'), port = 32400) + 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') - 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 = None, listener = None): - if not data: data = {} + def sendHTTP(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 notifyHTTP(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.sendHTTP(data, client): + successful += 1 + + return successful == total + + def sendJSON(self, method, params, client): + log.debug('sendJSON("%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 notifyJSON(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.sendJSON('GUI.ShowNotification', params, client): + successful += 1 + + return successful == total + + def notify(self, message = '', data = {}, listener = None, force = False): + self.updateClients(force) + + http_result = self.notifyHTTP(message, data, listener) + json_result = self.notifyJSON(message, data, listener) + + return http_result and json_result + def test(self, **kwargs): test_type = self.testNotifyName() @@ -83,7 +188,8 @@ class Plex(Notification): success = self.notify( message = self.test_message, data = {}, - listener = 'test' + listener = 'test', + force = True ) success2 = self.addToLibrary() @@ -91,17 +197,12 @@ class Plex(Notification): 'success': success or success2 } - def getHosts(self, port = None): + def createHost(self, host, port = None): - raw_hosts = splitString(self.conf('host')) - hosts = [] + h = cleanHost(host) + p = urlparse(h) + h = h.rstrip('/') + if port and not p.port: + h += ':%s' % port - 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 + return h