diff --git a/couchpotato/core/notifications/plex/client.py b/couchpotato/core/notifications/plex/client.py new file mode 100644 index 0000000..b873518 --- /dev/null +++ b/couchpotato/core/notifications/plex/client.py @@ -0,0 +1,85 @@ +import json +from couchpotato import CPLog +from couchpotato.core.event import addEvent +from couchpotato.core.helpers.encoding import tryUrlencode +import requests + +log = CPLog(__name__) + + +class PlexClientProtocol(object): + def __init__(self, plex): + self.plex = plex + + addEvent('notify.plex.notifyClient', self.notify) + + def notify(self, client, message): + raise NotImplementedError() + + +class PlexClientHTTP(PlexClientProtocol): + def request(self, command, client): + url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % ( + client['address'], + client['port'], + tryUrlencode(command) + ) + + headers = {} + + try: + self.plex.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(self, client, message): + if client.get('protocol') != 'xbmchttp': + return None + + data = { + 'command': 'ExecBuiltIn', + 'parameter': 'Notification(CouchPotato, %s)' % message + } + + return self.request(data, client) + + +class PlexClientJSON(PlexClientProtocol): + def request(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 notify(self, client, message): + if client.get('protocol') not in ['xbmcjson', 'plex']: + return None + + params = { + 'title': 'CouchPotato', + 'message': message + } + return self.request('GUI.ShowNotification', params, client) diff --git a/couchpotato/core/notifications/plex/main.py b/couchpotato/core/notifications/plex/main.py index 19ca670..3c127fe 100755 --- a/couchpotato/core/notifications/plex/main.py +++ b/couchpotato/core/notifications/plex/main.py @@ -1,183 +1,59 @@ -from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import tryUrlencode -from couchpotato.core.helpers.variable import cleanHost +from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification -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 +from .client import PlexClientHTTP, PlexClientJSON +from .server import PlexServer 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 + self.server = PlexServer(self) - addEvent('renamer.after', self.addToLibrary) - - 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 ['plex', '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.client_protocols = { + 'http': PlexClientHTTP(self), + 'json': PlexClientJSON(self) + } - self.clients_updated = datetime.now() + addEvent('renamer.after', self.addToLibrary) def addToLibrary(self, message = None, group = {}): if self.isDisabled(): return - log.info('Sending notification to Plex') - - source_type = ['movie'] - base_url = '%s/library/sections' % self.createHost(self.conf('media_server'), port = 32400) - 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') - - 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', - (self.conf('media_server'), traceback.format_exc(1))) - return False - - return True - - def sendHTTP(self, command, client): - url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % ( - client['address'], - client['port'], - tryUrlencode(command) - ) - - headers = {} + return self.server.refresh() - try: - 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 + def notifyClients(self, message, clients): + success = True - return True + while len(clients): + client = clients[0] - def notifyHTTP(self, message = '', data = {}, listener = None): - total = 0 - successful = 0 + success = fireEvent('notify.plex.notifyClient', client, message, single=True) - 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 + if success: + clients.pop(0) + else: + if self.server.staleClients(): + log.info('Failed to send notification to client "%s". ' + 'Client list is stale, updating the client list and retrying.', client['name']) + self.server.updateClients() + else: + log.warning('Failed to send notification to client %s, skipping this time', client['name']) + clients.pop(0) + success = False + break - return True + return success - 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'] in ['xbmcjson', 'plex']: - 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 notify(self, message = '', data = {}, listener = None): + return self.notifyClients(message, self.server.clients.values()) def test(self, **kwargs): @@ -185,24 +61,12 @@ class Plex(Notification): log.info('Sending test to %s', test_type) - success = self.notify( + notify_success = self.notify( message = self.test_message, data = {}, - listener = 'test', - force = True + listener = 'test' ) - 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 + refresh_success = self.addToLibrary() - return h + return {'success': notify_success or refresh_success} diff --git a/couchpotato/core/notifications/plex/server.py b/couchpotato/core/notifications/plex/server.py new file mode 100644 index 0000000..67e4937 --- /dev/null +++ b/couchpotato/core/notifications/plex/server.py @@ -0,0 +1,111 @@ +from datetime import timedelta, datetime +from couchpotato.core.helpers.variable import cleanHost +from couchpotato import CPLog +from urlparse import urlparse +import traceback + + +try: + import xml.etree.cElementTree as etree +except ImportError: + import xml.etree.ElementTree as etree + +log = CPLog(__name__) + + +class PlexServer(object): + def __init__(self, plex): + self.plex = plex + + self.clients = {} + self.last_clients_update = None + + def staleClients(self): + if not self.last_clients_update: + return True + + return self.last_clients_update + timedelta(minutes=15) < datetime.now() + + def request(self, path, data_type='xml'): + if not self.plex.conf('media_server'): + log.warning("Plex media server hostname is required") + return None + + if path.startswith('/'): + path = path[1:] + + data = self.plex.urlopen('%s/%s' % ( + self.createHost(self.plex.conf('media_server'), port = 32400), + path + )) + + if data_type == 'xml': + return etree.fromstring(data) + else: + return data + + def updateClients(self): + log.info('Searching for clients on Plex Media Server') + + self.clients = {} + + result = self.request('clients') + if not result: + return + + notify_clients = [ + x.strip().lower() + for x in self.plex.conf('clients').split(',') + ] + + found_clients = [ + c for c in result.findall('Server') + if c.get('name') and c.get('name').lower() in notify_clients + ] + + for client in found_clients: + name = client.get('name').lower() + + self.clients[name] = { + 'name': client.get('name'), + 'address': client.get('address'), + 'port': client.get('port'), + 'protocol': client.get('protocol', 'xbmchttp') + } + + notify_clients.remove(name) + + if len(notify_clients) > 0: + log.debug('Unable to find clients: %s', ', '.join(notify_clients)) + + self.last_clients_update = datetime.now() + + def refresh(self, section_types=None): + if not section_types: + section_types = ['movie'] + + sections = self.request('library/sections') + + try: + for section in sections.findall('Directory'): + if section.get('type') not in section_types: + continue + + self.request('library/sections/%s/refresh' % section.get('key'), 'text') + except: + log.error('Plex library update failed for %s, Media Server not running: %s', + (self.plex.conf('media_server'), traceback.format_exc(1))) + return False + + return True + + 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