Merge branch 'feature/dev_plex' of git://github.com/fuzeman/CouchPotatoServer into fuzeman-feature/dev_plex
This commit is contained in:
85
couchpotato/core/notifications/plex/client.py
Normal file
85
couchpotato/core/notifications/plex/client.py
Normal file
@@ -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)
|
||||
@@ -1,183 +1,68 @@
|
||||
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)
|
||||
|
||||
self.client_protocols = {
|
||||
'http': PlexClientHTTP(self),
|
||||
'json': PlexClientJSON(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.clients_updated = datetime.now()
|
||||
|
||||
|
||||
def addToLibrary(self, message = None, group = {}):
|
||||
if self.isDisabled(): return
|
||||
|
||||
log.info('Sending notification to Plex')
|
||||
return self.server.refresh()
|
||||
|
||||
source_type = ['movie']
|
||||
base_url = '%s/library/sections' % self.createHost(self.conf('media_server'), port = 32400)
|
||||
refresh_url = '%s/%%s/refresh' % base_url
|
||||
def getClientNames(self):
|
||||
return [
|
||||
x.strip().lower()
|
||||
for x in self.conf('clients').split(',')
|
||||
]
|
||||
|
||||
try:
|
||||
sections_xml = self.urlopen(base_url)
|
||||
xml_sections = minidom.parseString(sections_xml)
|
||||
sections = xml_sections.getElementsByTagName('Directory')
|
||||
def notifyClients(self, message, client_names):
|
||||
success = True
|
||||
|
||||
for s in sections:
|
||||
if s.getAttribute('type') in source_type:
|
||||
url = refresh_url % s.getAttribute('key')
|
||||
x = self.urlopen(url)
|
||||
while len(client_names):
|
||||
client_name = client_names[0]
|
||||
client_success = False
|
||||
client = self.server.clients.get(client_name)
|
||||
|
||||
except:
|
||||
log.error('Plex library update failed for %s, Media Server not running: %s',
|
||||
(self.conf('media_server'), traceback.format_exc(1)))
|
||||
return False
|
||||
if client and client['found']:
|
||||
client_success = fireEvent('notify.plex.notifyClient', client, message, single=True)
|
||||
|
||||
return True
|
||||
if client_success:
|
||||
client_names.pop(0)
|
||||
|
||||
def sendHTTP(self, command, client):
|
||||
url = 'http://%s:%s/xbmcCmds/xbmcHttp/?%s' % (
|
||||
client['address'],
|
||||
client['port'],
|
||||
tryUrlencode(command)
|
||||
)
|
||||
if not client_success:
|
||||
if self.server.staleClients() or not client:
|
||||
log.info('Failed to send notification to client "%s". '
|
||||
'Client list is stale, updating the client list and retrying.', client_name)
|
||||
self.server.updateClients(self.getClientNames())
|
||||
else:
|
||||
log.warning('Failed to send notification to client %s, skipping this time', client_name)
|
||||
client_names.pop(0)
|
||||
success = False
|
||||
|
||||
headers = {}
|
||||
return success
|
||||
|
||||
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
|
||||
|
||||
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'] 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.getClientNames())
|
||||
|
||||
def test(self, **kwargs):
|
||||
|
||||
@@ -185,24 +70,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
|
||||
}
|
||||
refresh_success = self.addToLibrary()
|
||||
|
||||
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
|
||||
return {'success': notify_success or refresh_success}
|
||||
|
||||
114
couchpotato/core/notifications/plex/server.py
Normal file
114
couchpotato/core/notifications/plex/server.py
Normal file
@@ -0,0 +1,114 @@
|
||||
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, client_names):
|
||||
log.info('Searching for clients on Plex Media Server')
|
||||
|
||||
self.clients = {}
|
||||
|
||||
result = self.request('clients')
|
||||
if not result:
|
||||
return
|
||||
|
||||
found_clients = [
|
||||
c for c in result.findall('Server')
|
||||
if c.get('name') and c.get('name').lower() in client_names
|
||||
]
|
||||
|
||||
# Store client details in cache
|
||||
for client in found_clients:
|
||||
name = client.get('name').lower()
|
||||
|
||||
self.clients[name] = {
|
||||
'name': client.get('name'),
|
||||
'found': True,
|
||||
'address': client.get('address'),
|
||||
'port': client.get('port'),
|
||||
'protocol': client.get('protocol', 'xbmchttp')
|
||||
}
|
||||
|
||||
client_names.remove(name)
|
||||
|
||||
# Store dummy info for missing clients
|
||||
for client_name in client_names:
|
||||
self.clients[client_name] = {
|
||||
'found': False
|
||||
}
|
||||
|
||||
if len(client_names) > 0:
|
||||
log.debug('Unable to find clients: %s', ', '.join(client_names))
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user