diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 434cdc6b..eb88f815 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1258,7 +1258,6 @@ class TVShows(Items): kodicursor.execute(query, (title, plot, rating, writer, premieredate, runtime, director, season, episode, title, airsBeforeSeason, airsBeforeEpisode, seasonid, episodeid)) - self.logMsg("Checkpoint 1", 2) else: query = ' '.join(( @@ -1272,10 +1271,8 @@ class TVShows(Items): airsBeforeEpisode, episodeid)) # Update the checksum in emby table - self.logMsg("Checkpoint 2", 2) emby_db.updateReference(itemid, checksum) # Update parentid reference - self.logMsg("Checkpoint 3", 2) emby_db.updateParentId(itemid, seasonid) ##### OR ADD THE EPISODE ##### @@ -1327,7 +1324,6 @@ class TVShows(Items): "WHERE idPath = ?" )) kodicursor.execute(query, (path, None, None, 1, pathid)) - self.logMsg("Checkpoint 4", 2) # Update the file query = ' '.join(( @@ -1336,24 +1332,17 @@ class TVShows(Items): "WHERE idFile = ?" )) kodicursor.execute(query, (pathid, filename, dateadded, fileid)) - self.logMsg("Checkpoint 5", 2) # Process cast people = API.getPeopleList() kodi_db.addPeople(episodeid, people, "episode") # Process artwork - self.logMsg("Checkpoint 6", 2) artworks = API.getAllArtwork() - self.logMsg("Checkpoint 7", 2) artwork.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", kodicursor) - self.logMsg("Checkpoint 8", 2) # Process stream details streams = API.getMediaStreams() - self.logMsg("Checkpoint 9", 2) kodi_db.addStreams(fileid, streams, runtime) - self.logMsg("Checkpoint 7", 2) # Process playstates kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed) - self.logMsg("Checkpoint 8", 2) if not self.directpath and resume: # Create additional entry for widgets. This is only required for plugin/episode. temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/") @@ -1364,7 +1353,6 @@ class TVShows(Items): "SET idPath = ?, strFilename = ?, dateAdded = ?", "WHERE idFile = ?" )) - self.logMsg("Checkpoint 9", 2) kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid)) kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed) diff --git a/resources/lib/plexbmc.helper/__init__.py b/resources/lib/plexbmc.helper/__init__.py new file mode 100644 index 00000000..b93054b3 --- /dev/null +++ b/resources/lib/plexbmc.helper/__init__.py @@ -0,0 +1 @@ +# Dummy file to make this directory a package. diff --git a/resources/lib/plexbmc.helper/functions.py b/resources/lib/plexbmc.helper/functions.py new file mode 100644 index 00000000..83948e41 --- /dev/null +++ b/resources/lib/plexbmc.helper/functions.py @@ -0,0 +1,195 @@ +import base64 +import inspect +import json +import string +import traceback +import xbmc +from settings import settings +from httppersist import requests + +def xbmc_photo(): + return "photo" +def xbmc_video(): + return "video" +def xbmc_audio(): + return "audio" + +def plex_photo(): + return "photo" +def plex_video(): + return "video" +def plex_audio(): + return "music" + +def xbmc_type(plex_type): + if plex_type == plex_photo(): + return xbmc_photo() + elif plex_type == plex_video(): + return xbmc_video() + elif plex_type == plex_audio(): + return xbmc_audio() + +def plex_type(xbmc_type): + if xbmc_type == xbmc_photo(): + return plex_photo() + elif xbmc_type == xbmc_video(): + return plex_video() + elif xbmc_type == xbmc_audio(): + return plex_audio() + +def getPlatform(): + if xbmc.getCondVisibility('system.platform.osx'): + return "MacOSX" + elif xbmc.getCondVisibility('system.platform.atv2'): + return "AppleTV2" + elif xbmc.getCondVisibility('system.platform.ios'): + return "iOS" + elif xbmc.getCondVisibility('system.platform.windows'): + return "Windows" + elif xbmc.getCondVisibility('system.platform.raspberrypi'): + return "RaspberryPi" + elif xbmc.getCondVisibility('system.platform.linux'): + return "Linux" + elif xbmc.getCondVisibility('system.platform.android'): + return "Android" + return "Unknown" + +def printDebug( msg, functionname=True ): + if settings['debug']: + if functionname is False: + print str(msg) + else: + print "PleXBMC Helper -> " + inspect.stack()[1][3] + ": " + str(msg) + +""" communicate with XBMC """ +def jsonrpc(action, arguments = {}): + """ put some JSON together for the JSON-RPC APIv6 """ + if action.lower() == "sendkey": + request=json.dumps({ "jsonrpc" : "2.0" , "method" : "Input.SendText", "params" : { "text" : self.arguments[0], "done" : False }} ) + elif action.lower() == "ping": + request=json.dumps({ "jsonrpc" : "2.0", + "id" : 1 , + "method" : "JSONRPC.Ping" }) + elif action.lower() == "playmedia": + fullurl=arguments[0] + resume=arguments[1] + xbmc.Player().play("plugin://plugin.video.plexbmc/?mode=5&force="+resume+"&url="+fullurl) + return True + elif arguments: + request=json.dumps({ "id" : 1, + "jsonrpc" : "2.0", + "method" : action, + "params" : arguments}) + else: + request=json.dumps({ "id" : 1, + "jsonrpc" : "2.0", + "method" : action}) + + printDebug("Sending request to XBMC without network stack: %s" % request) + result = parseJSONRPC(xbmc.executeJSONRPC(request)) + + if not result and settings['webserver_enabled']: + # xbmc.executeJSONRPC appears to fail on the login screen, but going + # through the network stack works, so let's try the request again + result = parseJSONRPC(requests.post( + "127.0.0.1", + settings['port'], + "/jsonrpc", + request, + { 'Content-Type' : 'application/json', + 'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) })) + + return result + + + +def parseJSONRPC(jsonraw): + if not jsonraw: + printDebug("Empty response from XBMC") + return {} + else: + printDebug("Response from XBMC: %s" % jsonraw) + parsed=json.loads(jsonraw) + if parsed.get('error', False): + print "XBMC returned an error: %s" % parsed.get('error') + return parsed.get('result', {}) + +def getXMLHeader(): + return ''+"\r\n" + +def getOKMsg(): + return getXMLHeader() + '' + +def getPlexHeaders(): + h = { + "Content-type": "application/x-www-form-urlencoded", + "Access-Control-Allow-Origin": "*", + "X-Plex-Version": settings['version'], + "X-Plex-Client-Identifier": settings['uuid'], + "X-Plex-Provides": "player", + "X-Plex-Product": "PleXBMC", + "X-Plex-Device-Name": settings['client_name'], + "X-Plex-Platform": "XBMC", + "X-Plex-Model": getPlatform(), + "X-Plex-Device": "PC", + } + if settings['myplex_user']: + h["X-Plex-Username"] = settings['myplex_user'] + return h + +def getServerByHost(host): + list = settings['serverList'] + if len(list) == 1: + return list[0] + for server in list: + if server.get('serverName') in host or server.get('server') in host: + return server + return {} + +def getPlayers(): + info = jsonrpc("Player.GetActivePlayers") or [] + ret = {} + for player in info: + player['playerid'] = int(player['playerid']) + ret[player['type']] = player + return ret + +def getPlayerIds(): + ret = [] + for player in getPlayers().values(): + ret.append(player['playerid']) + return ret + +def getVideoPlayerId(players = False): + if players is None: + players = getPlayers() + return players.get(xbmc_video(), {}).get('playerid', None) + +def getAudioPlayerId(players = False): + if players is None: + players = getPlayers() + return players.get(xbmc_audio(), {}).get('playerid', None) + +def getPhotoPlayerId(players = False): + if players is None: + players = getPlayers() + return players.get(xbmc_photo(), {}).get('playerid', None) + +def getVolume(): + return str(jsonrpc('Application.GetProperties', { "properties": [ "volume" ] }).get('volume', 100)) + +def timeToMillis(time): + return (time['hours']*3600 + time['minutes']*60 + time['seconds'])*1000 + time['milliseconds'] + +def millisToTime(t): + millis = int(t) + seconds = millis / 1000 + minutes = seconds / 60 + hours = minutes / 60 + seconds = seconds % 60 + minutes = minutes % 60 + millis = millis % 1000 + return {'hours':hours,'minutes':minutes,'seconds':seconds,'milliseconds':millis} + +def textFromXml(element): + return element.firstChild.data \ No newline at end of file diff --git a/resources/lib/plexbmc.helper/httppersist.py b/resources/lib/plexbmc.helper/httppersist.py new file mode 100644 index 00000000..8d8857f9 --- /dev/null +++ b/resources/lib/plexbmc.helper/httppersist.py @@ -0,0 +1,76 @@ +import httplib +import traceback +import string + +class RequestMgr: + def __init__(self): + self.conns = {} + + def getConnection(self, protocol, host, port): + conn = self.conns.get(protocol+host+str(port), False) + if not conn: + if protocol=="https": + conn = httplib.HTTPSConnection(host, port) + else: + conn = httplib.HTTPConnection(host, port) + self.conns[protocol+host+str(port)] = conn + return conn + + def closeConnection(self, protocol, host, port): + conn = self.conns.get(protocol+host+str(port), False) + if conn: + conn.close() + self.conns.pop(protocol+host+str(port), None) + + def dumpConnections(self): + for conn in self.conns.values(): + conn.close() + self.conns = {} + + def post(self, host, port, path, body, header={}, protocol="http"): + conn = None + try: + conn = self.getConnection(protocol, host, port) + header['Connection'] = "keep-alive" + conn.request("POST", path, body, header) + data = conn.getresponse() + if int(data.status) >= 400: + print "HTTP response error: " + str(data.status) + # this should return false, but I'm hacking it since iOS returns 404 no matter what + return data.read() or True + else: + return data.read() or True + except: + print "Unable to connect to %s\nReason:" % host + traceback.print_exc() + self.conns.pop(protocol+host+str(port), None) + if conn: + conn.close() + return False + + def getwithparams(self, host, port, path, params, header={}, protocol="http"): + newpath = path + '?' + pairs = [] + for key in params: + pairs.append(str(key)+'='+str(params[key])) + newpath += string.join(pairs, '&') + return self.get(host, port, newpath, header, protocol) + + def get(self, host, port, path, header={}, protocol="http"): + try: + conn = self.getConnection(protocol, host, port) + header['Connection'] = "keep-alive" + conn.request("GET", path, headers=header) + data = conn.getresponse() + if int(data.status) >= 400: + print "HTTP response error: " + str(data.status) + return False + else: + return data.read() or True + except: + print "Unable to connect to %s\nReason: %s" % (host, traceback.print_exc()) + self.conns.pop(protocol+host+str(port), None) + conn.close() + return False + +requests = RequestMgr() diff --git a/resources/lib/plexbmc.helper/listener.py b/resources/lib/plexbmc.helper/listener.py new file mode 100644 index 00000000..20b0ef87 --- /dev/null +++ b/resources/lib/plexbmc.helper/listener.py @@ -0,0 +1,190 @@ +import re +import traceback +import xbmc +from SocketServer import ThreadingMixIn +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from urlparse import urlparse, parse_qs +from settings import settings +from functions import * +from subscribers import subMgr + +class MyHandler(BaseHTTPRequestHandler): + protocol_version = 'HTTP/1.1' + def log_message(s, format, *args): + # I have my own logging, suppressing BaseHTTPRequestHandler's + #printDebug(format % args) + return True + def do_HEAD(s): + printDebug( "Serving HEAD request..." ) + s.answer_request(0) + + def do_GET(s): + printDebug( "Serving GET request..." ) + s.answer_request(1) + + def do_OPTIONS(s): + s.send_response(200) + s.send_header('Content-Length', '0') + s.send_header('X-Plex-Client-Identifier', settings['uuid']) + s.send_header('Content-Type', 'text/plain') + s.send_header('Connection', 'close') + s.send_header('Access-Control-Max-Age', '1209600') + s.send_header('Access-Control-Allow-Origin', '*') + s.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, HEAD') + s.send_header('Access-Control-Allow-Headers', 'x-plex-version, x-plex-platform-version, x-plex-username, x-plex-client-identifier, x-plex-target-client-identifier, x-plex-device-name, x-plex-platform, x-plex-product, accept, x-plex-device') + s.end_headers() + s.wfile.close() + + def response(s, body, headers = {}, code = 200): + try: + s.send_response(code) + for key in headers: + s.send_header(key, headers[key]) + s.send_header('Content-Length', len(body)) + s.send_header('Connection', "close") + s.end_headers() + s.wfile.write(body) + s.wfile.close() + except: + pass + + def answer_request(s, sendData): + try: + request_path=s.path[1:] + request_path=re.sub(r"\?.*","",request_path) + url = urlparse(s.path) + paramarrays = parse_qs(url.query) + params = {} + for key in paramarrays: + params[key] = paramarrays[key][0] + printDebug ( "request path is: [%s]" % ( request_path,) ) + printDebug ( "params are: %s" % params ) + subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False)) + if request_path=="version": + s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % settings['version']) + elif request_path=="verify": + result=jsonrpc("ping") + s.response("XBMC JSON connection test:\r\n"+result) + elif "resources" == request_path: + resp = getXMLHeader() + resp += "" + resp += " fullurl: %s" % fullurl) + jsonrpc("playmedia", [fullurl, resume]) + subMgr.lastkey = params['key'] + subMgr.server = server.get('server', 'localhost') + subMgr.port = port + subMgr.protocol = protocol + subMgr.notify() + elif request_path == "player/playback/play": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True}) + elif request_path == "player/playback/pause": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False}) + elif request_path == "player/playback/stop": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Stop", {"playerid" : playerid}) + elif request_path == "player/playback/seekTo": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))}) + subMgr.notify() + elif request_path == "player/playback/stepForward": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"}) + subMgr.notify() + elif request_path == "player/playback/stepBack": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"}) + subMgr.notify() + elif request_path == "player/playback/skipNext": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"}) + subMgr.notify() + elif request_path == "player/playback/skipPrevious": + s.response(getOKMsg(), getPlexHeaders()) + for playerid in getPlayerIds(): + jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"}) + subMgr.notify() + elif request_path == "player/navigation/moveUp": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Up") + elif request_path == "player/navigation/moveDown": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Down") + elif request_path == "player/navigation/moveLeft": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Left") + elif request_path == "player/navigation/moveRight": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Right") + elif request_path == "player/navigation/select": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Select") + elif request_path == "player/navigation/home": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Home") + elif request_path == "player/navigation/back": + s.response(getOKMsg(), getPlexHeaders()) + jsonrpc("Input.Back") + except: + traceback.print_exc() + +class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): + daemon_threads = True \ No newline at end of file diff --git a/resources/lib/plexbmc.helper/plexgdm.py b/resources/lib/plexbmc.helper/plexgdm.py new file mode 100644 index 00000000..209ccf66 --- /dev/null +++ b/resources/lib/plexbmc.helper/plexgdm.py @@ -0,0 +1,318 @@ +""" +PlexGDM.py - Version 0.2 + +This class implements the Plex GDM (G'Day Mate) protocol to discover +local Plex Media Servers. Also allow client registration into all local +media servers. + + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +MA 02110-1301, USA. +""" + +__author__ = 'DHJ (hippojay) ' + +import socket +import struct +import sys +import re +import threading +import time +import urllib2 + +class plexgdm: + + def __init__(self, debug=False): + + self.discover_message = 'M-SEARCH * HTTP/1.0' + self.client_header = '* HTTP/1.0' + self.client_data = None + self.client_id = None + + self._multicast_address = '239.0.0.250' + self.discover_group = (self._multicast_address, 32414) + self.client_register_group = (self._multicast_address, 32413) + self.client_update_port = 32412 + + self.server_list = [] + self.discovery_interval = 120 + + self._discovery_is_running = False + self._registration_is_running = False + + self.discovery_complete = False + self.client_registered = False + self.debug = debug + + def __printDebug(self, message, level=1): + if self.debug: + print "PlexGDM: %s" % message + + def clientDetails(self, c_id, c_name, c_post, c_product, c_version): + self.client_data = "Content-Type: plex/media-player\r\nResource-Identifier: %s\r\nName: %s\r\nPort: %s\r\nProduct: %s\r\nVersion: %s\r\nProtocol: plex\r\nProtocol-Version: 1\r\nProtocol-Capabilities: navigation,playback,timeline\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version ) + self.client_id = c_id + + def getClientDetails(self): + if not self.client_data: + self.__printDebug("Client data has not been initialised. Please use PlexGDM.clientDetails()") + + return self.client_data + + def client_update (self): + update_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + + #Set socket reuse, may not work on all OSs. + try: + update_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) + except: + pass + + #Attempt to bind to the socket to recieve and send data. If we can;t do this, then we cannot send registration + try: + update_sock.bind(('0.0.0.0',self.client_update_port)) + except: + self.__printDebug( "Error: Unable to bind to port [%s] - client will not be registered" % self.client_update_port, 0) + return + + update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255) + status = update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._multicast_address) + socket.inet_aton('0.0.0.0')) + update_sock.setblocking(0) + self.__printDebug("Sending registration data: HELLO %s\r\n%s" % (self.client_header, self.client_data), 3) + + #Send initial client registration + try: + update_sock.sendto("HELLO %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group) + except: + self.__printDebug( "Error: Unable to send registeration message" , 0) + + #Now, listen for client discovery reguests and respond. + while self._registration_is_running: + try: + data, addr = update_sock.recvfrom(1024) + self.__printDebug("Recieved UDP packet from [%s] containing [%s]" % (addr, data.strip()), 3) + except socket.error, e: + pass + else: + if "M-SEARCH * HTTP/1." in data: + self.__printDebug("Detected client discovery request from %s. Replying" % ( addr ,) , 2) + try: + update_sock.sendto("HTTP/1.0 200 OK\r\n%s" % self.client_data, addr) + except: + self.__printDebug( "Error: Unable to send client update message",0) + + self.__printDebug("Sending registration data: HTTP/1.0 200 OK\r\n%s" % (self.client_data), 3) + self.client_registered = True + time.sleep(0.5) + + self.__printDebug("Client Update loop stopped",1) + + #When we are finished, then send a final goodbye message to deregister cleanly. + self.__printDebug("Sending registration data: BYE %s\r\n%s" % (self.client_header, self.client_data), 3) + try: + update_sock.sendto("BYE %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group) + except: + self.__printDebug( "Error: Unable to send client update message" ,0) + + self.client_registered = False + + def check_client_registration(self): + + if self.client_registered and self.discovery_complete: + + if not self.server_list: + self.__printDebug("Server list is empty. Unable to check",2) + return False + + try: + media_server=self.server_list[0]['server'] + media_port=self.server_list[0]['port'] + + self.__printDebug("Checking server [%s] on port [%s]" % (media_server, media_port) ,2) + f = urllib2.urlopen('http://%s:%s/clients' % (media_server, media_port)) + client_result = f.read() + if self.client_id in client_result: + self.__printDebug("Client registration successful",1) + self.__printDebug("Client data is: %s" % client_result, 3) + return True + else: + self.__printDebug("Client registration not found",1) + self.__printDebug("Client data is: %s" % client_result, 3) + + except: + self.__printDebug("Unable to check status") + pass + + return False + + def getServerList (self): + return self.server_list + + def discover(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + # Set a timeout so the socket does not block indefinitely + sock.settimeout(0.6) + + # Set the time-to-live for messages to 1 for local network + ttl = struct.pack('b', 1) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl) + + returnData = [] + try: + # Send data to the multicast group + self.__printDebug("Sending discovery messages: %s" % self.discover_message, 2) + sent = sock.sendto(self.discover_message, self.discover_group) + + # Look for responses from all recipients + while True: + try: + data, server = sock.recvfrom(1024) + self.__printDebug("Received data from %s, %s" % server, 3) + self.__printDebug("Data received is:\r\n %s" % data, 3) + returnData.append( { 'from' : server, + 'data' : data } ) + except socket.timeout: + break + except: + # if we can't send our discovery query, just abort and try again + # on the next loop + return + finally: + sock.close() + + self.discovery_complete = True + + discovered_servers = [] + + if returnData: + + for response in returnData: + update = { 'server' : response.get('from')[0] } + + #Check if we had a positive HTTP response + if "200 OK" in response.get('data'): + + for each in response.get('data').split('\r\n'): + + update['discovery'] = "auto" + update['owned']='1' + update['master']= 1 + update['role']='master' + update['class']=None + + if "Content-Type:" in each: + update['content-type'] = each.split(':')[1].strip() + elif "Resource-Identifier:" in each: + update['uuid'] = each.split(':')[1].strip() + elif "Name:" in each: + update['serverName'] = each.split(':')[1].strip() + elif "Port:" in each: + update['port'] = each.split(':')[1].strip() + elif "Updated-At:" in each: + update['updated'] = each.split(':')[1].strip() + elif "Version:" in each: + update['version'] = each.split(':')[1].strip() + elif "Server-Class:" in each: + update['class'] = each.split(':')[1].strip() + + discovered_servers.append(update) + + self.server_list = discovered_servers + + if not self.server_list: + self.__printDebug("No servers have been discovered",1) + else: + self.__printDebug("Number of servers Discovered: %s" % len(self.server_list),1) + for items in self.server_list: + self.__printDebug("Server Discovered: %s" % items['serverName'] ,2) + + + def setInterval(self, interval): + self.discovery_interval = interval + + def stop_all(self): + self.stop_discovery() + self.stop_registration() + + def stop_discovery(self): + if self._discovery_is_running: + self.__printDebug("Discovery shutting down", 1) + self._discovery_is_running = False + self.discover_t.join() + del self.discover_t + else: + self.__printDebug("Discovery not running", 1) + + def stop_registration(self): + if self._registration_is_running: + self.__printDebug("Registration shutting down", 1) + self._registration_is_running = False + self.register_t.join() + del self.register_t + else: + self.__printDebug("Registration not running", 1) + + def run_discovery_loop(self): + #Run initial discovery + self.discover() + + discovery_count=0 + while self._discovery_is_running: + discovery_count+=1 + if discovery_count > self.discovery_interval: + self.discover() + discovery_count=0 + time.sleep(1) + + def start_discovery(self, daemon = False): + if not self._discovery_is_running: + self.__printDebug("Discovery starting up", 1) + self._discovery_is_running = True + self.discover_t = threading.Thread(target=self.run_discovery_loop) + self.discover_t.setDaemon(daemon) + self.discover_t.start() + else: + self.__printDebug("Discovery already running", 1) + + def start_registration(self, daemon = False): + if not self._registration_is_running: + self.__printDebug("Registration starting up", 1) + self._registration_is_running = True + self.register_t = threading.Thread(target=self.client_update) + self.register_t.setDaemon(daemon) + self.register_t.start() + else: + self.__printDebug("Registration already running", 1) + + def start_all(self, daemon = False): + self.start_discovery(daemon) + self.start_registration(daemon) + + +#Example usage +if __name__ == '__main__': + client = plexgdm(debug=3) + client.clientDetails("Test-Name", "Test Client", "3003", "Test-App", "1.2.3") + client.start_all() + while not client.discovery_complete: + print "Waiting for results" + time.sleep(1) + time.sleep(20) + print client.getServerList() + if client.check_client_registration(): + print "Successfully registered" + else: + print "Unsuccessfully registered" + client.stop_all() diff --git a/resources/lib/plexbmc.helper/settings.py b/resources/lib/plexbmc.helper/settings.py new file mode 100644 index 00000000..46bc1ca2 --- /dev/null +++ b/resources/lib/plexbmc.helper/settings.py @@ -0,0 +1,42 @@ +import uuid +import xbmc +import xbmcaddon +from xml.dom.minidom import parse + +settings = {} +try: + guidoc = parse(xbmc.translatePath('special://userdata/guisettings.xml')) +except: + print "Unable to read XBMC's guisettings.xml" + +def getGUI(name): + global guidoc + if guidoc is None: + return False + try: + return guidoc.getElementsByTagName(name)[0].firstChild.nodeValue + except: + return "" + +addon = xbmcaddon.Addon() +plexbmc = xbmcaddon.Addon('plugin.video.plexbmc') + +settings['debug'] = addon.getSetting('debug') == "true" +settings['gdm_debug'] = addon.getSetting('gdm_debug') == "true" +if addon.getSetting('use_xbmc_name') == "true": + settings['client_name'] = getGUI('devicename') +else: + settings['client_name'] = addon.getSetting('c_name') +# XBMC web server settings +settings['webserver_enabled'] = (getGUI('webserver') == "true") +settings['port'] = int(getGUI('webserverport')) +settings['user'] = getGUI('webserverusername') +settings['passwd'] = getGUI('webserverpassword') + +settings['uuid'] = str(addon.getSetting('uuid')) or str(uuid.uuid4()) +addon.setSetting('uuid', settings['uuid']) +settings['version'] = addon.getAddonInfo('version') +settings['plexbmc_version'] = plexbmc.getAddonInfo('version') +settings['myplex_user'] = plexbmc.getSetting('myplex_user') +settings['serverList'] = [] +settings['myport'] = 3005 diff --git a/resources/lib/plexbmc.helper/subscribers.py b/resources/lib/plexbmc.helper/subscribers.py new file mode 100644 index 00000000..cf5ebd2f --- /dev/null +++ b/resources/lib/plexbmc.helper/subscribers.py @@ -0,0 +1,197 @@ +import re +import threading +import xbmcgui +from xml.dom.minidom import parseString +from functions import * +from settings import settings +from httppersist import requests + +class SubscriptionManager: + def __init__(self): + self.subscribers = {} + self.info = {} + self.lastkey = "" + self.lastratingkey = "" + self.volume = 0 + self.guid = "" + self.server = "" + self.protocol = "http" + self.port = "" + self.playerprops = {} + self.sentstopped = True + + def getVolume(self): + self.volume = getVolume() + + def msg(self, players): + msg = getXMLHeader() + msg += ' 30: + sub.cleanup() + del self.subscribers[sub.uuid] + + def getPlayerProperties(self, playerid): + info = {} + try: + # get info from the player + props = jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled"]}) + printDebug(jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]})) + info['time'] = timeToMillis(props['time']) + info['duration'] = timeToMillis(props['totaltime']) + info['state'] = ("paused", "playing")[int(props['speed'])] + info['shuffle'] = ("0","1")[props.get('shuffled', False)] + except: + info['time'] = 0 + info['duration'] = 0 + info['state'] = "stopped" + info['shuffle'] = False + # get the volume from the application + info['volume'] = self.volume + info['guid'] = self.guid + + return info + +class Subscriber: + def __init__(self, protocol, host, port, uuid, commandID): + self.protocol = protocol or "http" + self.host = host + self.port = port or 32400 + self.uuid = uuid or host + self.commandID = int(commandID) or 0 + self.navlocationsent = False + self.age = 0 + def __eq__(self, other): + return self.uuid == other.uuid + def tostr(self): + return "uuid=%s,commandID=%i" % (self.uuid, self.commandID) + def cleanup(self): + requests.closeConnection(self.protocol, self.host, self.port) + def send_update(self, msg, is_nav): + self.age += 1 + if not is_nav: + self.navlocationsent = False + elif self.navlocationsent: + return True + else: + self.navlocationsent = True + msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg) + printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg)) + if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol): + subMgr.removeSubscriber(self.uuid) + +subMgr = SubscriptionManager()