From 637339ea6288705b8e1896b61edd194c99c11e3b Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 17 Jul 2015 16:48:47 +0200 Subject: [PATCH] Fix SSL communication issues. Add remote server switch in config. Add compulsory server verification in settings. Store the machine_id in settings for quick retrieval. Add setup complete screen on setup wizard and increase delay. Refresh server URL params on startup and set up 12 hourly refresh schedule. Use outer join on history tables. Add extra exception in http_handler to catch http requests trying to communicate with https-only server. --- data/interfaces/default/config.html | 92 +++++++++++++++++++++------- data/interfaces/default/welcome.html | 20 +++++- plexpy/__init__.py | 7 ++- plexpy/config.py | 3 + plexpy/datafactory.py | 2 +- plexpy/http_handler.py | 5 ++ plexpy/plextv.py | 91 ++++++++++++++++++++++++++- plexpy/pmsconnect.py | 17 +++-- plexpy/webserve.py | 11 +++- 9 files changed, 216 insertions(+), 32 deletions(-) diff --git a/data/interfaces/default/config.html b/data/interfaces/default/config.html index b2c18b47..e592a6e8 100644 --- a/data/interfaces/default/config.html +++ b/data/interfaces/default/config.html @@ -146,6 +146,17 @@

Set your preferred time format. Click here to see the parameter list.

+
+
+

Plex.tv Authentication

+
+
+ + +

Token for Plex.tv authentication.

+
+

Fetch Token

+

@@ -154,21 +165,25 @@

Plex Media Server

- - -

IP Address for Plex Media Server.

+ + +

IP Address or hostname for Plex Media Server.

+
+
+ Remote Server +

Check this is your Plex Server is not on the same local network as PlexPy.

+
+
+ Force SSL +

Force PlexPy to connect to your Plex Server via SSL. Your server needs to have remote access enabled.

- - + +

Port that Plex Media Server is listening on.

-
- - -

Token for Plex.tv authentication.

-
-

Fetch Token

+ + Verify
@@ -181,12 +196,6 @@

If you have media indexing enabled on your server, use these on the activity pane.

-
-
- Force SSL -

Force PlexPy to connect to your Plex Server via SSL.

-
-

Plex Logs

@@ -825,15 +834,56 @@ $(document).ready(function () { var configForm = $("#configUpdate"); $('.save-button').click(function() { - if (configForm.parsley().validate()) { - doAjaxCall('configUpdate',$(this),'tabs',true); - return false; + if ($("#pms_identifier").val() == "") { + showMsg(' Please verify your server.',false,true,2000) } else { - showMsg(' Please verify your settings.',false,true,2000) + if (configForm.parsley().validate()) { + doAjaxCall('configUpdate',$(this),'tabs',true); + return false; + } else { + showMsg(' Please verify your settings.',false,true,2000) + } } }); }); + $( ".pms-settings" ).change(function() { + $("#pms_identifier").val(""); + $("#pms-verify-status").html(""); + }); + + $("#verify-plex-server").click(function() { + var pms_ip = $("#pms_ip").val() + var pms_port = $("#pms_port").val() + if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) { + $("#pms-verify-status").html(' Verifying server...'); + $('#pms-verify-status').fadeIn('fast'); + $.ajax({ + url: 'http://' + pms_ip + ':' + pms_port + '/identity', + cache: true, + async: true, + timeout: 5000, + error: function(jqXHR, textStatus, errorThrown) { + $("#pms-verify-status").html(' This is not a Plex Server!'); + $('#pms-verify-status').fadeIn('fast'); + }, + success: function (xml) { + if ( $(xml).find('MediaContainer').attr('machineIdentifier') ) { + $("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier')); + $("#pms-verify-status").html(' Server verified!'); + $('#pms-verify-status').fadeIn('fast'); + } else { + $("#pms-verify-status").html(' This is not a Plex Server!'); + $('#pms-verify-status').fadeIn('fast'); + } + } + }); + } else { + $("#pms-verify-status").html(' Please enter IP and port number.'); + $('#pms-verify-status').fadeIn('fast'); + } + }); + // Plex.tv auth token fetch $("#get-pms-auth-token").click(function() { $("#pms-token-status").html(' Fetching token...'); diff --git a/data/interfaces/default/welcome.html b/data/interfaces/default/welcome.html index 3fa324bc..571c4c6d 100644 --- a/data/interfaces/default/welcome.html +++ b/data/interfaces/default/welcome.html @@ -57,9 +57,16 @@ from plexpy import version
+
+ Server uses SSL +
+
+ Remote Server +
+ Verify @@ -160,6 +167,14 @@ from plexpy import version +
+

Setup Complete!

+
+

Setup is now complete. For more configuration options please visit the Settings menu on the home page.

+
+ Just configuring a few things, please wait... + +
@@ -185,6 +200,8 @@ from plexpy import version wizard.show(); wizard.on("submit", function(wizard) { + // Probably should not success before we know, but hopefully validation is good enough. + wizard.submitSuccess(); $.ajax({ url: "configUpdate", type: "POST", @@ -194,7 +211,7 @@ from plexpy import version complete: function (data) { setTimeout(function(){ location.reload(); - }, 3000); + }, 5000); } }) }); @@ -293,6 +310,7 @@ from plexpy import version }, success: function (xml) { if ( $(xml).find('MediaContainer').attr('machineIdentifier') ) { + $("#pms_identifier").val($(xml).find('MediaContainer').attr('machineIdentifier')); $("#pms-verify-status").html(' Server found!'); $('#pms-verify-status').fadeIn('fast'); pms_verified = true; diff --git a/plexpy/__init__.py b/plexpy/__init__.py index ce658cff..36bd552f 100644 --- a/plexpy/__init__.py +++ b/plexpy/__init__.py @@ -159,9 +159,13 @@ def initialize(config_file): else: LATEST_VERSION = CURRENT_VERSION + # Get the real PMS urls for SSL and remote access + if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT: + plextv.get_real_pms_url() + # Refresh the users list on startup if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: - plextv.refresh_users() + threading.Thread(target=plextv.refresh_users).start() # Store the original umask UMASK = os.umask(0) @@ -268,6 +272,7 @@ def initialize_scheduler(): seconds = 0 if CONFIG.PMS_IP: + schedule_job(plextv.get_real_pms_url, 'Refresh Plex Server URLs', hours=12, minutes=0, seconds=0) schedule_job(monitor.check_active_sessions, 'Check for active sessions', hours=0, minutes=0, seconds=seconds) # Refresh the users list diff --git a/plexpy/config.py b/plexpy/config.py index 55033cf9..b7f51e73 100644 --- a/plexpy/config.py +++ b/plexpy/config.py @@ -22,12 +22,15 @@ _CONFIG_DEFINITIONS = { 'GROUPING_USER_HISTORY': (int, 'PlexWatch', 0), 'GROUPING_CHARTS': (int, 'PlexWatch', 0), 'PLEXWATCH_DATABASE': (str, 'PlexWatch', ''), + 'PMS_IDENTIFIER': (str, 'PMS', ''), 'PMS_IP': (str, 'PMS', '127.0.0.1'), + 'PMS_IS_REMOTE': (int, 'PMS', 0), 'PMS_LOGS_FOLDER': (str, 'PMS', ''), 'PMS_PORT': (int, 'PMS', 32400), 'PMS_PASSWORD': (str, 'PMS', ''), 'PMS_TOKEN': (str, 'PMS', ''), 'PMS_SSL': (int, 'General', 0), + 'PMS_URL': (str, 'PMS', ''), 'PMS_USERNAME': (str, 'PMS', ''), 'PMS_USE_BIF': (int, 'PMS', 0), 'PMS_UUID': (str, 'PMS', ''), diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py index 9186e832..1f4ea71a 100644 --- a/plexpy/datafactory.py +++ b/plexpy/datafactory.py @@ -173,7 +173,7 @@ class DataFactory(object): search_regex=search_regex, custom_where=custom_where, group_by='', - join_type=['JOIN', 'JOIN', 'JOIN'], + join_type=['LEFT OUTER JOIN', 'JOIN', 'JOIN'], join_table=[t3, t2, t4], join_evals=[[t1 + '.user_id', t3 + '.user_id'], [t1 + '.id', t2 + '.id'], diff --git a/plexpy/http_handler.py b/plexpy/http_handler.py index e073e723..a03db388 100644 --- a/plexpy/http_handler.py +++ b/plexpy/http_handler.py @@ -70,6 +70,11 @@ class HTTPHandler(object): except IOError, e: logger.warn(u"Failed to access uri endpoint %s with error %s" % (uri, e)) return None + except Exception, e: + logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only?" % uri) + return None + except: + logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % uri) if request_status == 200: if output_format == 'dict': diff --git a/plexpy/plextv.py b/plexpy/plextv.py index 6469e934..4f60131c 100644 --- a/plexpy/plextv.py +++ b/plexpy/plextv.py @@ -43,6 +43,42 @@ def refresh_users(): else: logger.warn("Unable to refresh users list.") +def get_real_pms_url(): + logger.info("Requesting URLs for server...") + + # Reset any current PMS_URL value + plexpy.CONFIG.__setattr__('PMS_URL', '') + plexpy.CONFIG.write() + + if plexpy.CONFIG.PMS_SSL: + result = PlexTV().get_server_urls(include_https=True) + process_urls = True + elif plexpy.CONFIG.PMS_IS_REMOTE: + result = PlexTV().get_server_urls(include_https=False) + process_urls = True + else: + real_url = 'http://' + plexpy.CONFIG.PMS_IP + ':' + str(plexpy.CONFIG.PMS_PORT) + process_urls = False + + if process_urls: + if len(result) > 0: + for item in result: + if plexpy.CONFIG.PMS_IS_REMOTE and item['local'] == '0': + real_url = item['uri'] + else: + real_url = item['uri'] + + plexpy.CONFIG.__setattr__('PMS_URL', real_url) + plexpy.CONFIG.write() + logger.info("Server URL retrieved.") + else: + fallback_url = 'http://' + plexpy.CONFIG.PMS_IP + ':' + str(plexpy.CONFIG.PMS_PORT) + plexpy.CONFIG.__setattr__('PMS_URL', fallback_url) + plexpy.CONFIG.write() + logger.warn("Unable to retrieve server URLs. Using user-defined value.") + else: + plexpy.CONFIG.__setattr__('PMS_URL', real_url) + plexpy.CONFIG.write() class PlexTV(object): """ @@ -137,6 +173,18 @@ class PlexTV(object): return request + def get_plextv_resources(self, include_https=False, output_format=''): + if include_https: + uri = '/api/resources?includeHttps=1' + else: + uri = '/api/resources' + request = self.request_handler.make_request(uri=uri, + proto=self.protocol, + request_type='GET', + output_format=output_format) + + return request + def get_full_users_list(self): friends_list = self.get_plextv_friends() own_account = self.get_plextv_user_details() @@ -299,4 +347,45 @@ class PlexTV(object): synced_items.append(sync_details) - return synced_items \ No newline at end of file + return synced_items + + def get_server_urls(self, include_https=True): + + if plexpy.CONFIG.PMS_IDENTIFIER: + server_id = plexpy.CONFIG.PMS_IDENTIFIER + else: + logger.error('PlexPy PlexTV connector :: Unable to retrieve server identity.') + return [] + + plextv_resources = self.get_plextv_resources(include_https=include_https) + server_urls = [] + + try: + xml_parse = minidom.parseString(plextv_resources) + except Exception, e: + logger.warn("Error parsing XML for Plex resources: %s" % e) + return [] + except: + logger.warn("Error parsing XML for Plex resources.") + return [] + + try: + xml_head = xml_parse.getElementsByTagName('Device') + except: + logger.warn("Error parsing XML for Plex resources.") + return [] + + for a in xml_head: + if helpers.get_xml_attr(a, 'clientIdentifier') == server_id: + connections = a.getElementsByTagName('Connection') + for connection in connections: + server_details = {"protocol": helpers.get_xml_attr(connection, 'protocol'), + "address": helpers.get_xml_attr(connection, 'address'), + "port": helpers.get_xml_attr(connection, 'port'), + "uri": helpers.get_xml_attr(connection, 'uri'), + "local": helpers.get_xml_attr(connection, 'local') + } + + server_urls.append(server_details) + + return server_urls \ No newline at end of file diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py index 53317725..29d73516 100644 --- a/plexpy/pmsconnect.py +++ b/plexpy/pmsconnect.py @@ -14,6 +14,7 @@ # along with PlexPy. If not, see . from plexpy import logger, helpers, datafactory, http_handler +from urlparse import urlparse import plexpy @@ -24,12 +25,18 @@ class PmsConnect(object): """ def __init__(self): - if plexpy.CONFIG.PMS_SSL: - self.protocol = 'HTTPS' + if plexpy.CONFIG.PMS_URL: + url_parsed = urlparse(plexpy.CONFIG.PMS_URL) + hostname = url_parsed.hostname + port = url_parsed.port + self.protocol = url_parsed.scheme else: - self.protocol = 'HTTP' - self.request_handler = http_handler.HTTPHandler(host=plexpy.CONFIG.PMS_IP, - port=plexpy.CONFIG.PMS_PORT, + hostname = plexpy.CONFIG.PMS_IP + port = plexpy.CONFIG.PMS_PORT + self.protocol = 'http' + + self.request_handler = http_handler.HTTPHandler(host=hostname, + port=port, token=plexpy.CONFIG.PMS_TOKEN) """ diff --git a/plexpy/webserve.py b/plexpy/webserve.py index b238563c..964493e5 100644 --- a/plexpy/webserve.py +++ b/plexpy/webserve.py @@ -71,9 +71,12 @@ class WebInterface(object): config = { "launch_browser": checked(plexpy.CONFIG.LAUNCH_BROWSER), "refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP), + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, "pms_ip": plexpy.CONFIG.PMS_IP, + "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE), "pms_port": plexpy.CONFIG.PMS_PORT, "pms_token": plexpy.CONFIG.PMS_TOKEN, + "pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_uuid": plexpy.CONFIG.PMS_UUID, "tv_notify_enable": checked(plexpy.CONFIG.TV_NOTIFY_ENABLE), "movie_notify_enable": checked(plexpy.CONFIG.MOVIE_NOTIFY_ENABLE), @@ -364,6 +367,7 @@ class WebInterface(object): "email_smtp_password": plexpy.CONFIG.EMAIL_SMTP_PASSWORD, "email_smtp_port": int(plexpy.CONFIG.EMAIL_SMTP_PORT), "email_tls": checked(plexpy.CONFIG.EMAIL_TLS), + "pms_identifier": plexpy.CONFIG.PMS_IDENTIFIER, "pms_ip": plexpy.CONFIG.PMS_IP, "pms_logs_folder": plexpy.CONFIG.PMS_LOGS_FOLDER, "pms_port": plexpy.CONFIG.PMS_PORT, @@ -395,7 +399,8 @@ class WebInterface(object): "ip_logging_enable": checked(plexpy.CONFIG.IP_LOGGING_ENABLE), "video_logging_enable": checked(plexpy.CONFIG.VIDEO_LOGGING_ENABLE), "music_logging_enable": checked(plexpy.CONFIG.MUSIC_LOGGING_ENABLE), - "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL + "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL, + "pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE) } return serve_template(templatename="config.html", title="Settings", config=config) @@ -416,7 +421,7 @@ class WebInterface(object): "tv_notify_on_start", "movie_notify_on_start", "music_notify_on_start", "tv_notify_on_stop", "movie_notify_on_stop", "music_notify_on_stop", "tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "refresh_users_on_startup", - "ip_logging_enable", "video_logging_enable", "music_logging_enable" + "ip_logging_enable", "video_logging_enable", "music_logging_enable", "pms_is_remote" ] for checked_config in checked_configs: if checked_config not in kwargs: @@ -441,6 +446,8 @@ class WebInterface(object): # Reconfigure scheduler plexpy.initialize_scheduler() + plextv.get_real_pms_url() + # Refresh users table. Probably shouldn't do this on every config save, will improve this later. threading.Thread(target=plextv.refresh_users).start()