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 += ""
+ resp += ""
+ printDebug("crafted resources response: %s" % resp)
+ s.response(resp, getPlexHeaders())
+ elif "/subscribe" in request_path:
+ s.response(getOKMsg(), getPlexHeaders())
+ protocol = params.get('protocol', False)
+ host = s.client_address[0]
+ port = params.get('port', False)
+ uuid = s.headers.get('X-Plex-Client-Identifier', "")
+ commandID = params.get('commandID', 0)
+ subMgr.addSubscriber(protocol, host, port, uuid, commandID)
+ elif "/poll" in request_path:
+ if params.get('wait', False) == '1':
+ xbmc.sleep(950)
+ commandID = params.get('commandID', 0)
+ s.response(re.sub(r"INSERTCOMMANDID", str(commandID), subMgr.msg(getPlayers())), {
+ 'X-Plex-Client-Identifier': settings['uuid'],
+ 'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
+ 'Access-Control-Allow-Origin': '*',
+ 'Content-Type': 'text/xml'
+ })
+ elif "/unsubscribe" in request_path:
+ s.response(getOKMsg(), getPlexHeaders())
+ uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0]
+ subMgr.removeSubscriber(uuid)
+ elif request_path == "player/playback/setParameters":
+ s.response(getOKMsg(), getPlexHeaders())
+ if 'volume' in params:
+ volume = int(params['volume'])
+ printDebug("adjusting the volume to %s%%" % volume)
+ jsonrpc("Application.SetVolume", {"volume": volume})
+ elif "/playMedia" in request_path:
+ s.response(getOKMsg(), getPlexHeaders())
+ resume = params.get('viewOffset', params.get('offset', "0"))
+ protocol = params.get('protocol', "http")
+ address = params.get('address', s.client_address[0])
+ server = getServerByHost(address)
+ port = params.get('port', server.get('port', '32400'))
+ fullurl = protocol+"://"+address+":"+port+params['key']
+ printDebug("playMedia command -> 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 += '' % self.mainlocation
+
+ msg += self.getTimelineXML(getAudioPlayerId(players), plex_audio())
+ msg += self.getTimelineXML(getPhotoPlayerId(players), plex_photo())
+ msg += self.getTimelineXML(getVideoPlayerId(players), plex_video())
+ msg += "\r\n"
+ return msg
+
+ def getTimelineXML(self, playerid, ptype):
+ if playerid is not None:
+ info = self.getPlayerProperties(playerid)
+ # save this info off so the server update can use it too
+ self.playerprops[playerid] = info;
+ state = info['state']
+ time = info['time']
+ else:
+ state = "stopped"
+ time = 0
+ ret = "\r\n"+''
+ return ret
+
+ def updateCommandID(self, uuid, commandID):
+ if commandID and self.subscribers.get(uuid, False):
+ self.subscribers[uuid].commandID = int(commandID)
+
+ def notify(self, event = False):
+ self.cleanup()
+ players = getPlayers()
+ # fetch the message, subscribers or not, since the server
+ # will need the info anyway
+ msg = self.msg(players)
+ if self.subscribers:
+ with threading.RLock():
+ for sub in self.subscribers.values():
+ sub.send_update(msg, len(players)==0)
+ self.notifyServer(players)
+ return True
+
+ def notifyServer(self, players):
+ if not players and self.sentstopped: return True
+ params = {'state': 'stopped'}
+ for p in players.values():
+ info = self.playerprops[p.get('playerid')]
+ params = {}
+ params['containerKey'] = (self.lastkey or "/library/metadata/900000")
+ params['key'] = (self.lastkey or "/library/metadata/900000")
+ params['ratingKey'] = (self.lastratingkey or "900000")
+ params['state'] = info['state']
+ params['time'] = info['time']
+ params['duration'] = info['duration']
+ serv = getServerByHost(self.server)
+ requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
+ printDebug("sent server notification with state = %s" % params['state'])
+ WINDOW = xbmcgui.Window(10000)
+ WINDOW.setProperty('plexbmc.nowplaying.sent', '1')
+ if players:
+ self.sentstopped = False
+ else:
+ self.sentstopped = True
+
+ def controllable(self):
+ return "playPause,play,stop,skipPrevious,skipNext,volume,stepBack,stepForward,seekTo"
+
+ def addSubscriber(self, protocol, host, port, uuid, commandID):
+ sub = Subscriber(protocol, host, port, uuid, commandID)
+ with threading.RLock():
+ self.subscribers[sub.uuid] = sub
+ return sub
+
+ def removeSubscriber(self, uuid):
+ with threading.RLock():
+ for sub in self.subscribers.values():
+ if sub.uuid == uuid or sub.host == uuid:
+ sub.cleanup()
+ del self.subscribers[sub.uuid]
+
+ def cleanup(self):
+ with threading.RLock():
+ for sub in self.subscribers.values():
+ if sub.age > 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()